From b2f92b8461443c5cd8e3d1f7a8e4faa43045a243 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Tue, 14 Jan 2020 10:46:21 -0500 Subject: [PATCH] Update to 2018 edition of Rust (1.42). Fix and format code. Update dependencies. Fix tests. --- Cargo.toml | 14 +- README.md | 4 +- build/version.rs | 17 +- core-traits/Cargo.toml | 8 +- core-traits/lib.rs | 208 +-- core-traits/value_type_set.rs | 9 +- core-traits/values.rs | 3 +- core/Cargo.toml | 6 +- core/src/cache.rs | 37 +- core/src/counter.rs | 8 +- core/src/lib.rs | 134 +- core/src/sql_types.rs | 45 +- core/src/tx_report.rs | 13 +- core/src/util.rs | 6 +- db-traits/Cargo.toml | 2 +- db-traits/errors.rs | 82 +- db-traits/lib.rs | 3 +- db/Cargo.toml | 18 +- db/src/add_retract_alter_set.rs | 10 +- db/src/bootstrap.rs | 321 ++-- db/src/cache.rs | 10 +- db/src/db.rs | 118 +- db/src/debug.rs | 301 ++-- db/src/entids.rs | 17 +- db/src/internal_types.rs | 144 +- db/src/lib.rs | 90 +- db/src/metadata.rs | 135 +- db/src/schema.rs | 483 ++++-- db/src/timelines.rs | 453 +++-- db/src/tx.rs | 740 +++++---- db/src/tx_checking.rs | 23 +- db/src/tx_observer.rs | 87 +- db/src/types.rs | 67 +- db/src/upsert_resolution.rs | 211 ++- db/src/watcher.rs | 20 +- db/tests/value_tests.rs | 102 +- edn/Cargo.toml | 12 +- edn/src/entities.rs | 13 +- edn/src/intern_set.rs | 29 +- edn/src/lib.rs | 32 +- edn/src/matcher.rs | 76 +- edn/src/namespaceable_name.rs | 111 +- edn/src/pretty_print.rs | 96 +- edn/src/query.rs | 346 ++-- edn/src/symbols.rs | 58 +- edn/src/types.rs | 163 +- edn/src/utils.rs | 2 +- edn/src/value_rc.rs | 38 +- edn/tests/query_tests.rs | 414 ++--- edn/tests/serde_support.rs | 33 +- edn/tests/tests.rs | 881 +++++----- ffi/src/android.rs | 4 +- ffi/src/lib.rs | 507 ++++-- ffi/src/utils.rs | 51 +- public-traits/Cargo.toml | 12 +- public-traits/errors.rs | 80 +- public-traits/lib.rs | 6 +- query-algebrizer-traits/errors.rs | 43 +- query-algebrizer/Cargo.toml | 2 +- query-algebrizer/src/clauses/convert.rs | 193 ++- query-algebrizer/src/clauses/fulltext.rs | 447 +++-- query-algebrizer/src/clauses/ground.rs | 248 +-- query-algebrizer/src/clauses/inputs.rs | 30 +- query-algebrizer/src/clauses/mod.rs | 502 +++--- query-algebrizer/src/clauses/not.rs | 517 ++++-- query-algebrizer/src/clauses/or.rs | 583 ++++--- query-algebrizer/src/clauses/pattern.rs | 1006 +++++++----- query-algebrizer/src/clauses/predicate.rs | 276 ++-- query-algebrizer/src/clauses/resolve.rs | 185 ++- query-algebrizer/src/clauses/tx_log_api.rs | 472 ++++-- query-algebrizer/src/clauses/where_fn.rs | 13 +- query-algebrizer/src/lib.rs | 270 +-- query-algebrizer/src/types.rs | 279 ++-- query-algebrizer/src/validate.rs | 171 +- query-algebrizer/tests/fulltext.rs | 95 +- query-algebrizer/tests/ground.rs | 235 +-- query-algebrizer/tests/predicate.rs | 172 +- query-algebrizer/tests/type_reqs.rs | 32 +- query-algebrizer/tests/utils/mod.rs | 63 +- query-projector-traits/Cargo.toml | 2 +- query-projector-traits/aggregates.rs | 116 +- query-projector-traits/errors.rs | 26 +- query-projector-traits/lib.rs | 3 +- query-projector-traits/tests/aggregates.rs | 76 +- query-projector/Cargo.toml | 2 +- query-projector/src/binding_tuple.rs | 76 +- query-projector/src/lib.rs | 378 +++-- query-projector/src/project.rs | 260 +-- query-projector/src/projectors/constant.rs | 34 +- query-projector/src/projectors/mod.rs | 34 +- .../src/projectors/pull_two_stage.rs | 246 +-- query-projector/src/projectors/simple.rs | 153 +- query-projector/src/pull.rs | 63 +- query-projector/src/relresult.rs | 55 +- query-projector/src/translate.rs | 316 ++-- query-projector/tests/translate.rs | 1208 ++++++++------ query-pull-traits/errors.rs | 8 +- query-pull/Cargo.toml | 2 +- query-pull/src/lib.rs | 158 +- query-sql/Cargo.toml | 1 + query-sql/src/lib.rs | 488 +++--- sql-traits/errors.rs | 2 +- sql/Cargo.toml | 4 +- sql/src/lib.rs | 88 +- src/conn.rs | 654 +++++--- src/lib.rs | 145 +- src/query_builder.rs | 393 +++-- src/store.rs | 623 ++++--- src/sync.rs | 23 +- src/vocabulary.rs | 515 +++--- tests/api.rs | 11 +- tests/cache.rs | 307 +++- tests/entity_builder.rs | 393 ++--- tests/external_test.rs | 21 +- tests/pull.rs | 352 ++-- tests/query.rs | 1455 +++++++++++------ tests/tolstoy.rs | 1008 +++++++++--- tests/vocabulary.rs | 933 ++++++----- tolstoy-traits/Cargo.toml | 7 +- tolstoy-traits/errors.rs | 34 +- tolstoy-traits/lib.rs | 2 +- tolstoy/Cargo.toml | 19 +- tolstoy/src/bootstrap.rs | 42 +- tolstoy/src/datoms.rs | 16 +- tolstoy/src/debug.rs | 191 +-- tolstoy/src/lib.rs | 34 +- tolstoy/src/metadata.rs | 176 +- tolstoy/src/remote_client.rs | 269 ++- tolstoy/src/schema.rs | 4 +- tolstoy/src/syncer.rs | 388 +++-- tolstoy/src/tx_mapper.rs | 66 +- tolstoy/src/tx_processor.rs | 85 +- tolstoy/src/tx_uploader.rs | 74 +- tolstoy/src/types.rs | 17 +- tools/cli/Cargo.toml | 20 +- tools/cli/src/mentat_cli/command_parser.rs | 484 +++--- tools/cli/src/mentat_cli/input.rs | 87 +- tools/cli/src/mentat_cli/lib.rs | 94 +- tools/cli/src/mentat_cli/repl.rs | 306 ++-- transaction/Cargo.toml | 2 +- transaction/src/entity_builder.rs | 178 +- transaction/src/lib.rs | 425 ++--- transaction/src/metadata.rs | 78 +- transaction/src/query.rs | 381 ++--- 144 files changed, 15646 insertions(+), 11212 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3e7bb8e..c677051b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [package] +edition = "2018" authors = [ "Richard Newman ", "Nicholas Alexander ", @@ -12,7 +13,7 @@ authors = [ "Thom Chiovoloni ", ] name = "mentat" -version = "0.11.1" +version = "0.11.2" build = "build/version.rs" [features] @@ -29,14 +30,15 @@ rustc_version = "0.2" [dependencies] chrono = "0.4" -failure = "0.1.1" -lazy_static = "0.2" -time = "0.1" +failure = "0.1.6" +lazy_static = "1.4.0" +time = "0.2" log = "0.4" -uuid = { version = "0.5", features = ["v4", "serde"] } +uuid = { version = "0.8", features = ["v4", "serde"] } + [dependencies.rusqlite] -version = "0.19" +version = "0.21.0" # System sqlite might be very old. features = ["limits"] diff --git a/README.md b/README.md index e6fe41ae..b0235943 100644 --- a/README.md +++ b/README.md @@ -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) **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 -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. diff --git a/build/version.rs b/build/version.rs index f67ff18c..52199c51 100644 --- a/build/version.rs +++ b/build/version.rs @@ -8,24 +8,25 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // 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::process::exit; -use rustc_version::{ - Version, - version, -}; /// MIN_VERSION should be changed when there's a new minimum version of rustc required /// to build the project. -static MIN_VERSION: &'static str = "1.25.0"; +static MIN_VERSION: &'static str = "1.41.0"; fn main() { let ver = version().unwrap(); let min = Version::parse(MIN_VERSION).unwrap(); 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); } } diff --git a/core-traits/Cargo.toml b/core-traits/Cargo.toml index ed6ddee7..069c5553 100644 --- a/core-traits/Cargo.toml +++ b/core-traits/Cargo.toml @@ -9,11 +9,11 @@ path = "lib.rs" [dependencies] chrono = { version = "0.4", features = ["serde"] } -enum-set = "0.0.7" -lazy_static = "0.2" +enum-set = "0.0.8" +lazy_static = "1.4.0" indexmap = "1" -ordered-float = { version = "0.5", features = ["serde"] } -uuid = { version = "0.5", features = ["v4", "serde"] } +ordered-float = { version = "1.0.2", features = ["serde"] } +uuid = { version = "0.8", features = ["v4", "serde"] } serde = { version = "1.0", features = ["rc"] } serde_derive = "1.0" diff --git a/core-traits/lib.rs b/core-traits/lib.rs index 48b5cb45..9aff7ca5 100644 --- a/core-traits/lib.rs +++ b/core-traits/lib.rs @@ -8,84 +8,57 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate enum_set; -extern crate ordered_float; extern crate chrono; +extern crate enum_set; extern crate indexmap; -#[macro_use] extern crate serde_derive; -extern crate uuid; +extern crate ordered_float; +#[macro_use] +extern crate serde_derive; extern crate edn; +extern crate uuid; #[macro_use] extern crate lazy_static; use std::fmt; -use std::ffi::{ - CString, -}; +use std::ffi::CString; -use std::ops::{ - Deref, -}; +use std::ops::Deref; -use std::os::raw::{ - c_char, -}; +use std::os::raw::c_char; -use std::rc::{ - Rc, -}; +use std::rc::Rc; -use std::sync::{ - Arc, -}; +use std::sync::Arc; use std::collections::BTreeMap; -use indexmap::{ - IndexMap, -}; +use indexmap::IndexMap; use enum_set::EnumSet; use ordered_float::OrderedFloat; -use chrono::{ - DateTime, - Timelike, -}; +use chrono::{DateTime, Timelike}; use uuid::Uuid; -use edn::{ - Cloned, - ValueRc, - Utc, - Keyword, - FromMicros, - FromRc, -}; +use edn::{Cloned, FromMicros, FromRc, Keyword, Utc, ValueRc}; use edn::entities::{ - AttributePlace, - EntityPlace, - EntidOrIdent, - ValuePlace, - TransactableValueMarker, + AttributePlace, EntidOrIdent, EntityPlace, TransactableValueMarker, ValuePlace, }; -pub mod values; mod value_type_set; +pub mod values; -pub use value_type_set::{ - ValueTypeSet, -}; +pub use value_type_set::ValueTypeSet; #[macro_export] macro_rules! bail { - ($e:expr) => ( + ($e:expr) => { return Err($e.into()); - ) + }; } /// Represents one entid in the entid space. @@ -129,16 +102,14 @@ impl Into> for KnownEntid { /// 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. pub enum AttributeBitFlags { - IndexAVET = 1 << 0, - IndexVAET = 1 << 1, + IndexAVET = 1 << 0, + IndexVAET = 1 << 1, IndexFulltext = 1 << 2, - UniqueValue = 1 << 3, + UniqueValue = 1 << 3, } pub mod attribute { - use ::{ - TypedValue, - }; + use TypedValue; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] pub enum Unique { @@ -161,7 +132,7 @@ pub mod attribute { /// with the attribute are interpreted. /// /// 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 { /// The associated value type, i.e., `:db/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_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 { - Some(attribute::Unique::Value) => { 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()); }, + Some(attribute::Unique::Value) => { + 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 => (), } @@ -310,7 +298,6 @@ impl ValueType { } } - impl ::enum_set::CLike for ValueType { fn to_u32(&self) -> u32 { *self as u32 @@ -323,16 +310,19 @@ impl ::enum_set::CLike for ValueType { impl ValueType { pub fn into_keyword(self) -> Keyword { - Keyword::namespaced("db.type", match self { - ValueType::Ref => "ref", - ValueType::Boolean => "boolean", - ValueType::Instant => "instant", - ValueType::Long => "long", - ValueType::Double => "double", - ValueType::String => "string", - ValueType::Keyword => "keyword", - ValueType::Uuid => "uuid", - }) + Keyword::namespaced( + "db.type", + match self { + ValueType::Ref => "ref", + ValueType::Boolean => "boolean", + ValueType::Instant => "instant", + ValueType::Long => "long", + ValueType::Double => "double", + ValueType::String => "string", + ValueType::Keyword => "keyword", + ValueType::Uuid => "uuid", + }, + ) } pub fn from_keyword(keyword: &Keyword) -> Option { @@ -350,20 +340,23 @@ impl ValueType { "keyword" => Some(ValueType::Keyword), "uuid" => Some(ValueType::Uuid), _ => None, - } + }; } pub fn into_typed_value(self) -> TypedValue { - TypedValue::typed_ns_keyword("db.type", match self { - ValueType::Ref => "ref", - ValueType::Boolean => "boolean", - ValueType::Instant => "instant", - ValueType::Long => "long", - ValueType::Double => "double", - ValueType::String => "string", - ValueType::Keyword => "keyword", - ValueType::Uuid => "uuid", - }) + TypedValue::typed_ns_keyword( + "db.type", + match self { + ValueType::Ref => "ref", + ValueType::Boolean => "boolean", + ValueType::Instant => "instant", + ValueType::Long => "long", + ValueType::Double => "double", + ValueType::String => "string", + ValueType::Keyword => "keyword", + ValueType::Uuid => "uuid", + }, + ) } pub fn into_edn_value(self) -> edn::Value { @@ -382,23 +375,27 @@ impl ValueType { pub fn is_numeric(&self) -> bool { match self { &ValueType::Long | &ValueType::Double => true, - _ => false + _ => false, } } } impl fmt::Display for ValueType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", match *self { - ValueType::Ref => ":db.type/ref", - ValueType::Boolean => ":db.type/boolean", - ValueType::Instant => ":db.type/instant", - ValueType::Long => ":db.type/long", - ValueType::Double => ":db.type/double", - ValueType::String => ":db.type/string", - ValueType::Keyword => ":db.type/keyword", - ValueType::Uuid => ":db.type/uuid", - }) + write!( + f, + "{}", + match *self { + ValueType::Ref => ":db.type/ref", + ValueType::Boolean => ":db.type/boolean", + ValueType::Instant => ":db.type/instant", + ValueType::Long => ":db.type/long", + 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), Long(i64), Double(OrderedFloat), - Instant(DateTime), // Use `into()` to ensure truncation. + Instant(DateTime), // Use `into()` to ensure truncation. // TODO: &str throughout? String(ValueRc), Keyword(ValueRc), - 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 for TypedValue { @@ -552,7 +549,7 @@ impl TypedValue { // Return a C-owned pointer. Some(c.into_raw()) - }, + } _ => None, } } @@ -568,7 +565,7 @@ impl TypedValue { // Return a C-owned pointer. Some(c.into_raw()) - }, + } _ => None, } } @@ -577,14 +574,14 @@ impl TypedValue { match self { TypedValue::Uuid(v) => { // 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. let c: CString = CString::new(s).expect("String conversion failed!"); // Return a C-owned pointer. Some(c.into_raw()) - }, + } _ => None, } } @@ -598,7 +595,7 @@ impl TypedValue { pub fn into_uuid_string(self) -> Option { match self { - TypedValue::Uuid(v) => Some(v.hyphenated().to_string()), + TypedValue::Uuid(v) => Some(v.to_hyphenated().to_string()), _ => None, } } @@ -709,7 +706,6 @@ impl MicrosecondPrecision for DateTime { } } - /// The values bound in a query specification can be: /// /// * Vecs of structured values, for multi-valued component attributes or nested expressions. @@ -729,7 +725,10 @@ pub enum Binding { Map(ValueRc), } -impl From for Binding where T: Into { +impl From for Binding +where + T: Into, +{ fn from(value: T) -> Self { Binding::Scalar(value.into()) } @@ -813,7 +812,11 @@ impl Deref for StructuredMap { } impl StructuredMap { - pub fn insert(&mut self, name: N, value: B) where N: Into>, B: Into { + pub fn insert(&mut self, name: N, value: B) + where + N: Into>, + B: Into, + { self.0.insert(name.into(), value.into()); } } @@ -825,7 +828,10 @@ impl From, Binding>> for StructuredMap { } // Mostly for testing. -impl From> for StructuredMap where T: Into { +impl From> for StructuredMap +where + T: Into, +{ fn from(value: Vec<(Keyword, T)>) -> Self { let mut sm = StructuredMap::default(); for (k, v) in value.into_iter() { @@ -936,7 +942,7 @@ impl Binding { pub fn into_uuid_string(self) -> Option { match self { - Binding::Scalar(TypedValue::Uuid(v)) => Some(v.hyphenated().to_string()), + Binding::Scalar(TypedValue::Uuid(v)) => Some(v.to_hyphenated().to_string()), _ => None, } } diff --git a/core-traits/value_type_set.rs b/core-traits/value_type_set.rs index c510ad82..34d443b2 100644 --- a/core-traits/value_type_set.rs +++ b/core-traits/value_type_set.rs @@ -8,13 +8,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use enum_set::{ - EnumSet, -}; +use enum_set::EnumSet; -use ::{ - ValueType, -}; +use ValueType; trait EnumSetExtensions { /// Return a set containing both `x` and `y`. @@ -41,7 +37,6 @@ impl EnumSetExtensions for EnumSet { } } - #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ValueTypeSet(pub EnumSet); diff --git a/core-traits/values.rs b/core-traits/values.rs index 555e4098..214bd93f 100644 --- a/core-traits/values.rs +++ b/core-traits/values.rs @@ -10,12 +10,11 @@ #![allow(dead_code)] +use edn::symbols; /// Literal `Value` instances in the the "db" namespace. /// /// Used through-out the transactor to match core DB constructs. - use edn::types::Value; -use edn::symbols; /// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and /// `name`. diff --git a/core/Cargo.toml b/core/Cargo.toml index 38a8eed8..77976c8f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -5,11 +5,11 @@ workspace = ".." [dependencies] chrono = { version = "0.4", features = ["serde"] } -enum-set = "0.0.7" +enum-set = "0.0.8" failure = "0.1.1" indexmap = "1" -ordered-float = { version = "0.5", features = ["serde"] } -uuid = { version = "0.5", features = ["v4", "serde"] } +ordered-float = { version = "1.0.2", features = ["serde"] } +uuid = { version = "0.8", features = ["v4", "serde"] } [dependencies.core_traits] path = "../core-traits" diff --git a/core/src/cache.rs b/core/src/cache.rs index 85a3dbee..8fc6cc30 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -9,34 +9,41 @@ // specific language governing permissions and limitations under the License. /// Cache traits. +use std::collections::BTreeSet; -use std::collections::{ - BTreeSet, -}; +use core_traits::{Entid, TypedValue}; -use core_traits::{ - Entid, - TypedValue, -}; - -use ::{ - Schema, -}; +use Schema; pub trait CachedAttributes { fn is_attribute_cached_reverse(&self, entid: Entid) -> bool; fn is_attribute_cached_forward(&self, entid: Entid) -> bool; fn has_cached_attributes(&self) -> bool; - fn get_values_for_entid(&self, schema: &Schema, attribute: Entid, entid: Entid) -> Option<&Vec>; - fn get_value_for_entid(&self, schema: &Schema, attribute: Entid, entid: Entid) -> Option<&TypedValue>; + fn get_values_for_entid( + &self, + schema: &Schema, + attribute: Entid, + entid: Entid, + ) -> Option<&Vec>; + fn get_value_for_entid( + &self, + schema: &Schema, + attribute: Entid, + entid: Entid, + ) -> Option<&TypedValue>; /// Reverse lookup. fn get_entid_for_value(&self, attribute: Entid, value: &TypedValue) -> Option; - fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet>; + fn get_entids_for_value( + &self, + attribute: Entid, + value: &TypedValue, + ) -> Option<&BTreeSet>; } pub trait UpdateableCache { fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E> - where I: Iterator; + where + I: Iterator; } diff --git a/core/src/counter.rs b/core/src/counter.rs index b480b8e2..db4f352a 100644 --- a/core/src/counter.rs +++ b/core/src/counter.rs @@ -19,11 +19,15 @@ pub struct RcCounter { /// A simple shared counter. impl RcCounter { 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 { - RcCounter { c: Rc::new(Cell::new(0)) } + RcCounter { + c: Rc::new(Cell::new(0)), + } } /// Return the next value in the sequence. diff --git a/core/src/lib.rs b/core/src/lib.rs index 68589188..c0dd972f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,63 +19,35 @@ extern crate core_traits; extern crate edn; -use core_traits::{ - Attribute, - Entid, - KnownEntid, - ValueType, -}; +use core_traits::{Attribute, Entid, KnownEntid, ValueType}; mod cache; -use std::collections::{ - BTreeMap, -}; +use std::collections::BTreeMap; pub use uuid::Uuid; pub use chrono::{ DateTime, - Timelike, // For truncation. + Timelike, // For truncation. }; -pub use edn::{ - Cloned, - FromMicros, - FromRc, - Keyword, - ToMicros, - Utc, - ValueRc, -}; +pub use edn::{Cloned, FromMicros, FromRc, Keyword, ToMicros, Utc, ValueRc}; -pub use edn::parse::{ - parse_query, -}; +pub use edn::parse::parse_query; -pub use cache::{ - CachedAttributes, - UpdateableCache, -}; +pub use cache::{CachedAttributes, UpdateableCache}; +mod sql_types; +mod tx_report; /// Core types defining a Mentat knowledge base. mod types; -mod tx_report; -mod sql_types; -pub use tx_report::{ - TxReport, -}; +pub use tx_report::TxReport; -pub use types::{ - ValueTypeTag, -}; +pub use types::ValueTypeTag; -pub use sql_types::{ - SQLTypeAffinity, - SQLValueType, - SQLValueTypeSet, -}; +pub use sql_types::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet}; /// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`). pub type IdentMap = BTreeMap; @@ -119,15 +91,21 @@ pub struct Schema { pub trait HasSchema { fn entid_for_type(&self, t: ValueType) -> Option; - fn get_ident(&self, x: T) -> Option<&Keyword> where T: Into; + fn get_ident(&self, x: T) -> Option<&Keyword> + where + T: Into; fn get_entid(&self, x: &Keyword) -> Option; - fn attribute_for_entid(&self, x: T) -> Option<&Attribute> where T: Into; + fn attribute_for_entid(&self, x: T) -> Option<&Attribute> + where + T: Into; // Returns the attribute and the entid named by the provided ident. fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>; /// Return true if the provided entid identifies an attribute in this schema. - fn is_attribute(&self, x: T) -> bool where T: Into; + fn is_attribute(&self, x: T) -> bool + where + T: Into; /// Return true if the provided ident identifies an attribute in this schema. fn identifies_attribute(&self, x: &Keyword) -> bool; @@ -137,17 +115,24 @@ pub trait HasSchema { impl 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 } /// Returns an symbolic representation of the schema suitable for applying across Mentat stores. pub fn to_edn_value(&self) -> edn::Value { - edn::Value::Vector((&self.attribute_map).iter() - .map(|(entid, attribute)| - attribute.to_edn_value(self.get_ident(*entid).cloned())) - .collect()) + edn::Value::Vector( + (&self.attribute_map) + .iter() + .map(|(entid, attribute)| attribute.to_edn_value(self.get_ident(*entid).cloned())) + .collect(), + ) } fn get_raw_entid(&self, x: &Keyword) -> Option { @@ -156,10 +141,11 @@ impl Schema { pub fn update_component_attributes(&mut self) { let mut components: Vec; - components = self.attribute_map - .iter() - .filter_map(|(k, v)| if v.component { Some(*k) } else { None }) - .collect(); + components = self + .attribute_map + .iter() + .filter_map(|(k, v)| if v.component { Some(*k) } else { None }) + .collect(); components.sort_unstable(); self.component_attributes = components; } @@ -171,7 +157,10 @@ impl HasSchema for Schema { self.get_entid(&t.into_keyword()) } - fn get_ident(&self, x: T) -> Option<&Keyword> where T: Into { + fn get_ident(&self, x: T) -> Option<&Keyword> + where + T: Into, + { self.entid_map.get(&x.into()) } @@ -179,25 +168,33 @@ impl HasSchema for Schema { self.get_raw_entid(x).map(KnownEntid) } - fn attribute_for_entid(&self, x: T) -> Option<&Attribute> where T: Into { + fn attribute_for_entid(&self, x: T) -> Option<&Attribute> + where + T: Into, + { self.attribute_map.get(&x.into()) } fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> { - self.get_raw_entid(&ident) - .and_then(|entid| { - self.attribute_for_entid(entid).map(|a| (a, KnownEntid(entid))) - }) + self.get_raw_entid(&ident).and_then(|entid| { + self.attribute_for_entid(entid) + .map(|a| (a, KnownEntid(entid))) + }) } /// Return true if the provided entid identifies an attribute in this schema. - fn is_attribute(&self, x: T) -> bool where T: Into { + fn is_attribute(&self, x: T) -> bool + where + T: Into, + { self.attribute_map.contains_key(&x.into()) } /// Return true if the provided ident identifies an attribute in this schema. 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] { @@ -228,7 +225,7 @@ pub mod util; macro_rules! interpose { ( $name: pat, $across: expr, $body: block, $inter: block ) => { interpose_iter!($name, $across.iter(), $body, $inter) - } + }; } /// A helper to bind `name` to values in `across`, running `body` for each value, @@ -244,7 +241,7 @@ macro_rules! interpose_iter { $body; } } - } + }; } #[cfg(test)] @@ -253,10 +250,7 @@ mod test { use std::str::FromStr; - use core_traits::{ - attribute, - TypedValue, - }; + use core_traits::{attribute, TypedValue}; fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) { schema.entid_map.insert(e, i.clone()); @@ -269,8 +263,10 @@ mod test { #[test] fn test_datetime_truncation() { - let dt: DateTime = DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed"); - let expected: DateTime = DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed"); + let dt: DateTime = + DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed"); + let expected: DateTime = + DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed"); let tv: TypedValue = dt.into(); if let TypedValue::Instant(roundtripped) = tv { @@ -338,7 +334,9 @@ mod test { :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :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); // let's compare the whole thing again, just to make sure we are not changing anything when we convert to edn. diff --git a/core/src/sql_types.rs b/core/src/sql_types.rs index e32d01f9..ae51b5d1 100644 --- a/core/src/sql_types.rs +++ b/core/src/sql_types.rs @@ -8,18 +8,11 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::collections::{ - BTreeSet, -}; +use std::collections::BTreeSet; -use core_traits::{ - ValueType, - ValueTypeSet, -}; +use core_traits::{ValueType, ValueTypeSet}; -use types::{ - ValueTypeTag, -}; +use types::ValueTypeTag; /// Type safe representation of the possible return values from SQLite's `typeof` #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] @@ -48,15 +41,15 @@ pub trait SQLValueType { impl SQLValueType for ValueType { fn sql_representation(&self) -> (ValueTypeTag, Option) { match *self { - ValueType::Ref => (0, None), + ValueType::Ref => (0, None), ValueType::Boolean => (1, None), ValueType::Instant => (4, None), // SQLite distinguishes integral from decimal types, allowing long and double to share a tag. - ValueType::Long => (5, Some(SQLTypeAffinity::Integer)), - ValueType::Double => (5, Some(SQLTypeAffinity::Real)), - ValueType::String => (10, None), - ValueType::Uuid => (11, None), + ValueType::Long => (5, Some(SQLTypeAffinity::Integer)), + ValueType::Double => (5, Some(SQLTypeAffinity::Real)), + ValueType::String => (10, None), + ValueType::Uuid => (11, None), ValueType::Keyword => (13, None), } } @@ -71,13 +64,13 @@ impl SQLValueType for ValueType { fn accommodates_integer(&self, int: i64) -> bool { use ValueType::*; match *self { - Instant => false, // Always use #inst. - Long | Double => true, - Ref => int >= 0, - Boolean => (int == 0) || (int == 1), - ValueType::String => false, - Keyword => false, - Uuid => false, + Instant => false, // Always use #inst. + Long | Double => true, + Ref => int >= 0, + Boolean => (int == 0) || (int == 1), + ValueType::String => false, + Keyword => false, + Uuid => false, } } } @@ -130,12 +123,8 @@ impl SQLValueTypeSet for ValueTypeSet { #[cfg(test)] mod tests { - use core_traits::{ - ValueType, - }; - use sql_types::{ - SQLValueType, - }; + use core_traits::ValueType; + use sql_types::SQLValueType; #[test] fn test_accommodates_integer() { diff --git a/core/src/tx_report.rs b/core/src/tx_report.rs index b88f0f92..e4555bc5 100644 --- a/core/src/tx_report.rs +++ b/core/src/tx_report.rs @@ -10,18 +10,11 @@ #![allow(dead_code)] -use std::collections::{ - BTreeMap, -}; +use std::collections::BTreeMap; -use core_traits::{ - Entid, -}; +use core_traits::Entid; -use ::{ - DateTime, - Utc, -}; +use {DateTime, Utc}; /// A transaction report summarizes an applied transaction. #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] diff --git a/core/src/util.rs b/core/src/util.rs index 77d02735..dbaed83d 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -67,7 +67,8 @@ pub enum Either { // Cribbed from https://github.com/bluss/either/blob/f793721f3fdeb694f009e731b23a2858286bc0d6/src/lib.rs#L219-L259. impl Either { pub fn map_left(self, f: F) -> Either - where F: FnOnce(L) -> M + where + F: FnOnce(L) -> M, { use self::Either::*; match self { @@ -77,7 +78,8 @@ impl Either { } pub fn map_right(self, f: F) -> Either - where F: FnOnce(R) -> S + where + F: FnOnce(R) -> S, { use self::Either::*; match self { diff --git a/db-traits/Cargo.toml b/db-traits/Cargo.toml index 9403189a..ceac5536 100644 --- a/db-traits/Cargo.toml +++ b/db-traits/Cargo.toml @@ -21,5 +21,5 @@ path = "../edn" path = "../core-traits" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] diff --git a/db-traits/errors.rs b/db-traits/errors.rs index 58b39aa8..a251a693 100644 --- a/db-traits/errors.rs +++ b/db-traits/errors.rs @@ -10,29 +10,15 @@ #![allow(dead_code)] -use failure::{ - Backtrace, - Context, - Fail, -}; +use failure::{Backtrace, Context, Fail}; -use std::collections::{ - BTreeMap, - BTreeSet, -}; +use std::collections::{BTreeMap, BTreeSet}; use rusqlite; -use edn::entities::{ - TempId, -}; +use edn::entities::TempId; -use core_traits::{ - Entid, - KnownEntid, - TypedValue, - ValueType, -}; +use core_traits::{Entid, KnownEntid, TypedValue, ValueType}; pub type Result = ::std::result::Result; @@ -72,40 +58,46 @@ pub enum SchemaConstraintViolation { /// A transaction tried to assert a datom or datoms with the wrong value `v` type(s). TypeDisagreements { /// 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. - CardinalityConflicts { - conflicts: Vec, - }, + CardinalityConflicts { conflicts: Vec }, } impl ::std::fmt::Display for SchemaConstraintViolation { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { use self::SchemaConstraintViolation::*; match self { - &ConflictingUpserts { ref conflicting_upserts } => { + &ConflictingUpserts { + ref conflicting_upserts, + } => { writeln!(f, "conflicting upserts:")?; for (tempid, entids) in conflicting_upserts { writeln!(f, " tempid {:?} upserts to {:?}", tempid, entids)?; } Ok(()) - }, - &TypeDisagreements { ref conflicting_datoms } => { + } + &TypeDisagreements { + ref conflicting_datoms, + } => { writeln!(f, "type disagreements:")?; 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(()) - }, + } &CardinalityConflicts { ref conflicts } => { writeln!(f, "cardinality conflicts:")?; for ref conflict in conflicts { writeln!(f, " {:?}", conflict)?; } Ok(()) - }, + } } } } @@ -146,7 +138,7 @@ impl ::std::fmt::Display for DbError { } impl Fail for DbError { - fn cause(&self) -> Option<&Fail> { + fn cause(&self) -> Option<&dyn Fail> { self.inner.cause() } @@ -162,20 +154,24 @@ impl DbError { } impl From for DbError { - fn from(kind: DbErrorKind) -> DbError { - DbError { inner: Context::new(kind) } + fn from(kind: DbErrorKind) -> Self { + DbError { + inner: Context::new(kind), + } } } impl From> for DbError { - fn from(inner: Context) -> DbError { + fn from(inner: Context) -> Self { DbError { inner: inner } } } impl From for DbError { - fn from(error: rusqlite::Error) -> DbError { - DbError { inner: Context::new(DbErrorKind::RusqliteError(error.to_string())) } + fn from(error: rusqlite::Error) -> Self { + DbError { + inner: Context::new(DbErrorKind::RusqliteError(error.to_string())), + } } } @@ -187,7 +183,10 @@ pub enum DbErrorKind { NotYetImplemented(String), /// 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), /// 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)] BadSQLValuePair(rusqlite::types::Value, i32), - // /// 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 ... - // #[fail(display = "bad SQL store user_version: {}", _0)] - // BadSQLiteStoreVersion(i32), - + /// 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 ... + /// #[fail(display = "bad SQL store user_version: {}", _0)] + /// BadSQLiteStoreVersion(i32), /// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not /// a runtime error. #[fail(display = "bad bootstrap definition: {}", _0)] @@ -239,7 +237,9 @@ pub enum DbErrorKind { #[fail(display = "transaction input error: {}", _0)] 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, // SQL errors. diff --git a/db-traits/lib.rs b/db-traits/lib.rs index f2e5acc0..b99d54d1 100644 --- a/db-traits/lib.rs +++ b/db-traits/lib.rs @@ -8,12 +8,11 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. - extern crate failure; extern crate failure_derive; extern crate rusqlite; -extern crate edn; extern crate core_traits; +extern crate edn; pub mod errors; diff --git a/db/Cargo.toml b/db/Cargo.toml index 3ff96e87..ceeaa314 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -9,20 +9,20 @@ sqlcipher = ["rusqlite/sqlcipher"] syncable = ["serde", "serde_json", "serde_derive"] [dependencies] -failure = "0.1.1" +failure = "0.1.6" indexmap = "1" -itertools = "0.7" -lazy_static = "0.2" +itertools = "0.8" +lazy_static = "1.4.0" log = "0.4" -ordered-float = "0.5" -time = "0.1" -petgraph = "0.4.12" +ordered-float = "1.0.2" +time = "0.2" +petgraph = "0.5" serde = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.edn] @@ -42,7 +42,7 @@ path = "../sql" # Should be dev-dependencies. [dependencies.tabwriter] -version = "1.0.3" +version = "1.2.1" [dev-dependencies] -env_logger = "0.5" +env_logger = "0.7" diff --git a/db/src/add_retract_alter_set.rs b/db/src/add_retract_alter_set.rs index faeb8f02..88747fba 100644 --- a/db/src/add_retract_alter_set.rs +++ b/db/src/add_retract_alter_set.rs @@ -24,7 +24,10 @@ pub struct AddRetractAlterSet { pub altered: BTreeMap, } -impl Default for AddRetractAlterSet where K: Ord { +impl Default for AddRetractAlterSet +where + K: Ord, +{ fn default() -> AddRetractAlterSet { AddRetractAlterSet { asserted: BTreeMap::default(), @@ -34,7 +37,10 @@ impl Default for AddRetractAlterSet where K: Ord { } } -impl AddRetractAlterSet where K: Ord { +impl AddRetractAlterSet +where + K: Ord, +{ pub fn witness(&mut self, key: K, value: V, added: bool) { if added { if let Some(retracted_value) = self.retracted.remove(&key) { diff --git a/db/src/bootstrap.rs b/db/src/bootstrap.rs index d51ed16d..810b6905 100644 --- a/db/src/bootstrap.rs +++ b/db/src/bootstrap.rs @@ -10,26 +10,17 @@ #![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_traits::errors::{DbErrorKind, Result}; +use edn; use edn::entities::Entity; +use edn::symbols; +use edn::types::Value; +use entids; -use core_traits::{ - TypedValue, - values, -}; +use core_traits::{values, TypedValue}; -use mentat_core::{ - IdentMap, - Schema, -}; +use mentat_core::{IdentMap, Schema}; use schema::SchemaBuilding; use types::{Partition, PartitionMap}; @@ -46,76 +37,118 @@ pub const CORE_SCHEMA_VERSION: u32 = 1; lazy_static! { 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", "txInstant"), entids::DB_TX_INSTANT), - (ns_keyword!("db.install", "partition"), entids::DB_INSTALL_PARTITION), - (ns_keyword!("db.install", "valueType"), entids::DB_INSTALL_VALUE_TYPE), - (ns_keyword!("db.install", "attribute"), entids::DB_INSTALL_ATTRIBUTE), - (ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE), - (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", "index"), entids::DB_INDEX), - (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", "retract"), entids::DB_RETRACT), - (ns_keyword!("db.part", "user"), entids::DB_PART_USER), - (ns_keyword!("db.part", "tx"), entids::DB_PART_TX), - (ns_keyword!("db", "excise"), entids::DB_EXCISE), - (ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS), - (ns_keyword!("db.excise", "beforeT"), entids::DB_EXCISE_BEFORE_T), - (ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE), - (ns_keyword!("db.alter", "attribute"), entids::DB_ALTER_ATTRIBUTE), - (ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF), - (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), + [ + (ns_keyword!("db", "ident"), entids::DB_IDENT), + (ns_keyword!("db.part", "db"), entids::DB_PART_DB), + (ns_keyword!("db", "txInstant"), entids::DB_TX_INSTANT), + ( + ns_keyword!("db.install", "partition"), + entids::DB_INSTALL_PARTITION, + ), + ( + ns_keyword!("db.install", "valueType"), + entids::DB_INSTALL_VALUE_TYPE, + ), + ( + ns_keyword!("db.install", "attribute"), + entids::DB_INSTALL_ATTRIBUTE, + ), + (ns_keyword!("db", "valueType"), entids::DB_VALUE_TYPE), + (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", "index"), entids::DB_INDEX), + (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", "retract"), entids::DB_RETRACT), + (ns_keyword!("db.part", "user"), entids::DB_PART_USER), + (ns_keyword!("db.part", "tx"), entids::DB_PART_TX), + (ns_keyword!("db", "excise"), entids::DB_EXCISE), + (ns_keyword!("db.excise", "attrs"), entids::DB_EXCISE_ATTRS), + ( + ns_keyword!("db.excise", "beforeT"), + entids::DB_EXCISE_BEFORE_T, + ), + (ns_keyword!("db.excise", "before"), entids::DB_EXCISE_BEFORE), + ( + ns_keyword!("db.alter", "attribute"), + entids::DB_ALTER_ATTRIBUTE, + ), + (ns_keyword!("db.type", "ref"), entids::DB_TYPE_REF), + (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] = { - [(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] = { - [(ns_keyword!("db", "ident")), - (ns_keyword!("db.install", "partition")), - (ns_keyword!("db.install", "valueType")), - (ns_keyword!("db.install", "attribute")), - (ns_keyword!("db", "txInstant")), - (ns_keyword!("db", "valueType")), - (ns_keyword!("db", "cardinality")), - (ns_keyword!("db", "doc")), - (ns_keyword!("db", "unique")), - (ns_keyword!("db", "isComponent")), - (ns_keyword!("db", "index")), - (ns_keyword!("db", "fulltext")), - (ns_keyword!("db", "noHistory")), - (ns_keyword!("db.alter", "attribute")), - (ns_keyword!("db.schema", "version")), - (ns_keyword!("db.schema", "attribute")), + static ref V1_CORE_SCHEMA: [symbols::Keyword; 16] = { + [ + (ns_keyword!("db", "ident")), + (ns_keyword!("db.install", "partition")), + (ns_keyword!("db.install", "valueType")), + (ns_keyword!("db.install", "attribute")), + (ns_keyword!("db", "txInstant")), + (ns_keyword!("db", "valueType")), + (ns_keyword!("db", "cardinality")), + (ns_keyword!("db", "doc")), + (ns_keyword!("db", "unique")), + (ns_keyword!("db", "isComponent")), + (ns_keyword!("db", "index")), + (ns_keyword!("db", "fulltext")), + (ns_keyword!("db", "noHistory")), + (ns_keyword!("db.alter", "attribute")), + (ns_keyword!("db.schema", "version")), + (ns_keyword!("db.schema", "attribute")), ] }; - static ref V1_SYMBOLIC_SCHEMA: Value = { let s = r#" {:db/ident {:db/valueType :db.type/keyword @@ -163,7 +196,9 @@ lazy_static! { :db/cardinality :db.cardinality/many}}"#; edn::parse::value(s) .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() }; } @@ -174,7 +209,12 @@ fn idents_to_assertions(idents: &[(symbols::Keyword, i64)]) -> Vec { .into_iter() .map(|&(ref ident, _)| { 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() } @@ -188,15 +228,19 @@ fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec< .into_iter() .map(|ident| { let value = Value::Keyword(ident.clone()); - Value::Vector(vec![values::DB_ADD.clone(), - schema_core.clone(), - schema_attr.clone(), - value]) + Value::Vector(vec![ + values::DB_ADD.clone(), + schema_core.clone(), + schema_attr.clone(), + value, + ]) }) - .chain(::std::iter::once(Value::Vector(vec![values::DB_ADD.clone(), - schema_core.clone(), - schema_version, - Value::Integer(version as i64)]))) + .chain(::std::iter::once(Value::Vector(vec![ + values::DB_ADD.clone(), + schema_core.clone(), + schema_version, + Value::Integer(version as i64), + ]))) .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 /// assertions. -fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) -> Result> { +fn symbolic_schema_to_triples( + ident_map: &IdentMap, + symbolic_schema: &Value, +) -> Result> { // Failure here is a coding error, not a runtime error. let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![]; // 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 { let ident = match 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 { Value::Map(ref mpp) => { for (attr, value) in mpp { let attr = match 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 // 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 // schema directly as Rust data. let typed_value = match TypedValue::from_edn_value(value) { - Some(TypedValue::Keyword(ref k)) => { - ident_map.get(k) - .map(|entid| TypedValue::Ref(*entid)) - .ok_or(DbErrorKind::UnrecognizedIdent(k.to_string()))? - }, + Some(TypedValue::Keyword(ref k)) => ident_map + .get(k) + .map(|entid| TypedValue::Ref(*entid)) + .ok_or(DbErrorKind::UnrecognizedIdent(k.to_string()))?, 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)); } - }, - _ => 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) } @@ -264,48 +321,64 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result> match *mp { Value::Map(ref mpp) => { for (attr, value) in mpp { - assertions.push(Value::Vector(vec![values::DB_ADD.clone(), - ident.clone(), - attr.clone(), - value.clone()])); + assertions.push(Value::Vector(vec![ + values::DB_ADD.clone(), + ident.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) } pub(crate) fn bootstrap_partition_map() -> PartitionMap { - V1_PARTS.iter() - .map(|&(ref part, start, end, index, allow_excision)| (part.to_string(), Partition::new(start, end, index, allow_excision))) - .collect() + V1_PARTS + .iter() + .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 { - V1_IDENTS.iter() - .map(|&(ref ident, entid)| (ident.clone(), entid)) - .collect() + V1_IDENTS + .iter() + .map(|&(ref ident, entid)| (ident.clone(), entid)) + .collect() } pub(crate) fn bootstrap_schema() -> Schema { 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() } pub(crate) fn bootstrap_entities() -> Vec> { - let bootstrap_assertions: Value = Value::Vector([ - symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"), - idents_to_assertions(&V1_IDENTS[..]), - schema_attrs_to_assertions(CORE_SCHEMA_VERSION, V1_CORE_SCHEMA.as_ref()), - ].concat()); + let bootstrap_assertions: Value = Value::Vector( + [ + symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"), + idents_to_assertions(&V1_IDENTS[..]), + 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. // TODO: represent these bootstrap data errors rather than just panicing. - let bootstrap_entities: Vec> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions"); + let bootstrap_entities: Vec> = + edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions"); return bootstrap_entities; } diff --git a/db/src/cache.rs b/db/src/cache.rs index 5ccc28d3..d05d4377 100644 --- a/db/src/cache.rs +++ b/db/src/cache.rs @@ -1059,7 +1059,7 @@ impl AttributeCaches { "SELECT a, e, v, value_type_tag FROM {} WHERE a = ? ORDER BY a ASC, e ASC", table ); - let args: Vec<&rusqlite::types::ToSql> = vec![&attribute]; + let args: Vec<&dyn rusqlite::types::ToSql> = vec![&attribute]; let mut stmt = sqlite .prepare(&sql) .context(DbErrorKind::CacheUpdateFailed)?; @@ -1071,7 +1071,7 @@ impl AttributeCaches { &'a mut self, schema: &'s Schema, statement: &'c mut rusqlite::Statement, - args: Vec<&'v rusqlite::types::ToSql>, + args: Vec<&'v dyn rusqlite::types::ToSql>, replacing: bool, ) -> Result<()> { let mut aev_factory = AevFactory::new(); @@ -1208,15 +1208,15 @@ impl AttributeCaches { &'a self, schema: &'s Schema, a: Entid, - ) -> Option<&'a AttributeCache> { + ) -> Option<&'a dyn AttributeCache> { if !self.forward_cached_attributes.contains(&a) { return None; } schema.attribute_for_entid(a).and_then(|attr| { if attr.multival { - self.multi_vals.get(&a).map(|v| v as &AttributeCache) + self.multi_vals.get(&a).map(|v| v as &dyn AttributeCache) } else { - self.single_vals.get(&a).map(|v| v as &AttributeCache) + self.single_vals.get(&a).map(|v| v as &dyn AttributeCache) } }) } diff --git a/db/src/db.rs b/db/src/db.rs index 4aacc55e..9c9b7ae6 100644 --- a/db/src/db.rs +++ b/db/src/db.rs @@ -43,6 +43,7 @@ use schema::SchemaBuilding; use tx::transact; use types::{AVMap, AVPair, Partition, PartitionMap, DB}; +use std::convert::TryInto; use watcher::NullWatcher; // In PRAGMA foo='bar', `'bar'` must be a constant string (it cannot be a @@ -256,8 +257,11 @@ lazy_static! { /// Mentat manages its own SQL schema version using the user version. See the [SQLite /// documentation](https://www.sqlite.org/pragma.html#pragma_user_version). fn set_user_version(conn: &rusqlite::Connection, version: i32) -> Result<()> { - conn.execute(&format!("PRAGMA user_version = {}", version), rusqlite::params![]) - .context(DbErrorKind::CouldNotSetVersionPragma)?; + conn.execute( + &format!("PRAGMA user_version = {}", version), + rusqlite::params![], + ) + .context(DbErrorKind::CouldNotSetVersionPragma)?; Ok(()) } @@ -418,15 +422,15 @@ impl TypedSQLValue for TypedValue { (5, rusqlite::types::Value::Real(x)) => Ok(TypedValue::Double(x.into())), (10, rusqlite::types::Value::Text(x)) => Ok(x.into()), (11, rusqlite::types::Value::Blob(x)) => { - let u = Uuid::from_bytes(x.as_slice()); - if u.is_err() { + let u = Uuid::from_bytes(x.as_slice().try_into().unwrap()); + if u.is_nil() { // Rather than exposing Uuid's ParseError… bail!(DbErrorKind::BadSQLValuePair( rusqlite::types::Value::Blob(x), value_type_tag )); } - Ok(TypedValue::Uuid(u.unwrap())) + Ok(TypedValue::Uuid(u)) } (13, rusqlite::types::Value::Text(x)) => to_namespaced_keyword(&x).map(|k| k.into()), (_, value) => bail!(DbErrorKind::BadSQLValuePair(value, value_type_tag)), @@ -491,7 +495,9 @@ pub(crate) fn read_materialized_view( ) -> Result> { let mut stmt: rusqlite::Statement = conn.prepare(format!("SELECT e, a, v, value_type_tag FROM {}", table).as_str())?; - let m: Result> = stmt.query_and_then(rusqlite::params![], row_to_datom_assertion)?.collect(); + let m: Result> = stmt + .query_and_then(rusqlite::params![], row_to_datom_assertion)? + .collect(); m } @@ -660,7 +666,8 @@ fn search(conn: &rusqlite::Connection) -> Result<()> { t.a0 = d.a"#; let mut stmt = conn.prepare_cached(s)?; - stmt.execute(rusqlite::params![]).context(DbErrorKind::CouldNotSearch)?; + stmt.execute(rusqlite::params![]) + .context(DbErrorKind::CouldNotSearch)?; Ok(()) } @@ -727,7 +734,8 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> { // datom twice in datoms. The transactor unifies repeated datoms, and in addition we add // indices to the search inputs and search results to ensure that we don't see repeated datoms // at this point. - let s = format!(r#" + let s = format!( + r#" INSERT INTO datoms (e, a, v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value) SELECT e0, a0, v0, ?, value_type_tag0, flags0 & {} IS NOT 0, @@ -736,10 +744,11 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> { flags0 & {} IS NOT 0 FROM temp.search_results WHERE added0 IS 1 AND ((rid IS NULL) OR ((rid IS NOT NULL) AND (v0 IS NOT v)))"#, - AttributeBitFlags::IndexAVET as u8, - AttributeBitFlags::IndexVAET as u8, - AttributeBitFlags::IndexFulltext as u8, - AttributeBitFlags::UniqueValue as u8); + AttributeBitFlags::IndexAVET as u8, + AttributeBitFlags::IndexVAET as u8, + AttributeBitFlags::IndexFulltext as u8, + AttributeBitFlags::UniqueValue as u8 + ); let mut stmt = conn.prepare_cached(&s)?; stmt.execute(&[&tx]) @@ -775,12 +784,12 @@ impl MentatStoring for rusqlite::Connection { }).collect(); // `params` reference computed values in `block`. - let params: Vec<&ToSql> = block.iter().flat_map(|&(ref searchid, ref a, ref value, ref value_type_tag)| { + let params: Vec<&dyn ToSql> = block.iter().flat_map(|&(ref searchid, ref a, ref value, ref value_type_tag)| { // Avoid inner heap allocation. - once(searchid as &ToSql) - .chain(once(a as &ToSql) - .chain(once(value as &ToSql) - .chain(once(value_type_tag as &ToSql)))) + once(searchid as &dyn ToSql) + .chain(once(a as &dyn ToSql) + .chain(once(value as &dyn ToSql) + .chain(once(value_type_tag as &dyn ToSql)))) }).collect(); // TODO: cache these statements for selected values of `count`. @@ -843,7 +852,6 @@ impl MentatStoring for rusqlite::Connection { value_type_tag0 SMALLINT NOT NULL, added0 TINYINT NOT NULL, flags0 TINYINT NOT NULL)"#, - // It is fine to transact the same [e a v] twice in one transaction, but the transaction // processor should unify such repeated datoms. This index will cause insertion to fail // if the transaction processor incorrectly tries to assert the same (cardinality one) @@ -919,15 +927,15 @@ impl MentatStoring for rusqlite::Connection { let block = block?; // `params` reference computed values in `block`. - let params: Vec<&ToSql> = block.iter().flat_map(|&(ref e, ref a, ref value, ref value_type_tag, added, ref flags)| { + let params: Vec<&dyn ToSql> = block.iter().flat_map(|&(ref e, ref a, ref value, ref value_type_tag, added, ref flags)| { // Avoid inner heap allocation. // TODO: extract some finite length iterator to make this less indented! - once(e as &ToSql) - .chain(once(a as &ToSql) - .chain(once(value as &ToSql) - .chain(once(value_type_tag as &ToSql) - .chain(once(to_bool_ref(added) as &ToSql) - .chain(once(flags as &ToSql)))))) + once(e as &dyn ToSql) + .chain(once(a as &dyn ToSql) + .chain(once(value as &dyn ToSql) + .chain(once(value_type_tag as &dyn ToSql) + .chain(once(to_bool_ref(added) as &dyn ToSql) + .chain(once(flags as &dyn ToSql)))))) }).collect(); // TODO: cache this for selected values of count. @@ -1019,15 +1027,15 @@ impl MentatStoring for rusqlite::Connection { // First, insert all fulltext string values. // `fts_params` reference computed values in `block`. - let fts_params: Vec<&ToSql> = + let fts_params: Vec<&dyn ToSql> = block.iter() .filter(|&&(ref _e, ref _a, ref value, ref _value_type_tag, _added, ref _flags, ref _searchid)| { value.is_some() }) .flat_map(|&(ref _e, ref _a, ref value, ref _value_type_tag, _added, ref _flags, ref searchid)| { // Avoid inner heap allocation. - once(value as &ToSql) - .chain(once(searchid as &ToSql)) + once(value as &dyn ToSql) + .chain(once(searchid as &dyn ToSql)) }).collect(); // TODO: make this maximally efficient. It's not terribly inefficient right now. @@ -1040,15 +1048,15 @@ impl MentatStoring for rusqlite::Connection { // Second, insert searches. // `params` reference computed values in `block`. - let params: Vec<&ToSql> = block.iter().flat_map(|&(ref e, ref a, ref _value, ref value_type_tag, added, ref flags, ref searchid)| { + let params: Vec<&dyn ToSql> = block.iter().flat_map(|&(ref e, ref a, ref _value, ref value_type_tag, added, ref flags, ref searchid)| { // Avoid inner heap allocation. // TODO: extract some finite length iterator to make this less indented! - once(e as &ToSql) - .chain(once(a as &ToSql) - .chain(once(searchid as &ToSql) - .chain(once(value_type_tag as &ToSql) - .chain(once(to_bool_ref(added) as &ToSql) - .chain(once(flags as &ToSql)))))) + once(e as &dyn ToSql) + .chain(once(a as &dyn ToSql) + .chain(once(searchid as &dyn ToSql) + .chain(once(value_type_tag as &dyn ToSql) + .chain(once(to_bool_ref(added) as &dyn ToSql) + .chain(once(flags as &dyn ToSql)))))) }).collect(); // TODO: cache this for selected values of count. @@ -1136,7 +1144,7 @@ pub fn committed_metadata_assertions( let mut stmt = conn.prepare_cached(&sql_stmt)?; let m: Result> = stmt - .query_and_then(&[&tx_id as &ToSql], row_to_transaction_assertion)? + .query_and_then(&[&tx_id as &dyn ToSql], row_to_transaction_assertion)? .collect(); m } @@ -1236,13 +1244,16 @@ SELECT EXISTS match alteration { &Index => { // This should always succeed. - index_stmt.execute(&[&attribute.index, &entid as &ToSql])?; + index_stmt.execute(&[&attribute.index, &entid as &dyn ToSql])?; } &Unique => { // TODO: This can fail if there are conflicting values; give a more helpful // error message in this case. if unique_value_stmt - .execute(&[to_bool_ref(attribute.unique.is_some()), &entid as &ToSql]) + .execute(&[ + to_bool_ref(attribute.unique.is_some()), + &entid as &dyn ToSql, + ]) .is_err() { match attribute.unique { @@ -1269,7 +1280,7 @@ SELECT EXISTS // TODO: improve the failure message. Perhaps try to mimic what Datomic says in // this case? if !attribute.multival { - let mut rows = cardinality_stmt.query(&[&entid as &ToSql])?; + let mut rows = cardinality_stmt.query(&[&entid as &dyn ToSql])?; if rows.next()?.is_some() { bail!(DbErrorKind::SchemaAlterationFailed(format!( "Cannot alter schema attribute {} to be :db.cardinality/one", @@ -3041,11 +3052,14 @@ mod tests { fn test_conflicting_upserts() { let mut conn = TestConn::default(); - assert_transact!(conn, r#"[ + assert_transact!( + conn, + r#"[ {:db/ident :page/id :db/valueType :db.type/string :db/index true :db/unique :db.unique/identity} {:db/ident :page/ref :db/valueType :db.type/ref :db/index true :db/unique :db.unique/identity} {:db/ident :page/title :db/valueType :db.type/string :db/cardinality :db.cardinality/many} - ]"#); + ]"# + ); // Let's test some conflicting upserts. First, valid data to work with -- note self references. assert_transact!( @@ -3101,11 +3115,14 @@ mod tests { fn test_upsert_issue_532() { let mut conn = TestConn::default(); - assert_transact!(conn, r#"[ + assert_transact!( + conn, + r#"[ {:db/ident :page/id :db/valueType :db.type/string :db/index true :db/unique :db.unique/identity} {:db/ident :page/ref :db/valueType :db.type/ref :db/index true :db/unique :db.unique/identity} {:db/ident :page/title :db/valueType :db.type/string :db/cardinality :db.cardinality/many} - ]"#); + ]"# + ); // Observe that "foo" and "zot" upsert to the same entid, and that doesn't cause a // cardinality conflict, because we treat the input with set semantics and accept @@ -3257,10 +3274,13 @@ mod tests { fn test_cardinality_constraints() { let mut conn = TestConn::default(); - assert_transact!(conn, r#"[ + assert_transact!( + conn, + r#"[ {:db/id 200 :db/ident :test/one :db/valueType :db.type/long :db/cardinality :db.cardinality/one} {:db/id 201 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many} - ]"#); + ]"# + ); // Can add the same datom multiple times for an attribute, regardless of cardinality. assert_transact!( @@ -3317,9 +3337,11 @@ mod tests { let sqlite = new_connection_with_key("../fixtures/v1encrypted.db", secret_key) .expect("Failed to find test DB"); sqlite - .query_row("SELECT COUNT(*) FROM sqlite_master", rusqlite::params![], |row| { - row.get::<_, i64>(0) - }) + .query_row( + "SELECT COUNT(*) FROM sqlite_master", + rusqlite::params![], + |row| row.get::<_, i64>(0), + ) .expect("Failed to execute sql query on encrypted DB"); } diff --git a/db/src/debug.rs b/db/src/debug.rs index 91d8905d..f0acd881 100644 --- a/db/src/debug.rs +++ b/db/src/debug.rs @@ -25,11 +25,13 @@ macro_rules! assert_matches { .expect(format!("to be able to parse expected {}", $expected).as_str()) .without_spans(); let input_value = $input.to_edn(); - assert!(input_value.matches(&pattern_value), - "Expected value:\n{}\nto match pattern:\n{}\n", - input_value.to_pretty(120).unwrap(), - pattern_value.to_pretty(120).unwrap()); - }} + assert!( + input_value.matches(&pattern_value), + "Expected value:\n{}\nto match pattern:\n{}\n", + input_value.to_pretty(120).unwrap(), + pattern_value.to_pretty(120).unwrap() + ); + }}; } // Transact $input against the given $conn, expecting success or a `Result`. @@ -45,61 +47,45 @@ macro_rules! assert_transact { ( $conn: expr, $input: expr ) => {{ trace!("assert_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() }}; } use std::borrow::Borrow; use std::collections::BTreeMap; -use std::io::{Write}; +use std::io::Write; use itertools::Itertools; use rusqlite; -use rusqlite::{TransactionBehavior}; -use rusqlite::types::{ToSql}; +use rusqlite::types::ToSql; +use rusqlite::TransactionBehavior; use tabwriter::TabWriter; use bootstrap; 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 entids; -use db_traits::errors::Result; -use core_traits::{ - Entid, - TypedValue, - ValueType, -}; +use core_traits::{Entid, TypedValue, ValueType}; -use mentat_core::{ - HasSchema, - SQLValueType, - TxReport, -}; -use edn::{ - InternSet, -}; -use edn::entities::{ - EntidOrIdent, - TempId, -}; -use internal_types::{ - TermWithTempIds, -}; -use schema::{ - SchemaBuilding, -}; +use edn::entities::{EntidOrIdent, TempId}; +use edn::InternSet; +use internal_types::TermWithTempIds; +use mentat_core::{HasSchema, SQLValueType, TxReport}; +use schema::SchemaBuilding; +use tx::{transact, transact_terms}; use types::*; -use tx::{ - transact, - transact_terms, -}; use watcher::NullWatcher; /// 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 { // TODO: generalize this. pub e: EntidOrIdent, @@ -160,19 +146,30 @@ impl Transactions { impl FulltextValues { 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. trait ToIdent { - fn map_ident(self, schema: &Schema) -> Self; + fn map_ident(self, schema: &Schema) -> Self; } impl ToIdent for TypedValue { fn map_ident(self, schema: &Schema) -> 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 { self } @@ -181,7 +178,11 @@ impl ToIdent for TypedValue { /// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`. 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`. @@ -199,38 +200,49 @@ pub fn datoms>(conn: &rusqlite::Connection, schema: &S) -> Res /// ordered by (e, a, v, tx). /// /// The datom set returned does not include any datoms of the form [... :db/txInstant ...]. -pub fn datoms_after>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result { +pub fn datoms_after>( + conn: &rusqlite::Connection, + schema: &S, + tx: i64, +) -> Result { 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 r: Result> = stmt.query_and_then(&[&tx], |row| { - let e: i64 = row.get(0)?; - let a: i64 = row.get(1)?; + let r: Result> = stmt + .query_and_then(&[&tx], |row| { + let e: i64 = row.get(0)?; + let a: i64 = row.get(1)?; - if a == entids::DB_TX_INSTANT { - return Ok(None); - } + if a == entids::DB_TX_INSTANT { + return Ok(None); + } - let v: rusqlite::types::Value = row.get(2)?; - let value_type_tag: i32 = row.get(3)?; + let v: rusqlite::types::Value = row.get(2)?; + let value_type_tag: i32 = row.get(3)?; - 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 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 typed_value = TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema); - let (value, _) = typed_value.to_edn_value_pair(); + let typed_value = + 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(4)?; + let tx: i64 = row.get(4)?; - Ok(Some(Datom { - e: EntidOrIdent::Entid(e), - a: to_entid(borrowed_schema, a), - v: value, - tx: tx, - added: None, - })) - })?.collect(); + Ok(Some(Datom { + e: EntidOrIdent::Entid(e), + a: to_entid(borrowed_schema, a), + v: value, + tx: tx, + added: None, + })) + })? + .collect(); Ok(Datoms(r?.into_iter().filter_map(|x| x).collect())) } @@ -239,51 +251,70 @@ pub fn datoms_after>(conn: &rusqlite::Connection, schema: &S, /// given `tx`, ordered by (tx, e, a, v). /// /// Each transaction returned includes the [(transaction-tx) :db/txInstant ...] datom. -pub fn transactions_after>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result { +pub fn transactions_after>( + conn: &rusqlite::Connection, + schema: &S, + tx: i64, +) -> Result { 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 r: Result> = stmt.query_and_then(&[&tx], |row| { - let e: i64 = row.get(0)?; - let a: i64 = row.get(1)?; + let r: Result> = stmt + .query_and_then(&[&tx], |row| { + let e: i64 = row.get(0)?; + let a: i64 = row.get(1)?; - let v: rusqlite::types::Value = row.get(2)?; - let value_type_tag: i32 = row.get(3)?; + let v: rusqlite::types::Value = row.get(2)?; + let value_type_tag: i32 = row.get(3)?; - 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 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 typed_value = TypedValue::from_sql_value_pair(v, value_type_tag)?.map_ident(borrowed_schema); - let (value, _) = typed_value.to_edn_value_pair(); + let typed_value = + 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(4)?; - let added: bool = row.get(5)?; + let tx: i64 = row.get(4)?; + let added: bool = row.get(5)?; - Ok(Datom { - e: EntidOrIdent::Entid(e), - a: to_entid(borrowed_schema, a), - v: value, - tx: tx, - added: Some(added), - }) - })?.collect(); + Ok(Datom { + e: EntidOrIdent::Entid(e), + a: to_entid(borrowed_schema, a), + v: value, + tx: tx, + added: Some(added), + }) + })? + .collect(); // Group by tx. - let r: Vec = r?.into_iter().group_by(|x| x.tx).into_iter().map(|(_key, group)| Datoms(group.collect())).collect(); + let r: Vec = r? + .into_iter() + .group_by(|x| x.tx) + .into_iter() + .map(|(_key, group)| Datoms(group.collect())) + .collect(); Ok(Transactions(r)) } /// Return the set of fulltext values in the store, ordered by rowid. pub fn fulltext_values(conn: &rusqlite::Connection) -> Result { - 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> = stmt.query_and_then(params, |row| { - let rowid: i64 = row.get(0)?; - let text: String = row.get(1)?; - Ok((rowid, text)) - })?.collect(); + let r: Result> = stmt + .query_and_then(params, |row| { + let rowid: i64 = row.get(0)?; + let text: String = row.get(1)?; + Ok((rowid, text)) + })? + .collect(); r.map(FulltextValues) } @@ -293,7 +324,11 @@ pub fn fulltext_values(conn: &rusqlite::Connection) -> Result { /// /// 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. -pub fn dump_sql_query(conn: &rusqlite::Connection, sql: &str, params: &[&ToSql]) -> Result { +pub fn dump_sql_query( + conn: &rusqlite::Connection, + sql: &str, + params: &[&dyn ToSql], +) -> Result { let mut stmt: rusqlite::Statement = conn.prepare(sql)?; let mut tw = TabWriter::new(Vec::new()).padding(2); @@ -304,14 +339,16 @@ pub fn dump_sql_query(conn: &rusqlite::Connection, sql: &str, params: &[&ToSql]) } write!(&mut tw, "\n").unwrap(); - let r: Result> = stmt.query_and_then(params, |row| { - for i in 0..row.column_count() { - let value: rusqlite::types::Value = row.get(i)?; - write!(&mut tw, "{:?}\t", value).unwrap(); - } - write!(&mut tw, "\n").unwrap(); - Ok(()) - })?.collect(); + let r: Result> = stmt + .query_and_then(params, |row| { + for i in 0..row.column_count() { + let value: rusqlite::types::Value = row.get(i)?; + write!(&mut tw, "{:?}\t", value).unwrap(); + } + write!(&mut tw, "\n").unwrap(); + Ok(()) + })? + .collect(); r?; let dump = String::from_utf8(tw.into_inner().unwrap()).unwrap(); @@ -331,20 +368,37 @@ impl TestConn { 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_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); } - pub fn transact(&mut self, transaction: I) -> Result where I: Borrow { + pub fn transact(&mut self, transaction: I) -> Result + where + I: Borrow, + { // 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 = { // The block scopes the borrow of self.sqlite. // 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. - 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()?; details }; @@ -361,13 +415,30 @@ impl TestConn { Ok(report) } - pub fn transact_simple_terms(&mut self, terms: I, tempid_set: InternSet) -> Result where I: IntoIterator { + pub fn transact_simple_terms( + &mut self, + terms: I, + tempid_set: InternSet, + ) -> Result + where + I: IntoIterator, + { let details = { // The block scopes the borrow of self.sqlite. // 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. - 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()?; details }; @@ -385,11 +456,19 @@ impl TestConn { } 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 { - 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 { diff --git a/db/src/entids.rs b/db/src/entids.rs index 05f7d664..a3021fce 100644 --- a/db/src/entids.rs +++ b/db/src/entids.rs @@ -13,10 +13,7 @@ /// Literal `Entid` values in the the "db" namespace. /// /// Used through-out the transactor to match core DB constructs. - -use core_traits::{ - Entid, -}; +use core_traits::Entid; // Added in SQL schema v1. pub const DB_IDENT: Entid = 1; @@ -64,7 +61,7 @@ pub const DB_SCHEMA_CORE: Entid = 40; /// partitions in the partition map. pub fn might_update_metadata(attribute: Entid) -> bool { if attribute >= DB_DOC { - return false + return false; } match attribute { // 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. pub fn is_a_schema_attribute(attribute: Entid) -> bool { match attribute { - DB_IDENT | - DB_CARDINALITY | - DB_FULLTEXT | - DB_INDEX | - DB_IS_COMPONENT | - DB_UNIQUE | - DB_VALUE_TYPE => - true, + DB_IDENT | DB_CARDINALITY | DB_FULLTEXT | DB_INDEX | DB_IS_COMPONENT | DB_UNIQUE + | DB_VALUE_TYPE => true, _ => false, } } diff --git a/db/src/internal_types.rs b/db/src/internal_types.rs index d86ff451..c0714e5f 100644 --- a/db/src/internal_types.rs +++ b/db/src/internal_types.rs @@ -12,50 +12,21 @@ //! Types used only within the transactor. These should not be exposed outside of this crate. -use std::collections::{ - BTreeMap, - BTreeSet, - HashMap, -}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; -use core_traits::{ - Attribute, - Entid, - KnownEntid, - TypedValue, - ValueType, -}; +use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType}; use mentat_core::util::Either; use edn; -use edn::{ - SpannedValue, - ValueAndSpan, - ValueRc, -}; use edn::entities; -use edn::entities::{ - EntityPlace, - OpType, - TempId, - TxFunction, -}; +use edn::entities::{EntityPlace, OpType, TempId, TxFunction}; +use edn::{SpannedValue, ValueAndSpan, ValueRc}; -use db_traits::errors as errors; -use db_traits::errors::{ - DbErrorKind, - Result, -}; -use schema::{ - SchemaTypeChecking, -}; -use types::{ - AVMap, - AVPair, - Schema, - TransactableValue, -}; +use db_traits::errors; +use db_traits::errors::{DbErrorKind, Result}; +use schema::SchemaTypeChecking; +use types::{AVMap, AVPair, Schema, TransactableValue}; impl TransactableValue for ValueAndSpan { fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result { @@ -73,7 +44,7 @@ impl TransactableValue for ValueAndSpan { // We only allow namespaced idents. bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) } - }, + } Text(v) => Ok(EntityPlace::TempId(TempId::External(v).into())), List(ls) => { let mut it = ls.iter(); @@ -81,35 +52,41 @@ impl TransactableValue for ValueAndSpan { // Like "(transaction-id)". (Some(&PlainSymbol(ref op)), None, None, None) => { Ok(EntityPlace::TxFunction(TxFunction { op: op.clone() })) - }, + } // 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()? { - EntityPlace::Entid(a) => Ok(EntityPlace::LookupRef(entities::LookupRef { a: entities::AttributePlace::Entid(a), v: v.clone() })), - EntityPlace::TempId(_) | - EntityPlace::TxFunction(_) | - EntityPlace::LookupRef(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), + EntityPlace::Entid(a) => { + Ok(EntityPlace::LookupRef(entities::LookupRef { + a: entities::AttributePlace::Entid(a), + v: v.clone(), + })) + } + EntityPlace::TempId(_) + | EntityPlace::TxFunction(_) + | EntityPlace::LookupRef(_) => { + bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) + } } - }, + } _ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), } - }, - Nil | - Boolean(_) | - Instant(_) | - BigInteger(_) | - Float(_) | - Uuid(_) | - PlainSymbol(_) | - NamespacedSymbol(_) | - Vector(_) | - Set(_) | - Map(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), + } + Nil | Boolean(_) | Instant(_) | BigInteger(_) | Float(_) | Uuid(_) | PlainSymbol(_) + | NamespacedSymbol(_) | Vector(_) | Set(_) | Map(_) => { + bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) + } } } fn as_tempid(&self) -> Option { - 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> { match self { 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::Boolean(_) | - TypedValue::Long(_) | - TypedValue::Double(_) | - TypedValue::Instant(_) | - TypedValue::Uuid(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), + TypedValue::Boolean(_) + | TypedValue::Long(_) + | TypedValue::Double(_) + | TypedValue::Instant(_) + | TypedValue::Uuid(_) => { + bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) + } } } @@ -160,13 +141,14 @@ pub type LookupRef = ValueRc; /// 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 /// 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 { LookupRef(LookupRef), - TempId(TempIdHandle) + TempId(TempIdHandle), } -pub type TermWithTempIdsAndLookupRefs = Term, TypedValueOr>; +pub type TermWithTempIdsAndLookupRefs = + Term, TypedValueOr>; pub type TermWithTempIds = Term, TypedValueOr>; pub type TermWithoutTempIds = Term; pub type Population = Vec; @@ -186,7 +168,7 @@ impl TermWithTempIds { impl TermWithoutTempIds { pub(crate) fn rewrap(self) -> Term, TypedValueOr> { 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_ /// (`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. -pub fn replace_lookup_ref(lookup_map: &AVMap, desired_or: Either, lift: U) -> errors::Result> where U: FnOnce(Entid) -> T { +pub fn replace_lookup_ref( + lookup_map: &AVMap, + desired_or: Either, + lift: U, +) -> errors::Result> +where + U: FnOnce(Entid) -> T, +{ match desired_or { Left(desired) => Ok(Left(desired)), // N.b., must unwrap here -- the ::Left types are different! Right(other) => { match other { LookupRefOrTempId::TempId(t) => Ok(Right(t)), - LookupRefOrTempId::LookupRef(av) => lookup_map.get(&*av) - .map(|x| lift(*x)).map(Left) + LookupRefOrTempId::LookupRef(av) => lookup_map + .get(&*av) + .map(|x| lift(*x)) + .map(Left) // 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 // checking more efficient. BTree* for deterministic errors. -pub(crate) type AEVTrie<'schema> = BTreeMap<(Entid, &'schema Attribute), BTreeMap>; +pub(crate) type AEVTrie<'schema> = + BTreeMap<(Entid, &'schema Attribute), BTreeMap>; diff --git a/db/src/lib.rs b/db/src/lib.rs index 9c9ac6fa..976d0e8d 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -11,113 +11,87 @@ extern crate failure; extern crate indexmap; extern crate itertools; -#[macro_use] extern crate lazy_static; -#[macro_use] extern crate log; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; #[cfg(feature = "syncable")] -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate serde_derive; extern crate petgraph; extern crate rusqlite; extern crate tabwriter; extern crate time; -#[macro_use] extern crate edn; -#[macro_use] extern crate mentat_core; +#[macro_use] +extern crate edn; +#[macro_use] +extern crate mentat_core; extern crate db_traits; -#[macro_use] extern crate core_traits; +#[macro_use] +extern crate core_traits; extern crate mentat_sql; use std::iter::repeat; use itertools::Itertools; -use db_traits::errors::{ - DbErrorKind, - Result, -}; +use db_traits::errors::{DbErrorKind, Result}; -#[macro_use] pub mod debug; +#[macro_use] +pub mod debug; mod add_retract_alter_set; +mod bootstrap; pub mod cache; pub mod db; -mod bootstrap; 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 schema; -pub mod tx_observer; -mod watcher; pub mod timelines; mod tx; mod tx_checking; +pub mod tx_observer; pub mod types; mod upsert_resolution; +mod watcher; // Export these for reference from sync code and tests. -pub use bootstrap::{ - TX0, - USER0, - V1_PARTS, -}; +pub use bootstrap::{TX0, USER0, V1_PARTS}; pub static TIMELINE_MAIN: i64 = 0; -pub use schema::{ - AttributeBuilder, - AttributeValidation, -}; +pub use schema::{AttributeBuilder, AttributeValidation}; -pub use bootstrap::{ - CORE_SCHEMA_VERSION, -}; +pub use bootstrap::CORE_SCHEMA_VERSION; use edn::symbols; -pub use entids::{ - DB_SCHEMA_CORE, -}; +pub use entids::DB_SCHEMA_CORE; -pub use db::{ - TypedSQLValue, - new_connection, -}; +pub use db::{new_connection, TypedSQLValue}; #[cfg(feature = "sqlcipher")] -pub use db::{ - new_connection_with_key, - change_encryption_key, -}; +pub use db::{change_encryption_key, new_connection_with_key}; -pub use watcher::{ - TransactWatcher, -}; +pub use watcher::TransactWatcher; -pub use tx::{ - transact, - transact_terms, -}; +pub use tx::{transact, transact_terms}; -pub use tx_observer::{ - InProgressObserverTransactWatcher, - TxObservationService, - TxObserver, -}; +pub use tx_observer::{InProgressObserverTransactWatcher, TxObservationService, TxObserver}; -pub use types::{ - AttributeSet, - DB, - Partition, - PartitionMap, - TransactableValue, -}; +pub use types::{AttributeSet, Partition, PartitionMap, TransactableValue, DB}; pub fn to_namespaced_keyword(s: &str) -> Result { let splits = [':', '/']; let mut i = s.split(&splits[..]); 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, }; diff --git a/db/src/metadata.rs b/db/src/metadata.rs index 58f09adc..cb76da0f 100644 --- a/db/src/metadata.rs +++ b/db/src/metadata.rs @@ -26,39 +26,21 @@ use failure::ResultExt; -use std::collections::{BTreeMap, BTreeSet}; use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, BTreeSet}; -use add_retract_alter_set::{ - AddRetractAlterSet, -}; +use add_retract_alter_set::AddRetractAlterSet; +use db_traits::errors::{DbErrorKind, Result}; use edn::symbols; use entids; -use db_traits::errors::{ - DbErrorKind, - Result, -}; -use core_traits::{ - attribute, - Entid, - TypedValue, - ValueType, -}; +use core_traits::{attribute, Entid, TypedValue, ValueType}; -use mentat_core::{ - Schema, - AttributeMap, -}; +use mentat_core::{AttributeMap, Schema}; -use schema::{ - AttributeBuilder, - AttributeValidation, -}; +use schema::{AttributeBuilder, AttributeValidation}; -use types::{ - EAV, -}; +use types::EAV; /// An alteration to an attribute. #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] @@ -100,8 +82,7 @@ pub struct MetadataReport { impl MetadataReport { pub fn attributes_did_change(&self) -> bool { - !(self.attributes_installed.is_empty() && - self.attributes_altered.is_empty()) + !(self.attributes_installed.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 /// /// 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, ident_retractions: &BTreeMap) -> Result> { +fn update_attribute_map_from_schema_retractions( + attribute_map: &mut AttributeMap, + retractions: Vec, + ident_retractions: &BTreeMap, +) -> Result> { // 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. // 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(); // 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. if ident_retractions.contains_key(&e) { // 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. /// /// Returns a report summarizing the mutations that were applied. -pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap, assertions: Vec, retractions: Vec) -> Result { - 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) +pub fn update_attribute_map_from_entid_triples( + attribute_map: &mut AttributeMap, + assertions: Vec, + retractions: Vec, +) -> Result { + 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. @@ -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 // attribute values. That allows us to check existing values and unset them. 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 { // You can only retract :db/unique, :db/isComponent; all others must be altered instead // 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))) } } - }; + } let mut attributes_installed: BTreeSet = BTreeSet::default(); let mut attributes_altered: BTreeMap> = BTreeMap::default(); @@ -312,20 +309,30 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap, match attribute_map.entry(entid) { Entry::Vacant(entry) => { // 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. let a = builder.build(); a.validate(|| entid.to_string())?; entry.insert(a); attributes_installed.insert(entid); - }, + } 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()); 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. /// /// Returns a report summarizing the mutations that were applied. -pub fn update_schema_from_entid_quadruples(schema: &mut Schema, assertions: U) -> Result - where U: IntoIterator { - +pub fn update_schema_from_entid_quadruples( + schema: &mut Schema, + assertions: U, +) -> Result +where + U: IntoIterator, +{ // 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 // retracted at most once), which means all attribute alterations are simple changes from an old // 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 = AddRetractAlterSet::default(); for (e, a, typed_value, added) in assertions.into_iter() { @@ -359,7 +371,7 @@ pub fn update_schema_from_entid_quadruples(schema: &mut Schema, assertions: U if a == entids::DB_IDENT { if let TypedValue::Keyword(ref keyword) = typed_value { ident_set.witness(e, keyword.as_ref().clone(), added); - continue + continue; } else { // Something is terribly wrong: the schema ensures we have a keyword. unreachable!(); @@ -370,20 +382,33 @@ pub fn update_schema_from_entid_quadruples(schema: &mut Schema, assertions: U } // Collect triples. - let retracted_triples = attribute_set.retracted.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)); + let retracted_triples = attribute_set + .retracted + .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. // 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, - retracted_triples.collect(), - &ident_set.retracted)?; + let non_schema_retractions = update_attribute_map_from_schema_retractions( + &mut schema.attribute_map, + retracted_triples.collect(), + &ident_set.retracted, + )?; // Now we process all other retractions. - let report = update_attribute_map_from_entid_triples(&mut schema.attribute_map, - asserted_triples.chain(altered_triples).collect(), - non_schema_retractions)?; + let report = update_attribute_map_from_entid_triples( + &mut schema.attribute_map, + asserted_triples.chain(altered_triples).collect(), + non_schema_retractions, + )?; let mut idents_altered: BTreeMap = BTreeMap::new(); @@ -420,6 +445,6 @@ pub fn update_schema_from_entid_quadruples(schema: &mut Schema, assertions: U Ok(MetadataReport { idents_altered: idents_altered, - .. report + ..report }) } diff --git a/db/src/schema.rs b/db/src/schema.rs index fff52032..4b2b58ae 100644 --- a/db/src/schema.rs +++ b/db/src/schema.rs @@ -11,54 +11,56 @@ #![allow(dead_code)] use db::TypedSQLValue; +use db_traits::errors::{DbErrorKind, Result}; use edn; -use db_traits::errors::{ - DbErrorKind, - Result, -}; use edn::symbols; -use core_traits::{ - attribute, - Attribute, - Entid, - KnownEntid, - TypedValue, - ValueType, -}; +use core_traits::{attribute, Attribute, Entid, KnownEntid, TypedValue, ValueType}; -use mentat_core::{ - EntidMap, - HasSchema, - IdentMap, - Schema, - AttributeMap, -}; +use mentat_core::{AttributeMap, EntidMap, HasSchema, IdentMap, Schema}; use metadata; -use metadata::{ - AttributeAlteration, -}; +use metadata::AttributeAlteration; pub trait AttributeValidation { - fn validate(&self, ident: F) -> Result<()> where F: Fn() -> String; + fn validate(&self, ident: F) -> Result<()> + where + F: Fn() -> String; } impl AttributeValidation for Attribute { - fn validate(&self, ident: F) -> Result<()> where F: Fn() -> String { + fn validate(&self, ident: F) -> Result<()> + where + F: Fn() -> String, + { 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 { - 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 { - 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 { - 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 { - 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, // 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. fn validate_attribute_map(entid_map: &EntidMap, attribute_map: &AttributeMap) -> Result<()> { 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)?; } Ok(()) } -#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] pub struct AttributeBuilder { helpful: bool, pub value_type: Option, @@ -103,9 +110,9 @@ impl AttributeBuilder { /// retraction. Only attributes that we allow to change are duplicated here. pub fn to_modify_attribute(attribute: &Attribute) -> Self { let mut ab = AttributeBuilder::default(); - ab.multival = Some(attribute.multival); - ab.unique = Some(attribute.unique); - ab.component = Some(attribute.component); + ab.multival = Some(attribute.multival); + ab.unique = Some(attribute.unique); + ab.component = Some(attribute.component); ab } @@ -157,17 +164,23 @@ impl AttributeBuilder { pub fn validate_install_attribute(&self) -> Result<()> { 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(()) } pub fn validate_alter_attribute(&self) -> Result<()> { 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() { - bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); + bail!(DbErrorKind::BadSchemaAssertion( + "Schema alteration must not set :db/fulltext".into() + )); } Ok(()) } @@ -247,27 +260,40 @@ pub trait SchemaBuilding { fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword>; fn require_entid(&self, ident: &symbols::Keyword) -> Result; fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>; - fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result; + fn from_ident_map_and_attribute_map( + ident_map: IdentMap, + attribute_map: AttributeMap, + ) -> Result; fn from_ident_map_and_triples(ident_map: IdentMap, assertions: U) -> Result - where U: IntoIterator; + where + U: IntoIterator; } impl SchemaBuilding for Schema { 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 { - 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> { - 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. - fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result { - let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect(); + fn from_ident_map_and_attribute_map( + ident_map: IdentMap, + attribute_map: AttributeMap, + ) -> Result { + let entid_map: EntidMap = ident_map + .iter() + .map(|(k, v)| (v.clone(), k.clone())) + .collect(); validate_attribute_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`. fn from_ident_map_and_triples(ident_map: IdentMap, assertions: U) -> Result - where U: IntoIterator{ + where + U: IntoIterator, + { + let entid_assertions: Result> = 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> = 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 mut schema = Schema::from_ident_map_and_attribute_map(ident_map, AttributeMap::default())?; - let metadata_report = metadata::update_attribute_map_from_entid_triples(&mut schema.attribute_map, - entid_assertions?, - // No retractions. - vec![])?; + let mut schema = + Schema::from_ident_map_and_attribute_map(ident_map, AttributeMap::default())?; + 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. 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) /// coerce the given value into the value type's value set. - fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result; + fn to_typed_value( + &self, + value: &edn::ValueAndSpan, + value_type: ValueType, + ) -> Result; } impl SchemaTypeChecking for Schema { - fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result { + fn to_typed_value( + &self, + value: &edn::ValueAndSpan, + value_type: ValueType, + ) -> Result { // 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 // wrapper function. @@ -324,38 +369,35 @@ impl SchemaTypeChecking for Schema { (ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv), // 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::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. // 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 // collection by 8! - (vt @ ValueType::Boolean, _) | - (vt @ ValueType::Long, _) | - (vt @ ValueType::Double, _) | - (vt @ ValueType::String, _) | - (vt @ ValueType::Uuid, _) | - (vt @ ValueType::Instant, _) | - (vt @ ValueType::Keyword, _) | - (vt @ ValueType::Ref, _) - => bail!(DbErrorKind::BadValuePair(format!("{}", value), vt)), - } + (vt @ ValueType::Boolean, _) + | (vt @ ValueType::Long, _) + | (vt @ ValueType::Double, _) + | (vt @ ValueType::String, _) + | (vt @ ValueType::Uuid, _) + | (vt @ ValueType::Instant, _) + | (vt @ ValueType::Keyword, _) + | (vt @ ValueType::Ref, _) => { + bail!(DbErrorKind::BadValuePair(format!("{}", value), vt)) + } + }, } } } - - #[cfg(test)] mod test { - use super::*; use self::edn::Keyword; + use super::*; - fn add_attribute(schema: &mut Schema, - ident: Keyword, - entid: Entid, - attribute: Attribute) { - + fn add_attribute(schema: &mut Schema, ident: Keyword, entid: Entid, attribute: Attribute) { schema.entid_map.insert(entid, ident.clone()); schema.ident_map.insert(ident.clone(), entid); @@ -370,55 +412,80 @@ mod test { fn validate_attribute_map_success() { let mut schema = Schema::default(); // attribute that is not an index has no uniqueness - add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 97, Attribute { - index: false, - value_type: ValueType::Boolean, - fulltext: false, - unique: None, - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bar"), + 97, + Attribute { + index: false, + value_type: ValueType::Boolean, + fulltext: false, + unique: None, + multival: false, + component: false, + no_history: false, + }, + ); // attribute is unique by value and an index - add_attribute(&mut schema, Keyword::namespaced("foo", "baz"), 98, Attribute { - index: true, - value_type: ValueType::Long, - fulltext: false, - unique: Some(attribute::Unique::Value), - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "baz"), + 98, + Attribute { + index: true, + value_type: ValueType::Long, + fulltext: false, + unique: Some(attribute::Unique::Value), + multival: false, + component: false, + no_history: false, + }, + ); // attribue is unique by identity and an index - add_attribute(&mut schema, Keyword::namespaced("foo", "bat"), 99, Attribute { - index: true, - value_type: ValueType::Ref, - fulltext: false, - unique: Some(attribute::Unique::Identity), - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bat"), + 99, + Attribute { + index: true, + value_type: ValueType::Ref, + fulltext: false, + unique: Some(attribute::Unique::Identity), + multival: false, + component: false, + no_history: false, + }, + ); // attribute is a components and a `Ref` - add_attribute(&mut schema, Keyword::namespaced("foo", "bak"), 100, Attribute { - index: false, - value_type: ValueType::Ref, - fulltext: false, - unique: None, - multival: false, - component: true, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bak"), + 100, + Attribute { + index: false, + value_type: ValueType::Ref, + fulltext: false, + unique: None, + multival: false, + component: true, + no_history: false, + }, + ); // fulltext attribute is a string and an index - add_attribute(&mut schema, Keyword::namespaced("foo", "bap"), 101, Attribute { - index: true, - value_type: ValueType::String, - fulltext: true, - unique: None, - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bap"), + 101, + Attribute { + index: true, + value_type: ValueType::String, + fulltext: true, + unique: None, + multival: false, + component: false, + no_history: false, + }, + ); assert!(validate_attribute_map(&schema.entid_map, &schema.attribute_map).is_ok()); } @@ -428,88 +495,150 @@ mod test { let mut schema = Schema::default(); // attribute unique by value but not index let ident = Keyword::namespaced("foo", "bar"); - add_attribute(&mut schema, ident , 99, Attribute { - index: false, - value_type: ValueType::Boolean, - fulltext: false, - unique: Some(attribute::Unique::Value), - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + ident, + 99, + Attribute { + index: false, + value_type: ValueType::Boolean, + 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()); - assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_value without :db/index true for entid: :foo/bar".into()))); + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map) + .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] fn invalid_schema_unique_identity_not_index() { let mut schema = Schema::default(); // attribute is unique by identity but not index - add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { - index: false, - value_type: ValueType::Long, - fulltext: false, - unique: Some(attribute::Unique::Identity), - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bar"), + 99, + Attribute { + index: false, + value_type: ValueType::Long, + 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()); - assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_identity without :db/index true for entid: :foo/bar".into()))); + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map) + .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] fn invalid_schema_component_not_ref() { let mut schema = Schema::default(); // attribute that is a component is not a `Ref` - add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { - index: false, - value_type: ValueType::Boolean, - fulltext: false, - unique: None, - multival: false, - component: true, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bar"), + 99, + Attribute { + index: false, + value_type: ValueType::Boolean, + 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()); - assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar".into()))); + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map) + .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] fn invalid_schema_fulltext_not_index() { let mut schema = Schema::default(); // attribute that is fulltext is not an index - add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { - index: false, - value_type: ValueType::String, - fulltext: true, - unique: None, - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bar"), + 99, + Attribute { + index: false, + value_type: ValueType::String, + 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()); - assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/index true for entid: :foo/bar".into()))); + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map) + .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() { let mut schema = Schema::default(); // attribute that is fulltext and not a `String` - add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute { - index: true, - value_type: ValueType::Long, - fulltext: true, - unique: None, - multival: false, - component: false, - no_history: false, - }); + add_attribute( + &mut schema, + Keyword::namespaced("foo", "bar"), + 99, + Attribute { + index: true, + value_type: ValueType::Long, + 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()); - assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar".into()))); + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map) + .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() + )) + ); } } diff --git a/db/src/timelines.rs b/db/src/timelines.rs index 26ad3060..3fc199f4 100644 --- a/db/src/timelines.rs +++ b/db/src/timelines.rs @@ -12,57 +12,39 @@ use std::ops::RangeFrom; use rusqlite; -use db_traits::errors::{ - DbErrorKind, - Result, -}; +use db_traits::errors::{DbErrorKind, Result}; -use core_traits::{ - Entid, - KnownEntid, - TypedValue, -}; +use core_traits::{Entid, KnownEntid, TypedValue}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::{ - InternSet, -}; +use edn::InternSet; use edn::entities::OpType; use db; -use db::{ - TypedSQLValue, -}; +use db::TypedSQLValue; -use tx::{ - transact_terms_with_action, - TransactorAction, -}; +use tx::{transact_terms_with_action, TransactorAction}; -use types::{ - PartitionMap, -}; +use types::PartitionMap; -use internal_types::{ - Term, - TermWithoutTempIds, -}; +use internal_types::{Term, TermWithoutTempIds}; -use watcher::{ - NullWatcher, -}; +use watcher::NullWatcher; /// Collects a supplied tx range into an DESC ordered Vec of valid txs, /// ensuring they all belong to the same timeline. -fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom, timeline: Entid) -> Result> { +fn collect_ordered_txs_to_move( + conn: &rusqlite::Connection, + txs_from: RangeFrom, + timeline: Entid, +) -> Result> { 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)>{ - Ok((row.get(0)?, row.get(1)?)) - })?; + let mut rows = stmt.query_and_then( + &[&txs_from.start, &timeline], + |row: &rusqlite::Row| -> Result<(Entid, Entid)> { Ok((row.get(0)?, row.get(1)?)) }, + )?; let mut txs = vec![]; @@ -72,8 +54,8 @@ fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom< let t = t?; txs.push(t.0); t.1 - }, - None => bail!(DbErrorKind::TimelinesInvalidRange) + } + None => bail!(DbErrorKind::TimelinesInvalidRange), }; while let Some(t) = rows.next() { @@ -87,13 +69,22 @@ fn collect_ordered_txs_to_move(conn: &rusqlite::Connection, txs_from: RangeFrom< 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. - conn.execute(&format!( - "UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}", + conn.execute( + &format!( + "UPDATE timelined_transactions SET timeline = {} WHERE tx IN {}", new_timeline, ::repeat_values(tx_ids.len(), 1) - ), &(tx_ids.iter().map(|x| x as &rusqlite::types::ToSql).collect::>()) + ), + &(tx_ids + .iter() + .map(|x| x as &dyn rusqlite::types::ToSql) + .collect::>()), )?; 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 { - let mut stmt = conn.prepare("SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline")?; - let rows = stmt.query_and_then(&[&timeline], |row| -> Result { - Ok(row.get(0)?) - })?; + let mut stmt = conn.prepare( + "SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline", + )?; + let rows = stmt.query_and_then(&[&timeline], |row| -> Result { Ok(row.get(0)?) })?; Ok(rows.count() == 0) } /// Get terms for tx_id, reversing them in meaning (swap add & retract). -fn reversed_terms_for(conn: &rusqlite::Connection, tx_id: Entid) -> Result> { +fn reversed_terms_for( + conn: &rusqlite::Connection, + tx_id: Entid, +) -> Result> { 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 { - let op = match row.get(5)? { - true => OpType::Retract, - false => OpType::Add - }; - Ok(Term::AddOrRetract( - op, - KnownEntid(row.get(0)?), - row.get(1)?, - TypedValue::from_sql_value_pair(row.get(2)?, row.get(3)?)?, - )) - })?; + let mut rows = stmt.query_and_then( + &[&tx_id, &::TIMELINE_MAIN], + |row| -> Result { + let op = match row.get(5)? { + true => OpType::Retract, + false => OpType::Add, + }; + Ok(Term::AddOrRetract( + op, + KnownEntid(row.get(0)?), + row.get(1)?, + TypedValue::from_sql_value_pair(row.get(2)?, row.get(3)?)?, + )) + }, + )?; let mut terms = vec![]; @@ -136,11 +133,17 @@ fn reversed_terms_for(conn: &rusqlite::Connection, tx_id: Entid) -> Result, new_timeline: Entid) -> Result<(Option, PartitionMap)> { - +pub fn move_from_main_timeline( + conn: &rusqlite::Connection, + schema: &Schema, + partition_map: PartitionMap, + txs_from: RangeFrom, + new_timeline: Entid, +) -> Result<(Option, PartitionMap)> { 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 @@ -158,9 +161,14 @@ pub fn move_from_main_timeline(conn: &rusqlite::Connection, schema: &Schema, // Rewind schema and datoms. 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()), - InternSet::new(), TransactorAction::Materialize + InternSet::new(), + TransactorAction::Materialize, )?; // Rewind operation generated a 'tx' and a 'txInstant' assertion, which got @@ -188,13 +196,9 @@ mod tests { use edn; - use std::borrow::{ - Borrow, - }; + use std::borrow::Borrow; - use debug::{ - TestConn, - }; + use debug::TestConn; use bootstrap; @@ -203,7 +207,7 @@ mod tests { fn update_conn(conn: &mut TestConn, schema: &Option, pmap: &PartitionMap) { match schema { &Some(ref s) => conn.schema = s.clone(), - &None => () + &None => (), }; conn.partition_map = pmap.clone(); } @@ -223,9 +227,13 @@ mod tests { let partition_map1 = conn.partition_map.clone(); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - conn.last_tx_id().., 1 - ).expect("moved single tx"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + conn.last_tx_id().., + 1, + ) + .expect("moved single tx"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), "[]"); @@ -238,20 +246,30 @@ mod tests { // Ensure that we can't move transactions to a non-empty timeline: move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - conn.last_tx_id().., 1 - ).expect_err("Can't move transactions to a non-empty timeline"); + &conn.sqlite, + &conn.schema, + 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!(partition_map1, partition_map2); - assert_matches!(conn.datoms(), r#" + assert_matches!( + conn.datoms(), + r#" [[37 :db/doc "test"]] - "#); - assert_matches!(conn.transactions(), r#" + "# + ); + assert_matches!( + conn.transactions(), + r#" [[[37 :db/doc "test" ?tx true] [?tx :db/txInstant ?ms ?tx true]]] - "#); + "# + ); } #[test] @@ -271,9 +289,13 @@ mod tests { let schema1 = conn.schema.clone(); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - conn.last_tx_id().., 1 - ).expect("moved single tx"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + conn.last_tx_id().., + 1, + ) + .expect("moved single tx"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), "[]"); @@ -287,17 +309,23 @@ mod tests { assert_eq!(conn.partition_map, partition_map1); assert_eq!(conn.schema, schema1); - assert_matches!(conn.datoms(), r#" + assert_matches!( + conn.datoms(), + r#" [[?e :db/ident :test/entid] [?e :db/doc "test"] [?e :db.schema/version 1]] - "#); - assert_matches!(conn.transactions(), r#" + "# + ); + assert_matches!( + conn.transactions(), + r#" [[[?e :db/ident :test/entid ?tx true] [?e :db/doc "test" ?tx true] [?e :db.schema/version 1 ?tx true] [?tx :db/txInstant ?ms ?tx true]]] - "#); + "# + ); } #[test] @@ -306,59 +334,83 @@ mod tests { conn.sanitized_partition_map(); // 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}] - "#); + "# + ); // Make an assertion against our schema. assert_transact!(conn, r#"[{:person/name "Vanya"}]"#); // Move that assertion away from the main timeline. let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - conn.last_tx_id().., 1 - ).expect("moved single tx"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + conn.last_tx_id().., + 1, + ) + .expect("moved single tx"); update_conn(&mut conn, &new_schema, &new_partition_map); // Assert that our datoms are now just the schema. - assert_matches!(conn.datoms(), " + assert_matches!( + conn.datoms(), + " [[?e :db/ident :person/name] [?e :db/valueType :db.type/string] [?e :db/cardinality :db.cardinality/one] [?e :db/unique :db.unique/identity] - [?e :db/index true]]"); + [?e :db/index true]]" + ); // Same for transactions. - assert_matches!(conn.transactions(), " + assert_matches!( + conn.transactions(), + " [[[?e :db/ident :person/name ?tx true] [?e :db/valueType :db.type/string ?tx true] [?e :db/cardinality :db.cardinality/one ?tx true] [?e :db/unique :db.unique/identity ?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. - assert_transact!(conn, r#" - [[:db/add "tempid" :person/name "Vanya"]]"#); + assert_transact!( + conn, + r#" + [[:db/add "tempid" :person/name "Vanya"]]"# + ); // Now, change that fact. This is the "clashing" transaction, if we're // performing a timeline move using the transactor. - assert_transact!(conn, r#" - [[:db/add (lookup-ref :person/name "Vanya") :person/name "Ivan"]]"#); + assert_transact!( + 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_matches!(conn.datoms(), r#" + assert_matches!( + conn.datoms(), + r#" [[?e1 :db/ident :person/name] [?e1 :db/valueType :db.type/string] [?e1 :db/cardinality :db.cardinality/one] [?e1 :db/unique :db.unique/identity] [?e1 :db/index true] [?e2 :person/name "Ivan"]] - "#); + "# + ); // Assert that we have three correct looking transactions. // This will fail if we're not cleaning up the 'datoms' table // after the timeline move. - assert_matches!(conn.transactions(), r#" + assert_matches!( + conn.transactions(), + r#" [[ [?e1 :db/ident :person/name ?tx1 true] [?e1 :db/valueType :db.type/string ?tx1 true] @@ -376,7 +428,8 @@ mod tests { [?e2 :person/name "Vanya" ?tx3 false] [?tx3 :db/txInstant ?ms3 ?tx3 true] ]] - "#); + "# + ); } #[test] @@ -397,8 +450,13 @@ mod tests { let schema1 = conn.schema.clone(); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - report1.tx_id.., 1).expect("moved single tx"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + report1.tx_id.., + 1, + ) + .expect("moved single tx"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), "[]"); @@ -414,15 +472,20 @@ mod tests { assert_eq!(partition_map1, partition_map2); assert_eq!(schema1, schema2); - assert_matches!(conn.datoms(), r#" + assert_matches!( + conn.datoms(), + r#" [[?e1 :db/ident :test/one] [?e1 :db/valueType :db.type/long] [?e1 :db/cardinality :db.cardinality/one] [?e2 :db/ident :test/many] [?e2 :db/valueType :db.type/long] [?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/valueType :db.type/long ?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/cardinality :db.cardinality/many ?tx1 true] [?tx1 :db/txInstant ?ms ?tx1 true]]] - "#); + "# + ); } #[test] @@ -458,8 +522,13 @@ mod tests { let schema1 = conn.schema.clone(); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - report1.tx_id.., 1).expect("moved single tx"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + report1.tx_id.., + 1, + ) + .expect("moved single tx"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), "[]"); @@ -475,15 +544,20 @@ mod tests { assert_eq!(partition_map1, partition_map2); assert_eq!(schema1, schema2); - assert_matches!(conn.datoms(), r#" + assert_matches!( + conn.datoms(), + r#" [[?e1 :db/ident :test/one] [?e1 :db/valueType :db.type/string] [?e1 :db/cardinality :db.cardinality/one] [?e1 :db/unique :db.unique/value] [?e1 :db/index true] [?e1 :db/fulltext true]] - "#); - assert_matches!(conn.transactions(), r#" + "# + ); + assert_matches!( + conn.transactions(), + r#" [[[?e1 :db/ident :test/one ?tx1 true] [?e1 :db/valueType :db.type/string ?tx1 true] [?e1 :db/cardinality :db.cardinality/one ?tx1 true] @@ -491,10 +565,11 @@ mod tests { [?e1 :db/index true ?tx1 true] [?e1 :db/fulltext true ?tx1 true] [?tx1 :db/txInstant ?ms ?tx1 true]]] - "#); + "# + ); } - #[test] + #[test] fn test_pop_schema_all_attributes_component() { let mut conn = TestConn::default(); conn.sanitized_partition_map(); @@ -519,8 +594,13 @@ mod tests { let schema1 = conn.schema.clone(); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - report1.tx_id.., 1).expect("moved single tx"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + report1.tx_id.., + 1, + ) + .expect("moved single tx"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), "[]"); @@ -531,7 +611,10 @@ mod tests { assert_eq!(conn.schema.entid_map, schema0.entid_map); assert_eq!(conn.schema.ident_map, schema0.ident_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_eq!(conn.schema, schema0); @@ -543,15 +626,20 @@ mod tests { assert_eq!(partition_map1, partition_map2); assert_eq!(schema1, schema2); - assert_matches!(conn.datoms(), r#" + assert_matches!( + conn.datoms(), + r#" [[?e1 :db/ident :test/one] [?e1 :db/valueType :db.type/ref] [?e1 :db/cardinality :db.cardinality/one] [?e1 :db/unique :db.unique/value] [?e1 :db/isComponent true] [?e1 :db/index true]] - "#); - assert_matches!(conn.transactions(), r#" + "# + ); + assert_matches!( + conn.transactions(), + r#" [[[?e1 :db/ident :test/one ?tx1 true] [?e1 :db/valueType :db.type/ref ?tx1 true] [?e1 :db/cardinality :db.cardinality/one ?tx1 true] @@ -559,7 +647,8 @@ mod tests { [?e1 :db/isComponent true ?tx1 true] [?e1 :db/index true ?tx1 true] [?tx1 :db/txInstant ?ms ?tx1 true]]] - "#); + "# + ); } #[test] @@ -569,12 +658,17 @@ mod tests { let partition_map_after_bootstrap = conn.partition_map.clone(); - assert_eq!((65536..65538), - conn.partition_map.allocate_entids(":db.part/user", 2)); - let tx_report0 = assert_transact!(conn, r#"[ + assert_eq!( + (65536..65538), + 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 65537 :db/ident :test/many :db/valueType :db.type/long :db/cardinality :db.cardinality/many} - ]"#); + ]"# + ); let first = "[ [65536 :db/ident :test/one] @@ -590,21 +684,28 @@ mod tests { let partition_map0 = conn.partition_map.clone(); - assert_eq!((65538..65539), - conn.partition_map.allocate_entids(":db.part/user", 1)); - let tx_report1 = assert_transact!(conn, r#"[ + assert_eq!( + (65538..65539), + 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/many 2] [:db/add 65538 :test/many 3] - ]"#); + ]"# + ); let schema1 = conn.schema.clone(); let partition_map1 = conn.partition_map.clone(); - assert_matches!(conn.last_transaction(), - "[[65538 :test/one 1 ?tx true] + assert_matches!( + conn.last_transaction(), + "[[65538 :test/one 1 ?tx true] [65538 :test/many 2 ?tx true] [65538 :test/many 3 ?tx true] - [?tx :db/txInstant ?ms ?tx true]]"); + [?tx :db/txInstant ?ms ?tx true]]" + ); let second = "[ [65536 :db/ident :test/one] @@ -621,20 +722,25 @@ mod tests { ]"; 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/many 2] [:db/retract 65538 :test/many 3] [:db/add 65538 :test/many 4] - ]"#); + ]"# + ); let schema2 = conn.schema.clone(); - assert_matches!(conn.last_transaction(), - "[[65538 :test/one 1 ?tx false] + assert_matches!( + conn.last_transaction(), + "[[65538 :test/one 1 ?tx false] [65538 :test/one 2 ?tx true] [65538 :test/many 3 ?tx false] [65538 :test/many 4 ?tx true] - [?tx :db/txInstant ?ms ?tx true]]"); + [?tx :db/txInstant ?ms ?tx true]]" + ); let third = "[ [65536 :db/ident :test/one] @@ -652,8 +758,13 @@ mod tests { assert_matches!(conn.datoms(), third); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - tx_report2.tx_id.., 1).expect("moved timeline"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + tx_report2.tx_id.., + 1, + ) + .expect("moved timeline"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), second); @@ -664,8 +775,13 @@ mod tests { assert_eq!(conn.partition_map, partition_map1); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - tx_report1.tx_id.., 2).expect("moved timeline"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + tx_report1.tx_id.., + 2, + ) + .expect("moved timeline"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_matches!(conn.datoms(), first); assert_eq!(None, new_schema); @@ -673,8 +789,13 @@ mod tests { assert_eq!(conn.partition_map, partition_map0); let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - tx_report0.tx_id.., 3).expect("moved timeline"); + &conn.sqlite, + &conn.schema, + conn.partition_map.clone(), + tx_report0.tx_id.., + 3, + ) + .expect("moved timeline"); update_conn(&mut conn, &new_schema, &new_partition_map); assert_eq!(true, new_schema.is_some()); assert_eq!(bootstrap::bootstrap_schema(), conn.schema); @@ -690,31 +811,47 @@ mod tests { let partition_map_after_bootstrap = conn.partition_map.clone(); - assert_eq!((65536..65539), - conn.partition_map.allocate_entids(":db.part/user", 3)); - let tx_report0 = assert_transact!(conn, r#"[ + assert_eq!( + (65536..65539), + 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 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/many 2] [:db/add 65538 :test/many 3] - ]"#); + ]"# + ); - assert_transact!(conn, r#"[ + assert_transact!( + conn, + r#"[ [:db/add 65538 :test/one 2] [:db/add 65538 :test/many 2] [:db/retract 65538 :test/many 3] [:db/add 65538 :test/many 4] - ]"#); + ]"# + ); // Remove all of these transactions from the main timeline, // ensure we get back to a "just bootstrapped" state. let (new_schema, new_partition_map) = move_from_main_timeline( - &conn.sqlite, &conn.schema, conn.partition_map.clone(), - tx_report0.tx_id.., 1).expect("moved timeline"); + &conn.sqlite, + &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); diff --git a/db/src/tx.rs b/db/src/tx.rs index 25c700a1..fcc67b6b 100644 --- a/db/src/tx.rs +++ b/db/src/tx.rs @@ -45,93 +45,37 @@ //! names -- `TermWithTempIdsAndLookupRefs`, anyone? -- and strongly typed stage functions will help //! keep everything straight. -use std::borrow::{ - Cow, -}; -use std::collections::{ - BTreeMap, - BTreeSet, - VecDeque, -}; -use std::iter::{ - once, -}; +use std::borrow::Cow; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::iter::once; use db; -use db::{ - MentatStoring, -}; -use edn::{ - InternSet, - Keyword, -}; +use db::MentatStoring; +use db_traits::errors; +use db_traits::errors::{DbErrorKind, Result}; +use edn::{InternSet, Keyword}; use entids; -use db_traits::errors as errors; -use db_traits::errors::{ - DbErrorKind, - Result, -}; use internal_types::{ - AddAndRetract, - AEVTrie, - KnownEntidOr, - LookupRef, - LookupRefOrTempId, - TempIdHandle, - TempIdMap, - Term, - TermWithTempIds, - TermWithTempIdsAndLookupRefs, - TermWithoutTempIds, - TypedValueOr, - replace_lookup_ref, + replace_lookup_ref, AEVTrie, AddAndRetract, KnownEntidOr, LookupRef, LookupRefOrTempId, + TempIdHandle, TempIdMap, Term, TermWithTempIds, TermWithTempIdsAndLookupRefs, + TermWithoutTempIds, TypedValueOr, }; use mentat_core::util::Either; -use core_traits::{ - attribute, - Attribute, - Entid, - KnownEntid, - TypedValue, - ValueType, - now, -}; +use core_traits::{attribute, now, Attribute, Entid, KnownEntid, TypedValue, ValueType}; -use mentat_core::{ - DateTime, - Schema, - TxReport, - Utc, -}; +use mentat_core::{DateTime, Schema, TxReport, Utc}; use edn::entities as entmod; -use edn::entities::{ - AttributePlace, - Entity, - OpType, - TempId, -}; +use edn::entities::{AttributePlace, Entity, OpType, TempId}; use metadata; use rusqlite; -use schema::{ - SchemaBuilding, -}; +use schema::SchemaBuilding; use tx_checking; -use types::{ - AVMap, - AVPair, - PartitionMap, - TransactableValue, -}; -use upsert_resolution::{ - FinalPopulations, - Generation, -}; -use watcher::{ - TransactWatcher, -}; +use types::{AVMap, AVPair, PartitionMap, TransactableValue}; +use upsert_resolution::{FinalPopulations, Generation}; +use watcher::TransactWatcher; /// Defines transactor's high level behaviour. pub(crate) enum TransactorAction { @@ -150,7 +94,10 @@ pub(crate) enum TransactorAction { /// A transaction on its way to being applied. #[derive(Debug)] -pub struct Tx<'conn, 'a, W> where W: TransactWatcher { +pub struct Tx<'conn, 'a, W> +where + W: TransactWatcher, +{ /// The storage to apply against. In the future, this will be a Mentat connection. store: &'conn rusqlite::Connection, // TODO: db::MentatStoring, @@ -179,7 +126,9 @@ pub struct Tx<'conn, 'a, W> where W: TransactWatcher { /// Remove any :db/id value from the given map notation, converting the returned value into /// something suitable for the entity position rather than something suitable for a value position. -pub fn remove_db_id(map: &mut entmod::MapNotation) -> Result>> { +pub fn remove_db_id( + map: &mut entmod::MapNotation, +) -> Result>> { // TODO: extract lazy defined constant. let db_id_key = entmod::EntidOrIdent::Ident(Keyword::namespaced("db", "id")); @@ -190,10 +139,9 @@ pub fn remove_db_id(map: &mut entmod::MapNotation) -> R entmod::ValuePlace::TempId(e) => Some(entmod::EntityPlace::TempId(e)), entmod::ValuePlace::TxFunction(e) => Some(entmod::EntityPlace::TxFunction(e)), entmod::ValuePlace::Atom(v) => Some(v.into_entity_place()?), - entmod::ValuePlace::Vector(_) | - entmod::ValuePlace::MapNotation(_) => { + entmod::ValuePlace::Vector(_) | entmod::ValuePlace::MapNotation(_) => { bail!(DbErrorKind::InputError(errors::InputError::BadDbId)) - }, + } } } else { None @@ -202,14 +150,18 @@ pub fn remove_db_id(map: &mut entmod::MapNotation) -> R Ok(db_id) } -impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { +impl<'conn, 'a, W> Tx<'conn, 'a, W> +where + W: TransactWatcher, +{ pub fn new( store: &'conn rusqlite::Connection, partition_map: PartitionMap, schema_for_mutation: &'a Schema, schema: &'a Schema, watcher: W, - tx_id: Entid) -> Tx<'conn, 'a, W> { + tx_id: Entid, + ) -> Tx<'conn, 'a, W> { Tx { store: store, partition_map: partition_map, @@ -223,7 +175,10 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { /// Given a collection of tempids and the [a v] pairs that they might upsert to, resolve exactly /// which [a v] pairs do upsert to entids, and map each tempid that upserts to the upserted /// entid. The keys of the resulting map are exactly those tempids that upserted. - pub(crate) fn resolve_temp_id_avs<'b>(&self, temp_id_avs: &'b [(TempIdHandle, AVPair)]) -> Result { + pub(crate) fn resolve_temp_id_avs<'b>( + &self, + temp_id_avs: &'b [(TempIdHandle, AVPair)], + ) -> Result { if temp_id_avs.is_empty() { return Ok(TempIdMap::default()); } @@ -246,18 +201,30 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { let mut conflicting_upserts: BTreeMap> = BTreeMap::default(); for &(ref tempid, ref av_pair) in temp_id_avs { - trace!("tempid {:?} av_pair {:?} -> {:?}", tempid, av_pair, av_map.get(&av_pair)); + trace!( + "tempid {:?} av_pair {:?} -> {:?}", + tempid, + av_pair, + av_map.get(&av_pair) + ); if let Some(entid) = av_map.get(&av_pair).cloned().map(KnownEntid) { tempids.insert(tempid.clone(), entid).map(|previous| { if entid != previous { - conflicting_upserts.entry((**tempid).clone()).or_insert_with(|| once(previous).collect::>()).insert(entid); + conflicting_upserts + .entry((**tempid).clone()) + .or_insert_with(|| once(previous).collect::>()) + .insert(entid); } }); } } if !conflicting_upserts.is_empty() { - bail!(DbErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); + bail!(DbErrorKind::SchemaConstraintViolation( + errors::SchemaConstraintViolation::ConflictingUpserts { + conflicting_upserts + } + )); } Ok(tempids) @@ -268,7 +235,17 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { /// /// The `Term` instances produce share interned TempId and LookupRef handles, and we return the /// interned handle sets so that consumers can ensure all handles are used appropriately. - fn entities_into_terms_with_temp_ids_and_lookup_refs(&self, entities: I) -> Result<(Vec, InternSet, InternSet)> where I: IntoIterator> { + fn entities_into_terms_with_temp_ids_and_lookup_refs( + &self, + entities: I, + ) -> Result<( + Vec, + InternSet, + InternSet, + )> + where + I: IntoIterator>, + { struct InProcess<'a> { partition_map: &'a PartitionMap, schema: &'a Schema, @@ -279,7 +256,11 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } impl<'a> InProcess<'a> { - fn with_schema_and_partition_map(schema: &'a Schema, partition_map: &'a PartitionMap, tx_id: KnownEntid) -> InProcess<'a> { + fn with_schema_and_partition_map( + schema: &'a Schema, + partition_map: &'a PartitionMap, + tx_id: KnownEntid, + ) -> InProcess<'a> { InProcess { partition_map, schema, @@ -302,16 +283,27 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { self.schema.require_entid(e) } - fn intern_lookup_ref(&mut self, lookup_ref: &entmod::LookupRef) -> Result { + fn intern_lookup_ref( + &mut self, + lookup_ref: &entmod::LookupRef, + ) -> Result { let lr_a: i64 = match lookup_ref.a { AttributePlace::Entid(entmod::EntidOrIdent::Entid(ref a)) => *a, - AttributePlace::Entid(entmod::EntidOrIdent::Ident(ref a)) => self.schema.require_entid(&a)?.into(), + AttributePlace::Entid(entmod::EntidOrIdent::Ident(ref a)) => { + self.schema.require_entid(&a)?.into() + } }; let lr_attribute: &Attribute = self.schema.require_attribute_for_entid(lr_a)?; - let lr_typed_value: TypedValue = lookup_ref.v.clone().into_typed_value(&self.schema, lr_attribute.value_type)?; + let lr_typed_value: TypedValue = lookup_ref + .v + .clone() + .into_typed_value(&self.schema, lr_attribute.value_type)?; if lr_attribute.unique.is_none() { - bail!(DbErrorKind::NotYetImplemented(format!("Cannot resolve (lookup-ref {} {:?}) with attribute that is not :db/unique", lr_a, lr_typed_value))) + bail!(DbErrorKind::NotYetImplemented(format!( + "Cannot resolve (lookup-ref {} {:?}) with attribute that is not :db/unique", + lr_a, lr_typed_value + ))) } Ok(self.lookup_refs.intern((lr_a, lr_typed_value))) @@ -324,7 +316,10 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { entmod::EntityPlace::TempId(TempId::Internal(self.mentat_id_count).into()) } - fn entity_e_into_term_e(&mut self, x: entmod::EntityPlace) -> Result> { + fn entity_e_into_term_e( + &mut self, + x: entmod::EntityPlace, + ) -> Result> { match x { entmod::EntityPlace::Entid(e) => { let e = match e { @@ -332,22 +327,25 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { entmod::EntidOrIdent::Ident(ref e) => self.ensure_ident_exists(&e)?, }; Ok(Either::Left(e)) - }, + } - entmod::EntityPlace::TempId(e) => { - Ok(Either::Right(LookupRefOrTempId::TempId(self.temp_ids.intern(e)))) - }, + entmod::EntityPlace::TempId(e) => Ok(Either::Right(LookupRefOrTempId::TempId( + self.temp_ids.intern(e), + ))), - entmod::EntityPlace::LookupRef(ref lookup_ref) => { - Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?))) - }, + entmod::EntityPlace::LookupRef(ref lookup_ref) => Ok(Either::Right( + LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?), + )), entmod::EntityPlace::TxFunction(ref tx_function) => { match tx_function.op.0.as_str() { "transaction-tx" => Ok(Either::Left(self.tx_id)), - unknown @ _ => bail!(DbErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), + unknown @ _ => bail!(DbErrorKind::NotYetImplemented(format!( + "Unknown transaction function {}", + unknown + ))), } - }, + } } } @@ -359,18 +357,27 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { Ok(a) } - fn entity_e_into_term_v(&mut self, x: entmod::EntityPlace) -> Result> { - self.entity_e_into_term_e(x).map(|r| r.map_left(|ke| TypedValue::Ref(ke.0))) + fn entity_e_into_term_v( + &mut self, + x: entmod::EntityPlace, + ) -> Result> { + self.entity_e_into_term_e(x) + .map(|r| r.map_left(|ke| TypedValue::Ref(ke.0))) } - fn entity_v_into_term_e(&mut self, x: entmod::ValuePlace, backward_a: &entmod::EntidOrIdent) -> Result> { + fn entity_v_into_term_e( + &mut self, + x: entmod::ValuePlace, + backward_a: &entmod::EntidOrIdent, + ) -> Result> { match backward_a.unreversed() { None => { bail!(DbErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute"))); - }, + } Some(forward_a) => { let forward_a = self.entity_a_into_term_a(forward_a)?; - let forward_attribute = self.schema.require_attribute_for_entid(forward_a)?; + let forward_attribute = + self.schema.require_attribute_for_entid(forward_a)?; if forward_attribute.value_type != ValueType::Ref { bail!(DbErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} that is not :db/valueType :db.type/ref", forward_a))) } @@ -415,12 +422,16 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { entmod::ValuePlace::MapNotation(_) => bail!(DbErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))), } - }, + } } } } - let mut in_process = InProcess::with_schema_and_partition_map(&self.schema, &self.partition_map, KnownEntid(self.tx_id)); + let mut in_process = InProcess::with_schema_and_partition_map( + &self.schema, + &self.partition_map, + KnownEntid(self.tx_id), + ); // We want to handle entities in the order they're given to us, while also "exploding" some // entities into many. We therefore push the initial entities onto the back of the deque, @@ -435,7 +446,8 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { Entity::MapNotation(mut map_notation) => { // :db/id is optional; if it's not given, we generate a special internal tempid // to use for upserting. This tempid will not be reported in the TxReport. - let db_id: entmod::EntityPlace = remove_db_id(&mut map_notation)?.unwrap_or_else(|| in_process.allocate_mentat_id()); + let db_id: entmod::EntityPlace = remove_db_id(&mut map_notation)? + .unwrap_or_else(|| in_process.allocate_mentat_id()); // We're not nested, so :db/isComponent is not relevant. We just explode the // map notation. @@ -447,7 +459,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { v: v, }); } - }, + } Entity::AddOrRetract { op, e, a, v } => { let AttributePlace::Entid(a) = a; @@ -456,7 +468,12 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { let reversed_e = in_process.entity_v_into_term_e(v, &a)?; let reversed_a = in_process.entity_a_into_term_a(reversed_a)?; let reversed_v = in_process.entity_e_into_term_v(e)?; - terms.push(Term::AddOrRetract(OpType::Add, reversed_e, reversed_a, reversed_v)); + terms.push(Term::AddOrRetract( + OpType::Add, + reversed_e, + reversed_a, + reversed_v, + )); } else { let a = in_process.entity_a_into_term_a(a)?; let attribute = self.schema.require_attribute_for_entid(a)?; @@ -468,32 +485,44 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // limited cases) coerce the value into the attribute's value set. if attribute.value_type == ValueType::Ref { match v.as_tempid() { - Some(tempid) => Either::Right(LookupRefOrTempId::TempId(in_process.temp_ids.intern(tempid))), - None => v.into_typed_value(&self.schema, attribute.value_type).map(Either::Left)?, + Some(tempid) => Either::Right(LookupRefOrTempId::TempId( + in_process.temp_ids.intern(tempid), + )), + None => v + .into_typed_value(&self.schema, attribute.value_type) + .map(Either::Left)?, } } else { - v.into_typed_value(&self.schema, attribute.value_type).map(Either::Left)? + v.into_typed_value(&self.schema, attribute.value_type) + .map(Either::Left)? } - }, + } - entmod::ValuePlace::Entid(entid) => - Either::Left(TypedValue::Ref(in_process.entity_a_into_term_a(entid)?)), + entmod::ValuePlace::Entid(entid) => Either::Left(TypedValue::Ref( + in_process.entity_a_into_term_a(entid)?, + )), - entmod::ValuePlace::TempId(tempid) => - Either::Right(LookupRefOrTempId::TempId(in_process.temp_ids.intern(tempid))), + entmod::ValuePlace::TempId(tempid) => Either::Right( + LookupRefOrTempId::TempId(in_process.temp_ids.intern(tempid)), + ), entmod::ValuePlace::LookupRef(ref lookup_ref) => { if attribute.value_type != ValueType::Ref { bail!(DbErrorKind::NotYetImplemented(format!("Cannot resolve value lookup ref for attribute {} that is not :db/valueType :db.type/ref", a))) } - Either::Right(LookupRefOrTempId::LookupRef(in_process.intern_lookup_ref(lookup_ref)?)) - }, + Either::Right(LookupRefOrTempId::LookupRef( + in_process.intern_lookup_ref(lookup_ref)?, + )) + } entmod::ValuePlace::TxFunction(ref tx_function) => { let typed_value = match tx_function.op.0.as_str() { "transaction-tx" => TypedValue::Ref(self.tx_id), - unknown @ _ => bail!(DbErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), + unknown @ _ => bail!(DbErrorKind::NotYetImplemented(format!( + "Unknown transaction function {}", + unknown + ))), }; // Here we do schema-aware typechecking: we assert that the computed @@ -508,7 +537,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } Either::Left(typed_value) - }, + } entmod::ValuePlace::Vector(vs) => { if !attribute.multival { @@ -523,8 +552,8 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { v: vv, }); } - continue - }, + continue; + } entmod::ValuePlace::MapNotation(mut map_notation) => { // TODO: consider handling this at the tx-parser level. That would be @@ -541,9 +570,11 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // :db/id is optional; if it's not given, we generate a special internal tempid // to use for upserting. This tempid will not be reported in the TxReport. - let db_id: Option> = remove_db_id(&mut map_notation)?; + let db_id: Option> = + remove_db_id(&mut map_notation)?; let mut dangling = db_id.is_none(); - let db_id: entmod::EntityPlace = db_id.unwrap_or_else(|| in_process.allocate_mentat_id()); + let db_id: entmod::EntityPlace = + db_id.unwrap_or_else(|| in_process.allocate_mentat_id()); // We're nested, so we want to ensure we're not creating "dangling" // entities that can't be reached. If we're :db/isComponent, then this @@ -568,21 +599,34 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // dangling. dangling = false; - let reversed_e = in_process.entity_v_into_term_e(inner_v, &inner_a)?; - let reversed_a = in_process.entity_a_into_term_a(reversed_a)?; - let reversed_v = in_process.entity_e_into_term_v(db_id.clone())?; - terms.push(Term::AddOrRetract(OpType::Add, reversed_e, reversed_a, reversed_v)); + let reversed_e = + in_process.entity_v_into_term_e(inner_v, &inner_a)?; + let reversed_a = + in_process.entity_a_into_term_a(reversed_a)?; + let reversed_v = + in_process.entity_e_into_term_v(db_id.clone())?; + terms.push(Term::AddOrRetract( + OpType::Add, + reversed_e, + reversed_a, + reversed_v, + )); } else { let inner_a = in_process.entity_a_into_term_a(inner_a)?; - let inner_attribute = self.schema.require_attribute_for_entid(inner_a)?; - if inner_attribute.unique == Some(attribute::Unique::Identity) { + let inner_attribute = + self.schema.require_attribute_for_entid(inner_a)?; + if inner_attribute.unique + == Some(attribute::Unique::Identity) + { dangling = false; } deque.push_front(Entity::AddOrRetract { op: OpType::Add, e: db_id.clone(), - a: AttributePlace::Entid(entmod::EntidOrIdent::Entid(inner_a)), + a: AttributePlace::Entid(entmod::EntidOrIdent::Entid( + inner_a, + )), v: inner_v, }); } @@ -593,15 +637,15 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } in_process.entity_e_into_term_v(db_id)? - }, + } }; let e = in_process.entity_e_into_term_e(e)?; terms.push(Term::AddOrRetract(op, e, a, v)); } - }, + } } - }; + } Ok((terms, in_process.temp_ids, in_process.lookup_refs)) } @@ -609,16 +653,28 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { /// lookup refs. /// /// The `Term` instances produced share interned TempId handles and have no LookupRef references. - fn resolve_lookup_refs(&self, lookup_ref_map: &AVMap, terms: I) -> Result> where I: IntoIterator { - terms.into_iter().map(|term: TermWithTempIdsAndLookupRefs| -> Result { - match term { - Term::AddOrRetract(op, e, a, v) => { - let e = replace_lookup_ref(&lookup_ref_map, e, |x| KnownEntid(x))?; - let v = replace_lookup_ref(&lookup_ref_map, v, |x| TypedValue::Ref(x))?; - Ok(Term::AddOrRetract(op, e, a, v)) + fn resolve_lookup_refs( + &self, + lookup_ref_map: &AVMap, + terms: I, + ) -> Result> + where + I: IntoIterator, + { + terms + .into_iter() + .map( + |term: TermWithTempIdsAndLookupRefs| -> Result { + match term { + Term::AddOrRetract(op, e, a, v) => { + let e = replace_lookup_ref(&lookup_ref_map, e, |x| KnownEntid(x))?; + let v = replace_lookup_ref(&lookup_ref_map, v, |x| TypedValue::Ref(x))?; + Ok(Term::AddOrRetract(op, e, a, v)) + } + } }, - } - }).collect::>>() + ) + .collect::>>() } /// Transact the given `entities` against the store. @@ -626,26 +682,52 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { /// This approach is explained in https://github.com/mozilla/mentat/wiki/Transacting. // TODO: move this to the transactor layer. pub fn transact_entities(&mut self, entities: I) -> Result - where I: IntoIterator> { + where + I: IntoIterator>, + { // Pipeline stage 1: entities -> terms with tempids and lookup refs. - let (terms_with_temp_ids_and_lookup_refs, tempid_set, lookup_ref_set) = self.entities_into_terms_with_temp_ids_and_lookup_refs(entities)?; + let (terms_with_temp_ids_and_lookup_refs, tempid_set, lookup_ref_set) = + self.entities_into_terms_with_temp_ids_and_lookup_refs(entities)?; // Pipeline stage 2: resolve lookup refs -> terms with tempids. - let lookup_ref_avs: Vec<&(i64, TypedValue)> = lookup_ref_set.iter().map(|rc| &**rc).collect(); + let lookup_ref_avs: Vec<&(i64, TypedValue)> = + lookup_ref_set.iter().map(|rc| &**rc).collect(); let lookup_ref_map: AVMap = self.store.resolve_avs(&lookup_ref_avs[..])?; - let terms_with_temp_ids = self.resolve_lookup_refs(&lookup_ref_map, terms_with_temp_ids_and_lookup_refs)?; + let terms_with_temp_ids = + self.resolve_lookup_refs(&lookup_ref_map, terms_with_temp_ids_and_lookup_refs)?; - self.transact_simple_terms_with_action(terms_with_temp_ids, tempid_set, TransactorAction::MaterializeAndCommit) + self.transact_simple_terms_with_action( + terms_with_temp_ids, + tempid_set, + TransactorAction::MaterializeAndCommit, + ) } - pub fn transact_simple_terms(&mut self, terms: I, tempid_set: InternSet) -> Result - where I: IntoIterator { - self.transact_simple_terms_with_action(terms, tempid_set, TransactorAction::MaterializeAndCommit) + pub fn transact_simple_terms( + &mut self, + terms: I, + tempid_set: InternSet, + ) -> Result + where + I: IntoIterator, + { + self.transact_simple_terms_with_action( + terms, + tempid_set, + TransactorAction::MaterializeAndCommit, + ) } - fn transact_simple_terms_with_action(&mut self, terms: I, tempid_set: InternSet, action: TransactorAction) -> Result - where I: IntoIterator { + fn transact_simple_terms_with_action( + &mut self, + terms: I, + tempid_set: InternSet, + action: TransactorAction, + ) -> Result + where + I: IntoIterator, + { // TODO: push these into an internal transaction report? let mut tempids: BTreeMap = BTreeMap::default(); @@ -668,7 +750,8 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { generation = generation.evolve_one_step(&temp_id_map); // Errors. BTree* since we want deterministic results. - let mut conflicting_upserts: BTreeMap> = BTreeMap::default(); + let mut conflicting_upserts: BTreeMap> = + BTreeMap::default(); // Report each tempid that resolves via upsert. for (tempid, entid) in temp_id_map { @@ -676,13 +759,20 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // that a tempid resolves in two generations, and those resolutions might conflict. tempids.insert((*tempid).clone(), entid).map(|previous| { if entid != previous { - conflicting_upserts.entry((*tempid).clone()).or_insert_with(|| once(previous).collect::>()).insert(entid); + conflicting_upserts + .entry((*tempid).clone()) + .or_insert_with(|| once(previous).collect::>()) + .insert(entid); } }); } if !conflicting_upserts.is_empty() { - bail!(DbErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); + bail!(DbErrorKind::SchemaConstraintViolation( + errors::SchemaConstraintViolation::ConflictingUpserts { + conflicting_upserts + } + )); } debug!("tempids {:?}", tempids); @@ -693,12 +783,15 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { debug!("final generation {:?}", generation); // Allocate entids for tempids that didn't upsert. BTreeMap so this is deterministic. - let unresolved_temp_ids: BTreeMap = generation.temp_ids_in_allocations(&self.schema)?; + let unresolved_temp_ids: BTreeMap = + generation.temp_ids_in_allocations(&self.schema)?; debug!("unresolved tempids {:?}", unresolved_temp_ids); // TODO: track partitions for temporary IDs. - let entids = self.partition_map.allocate_entids(":db.part/user", unresolved_temp_ids.len()); + let entids = self + .partition_map + .allocate_entids(":db.part/user", unresolved_temp_ids.len()); let temp_id_allocations = unresolved_temp_ids .into_iter() @@ -724,7 +817,10 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // Any internal tempid has been allocated by the system and is a private implementation // detail; it shouldn't be exposed in the final transaction report. - let tempids = tempids.into_iter().filter_map(|(tempid, e)| tempid.into_external().map(|s| (s, e.0))).collect(); + let tempids = tempids + .into_iter() + .filter_map(|(tempid, e)| tempid.into_external().map(|s| (s, e.0))) + .collect(); // A transaction might try to add or retract :db/ident assertions or other metadata mutating // assertions , but those assertions might not make it to the store. If we see a possible @@ -737,88 +833,103 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { let mut aev_trie = into_aev_trie(&self.schema, final_populations, inert_terms)?; let tx_instant; - { // TODO: Don't use this block to scope borrowing the schema; instead, extract a helper function. + { + // TODO: Don't use this block to scope borrowing the schema; instead, extract a helper function. - // Assertions that are :db.cardinality/one and not :db.fulltext. - let mut non_fts_one: Vec = vec![]; + // Assertions that are :db.cardinality/one and not :db.fulltext. + let mut non_fts_one: Vec = vec![]; - // Assertions that are :db.cardinality/many and not :db.fulltext. - let mut non_fts_many: Vec = vec![]; + // Assertions that are :db.cardinality/many and not :db.fulltext. + let mut non_fts_many: Vec = vec![]; - // Assertions that are :db.cardinality/one and :db.fulltext. - let mut fts_one: Vec = vec![]; + // Assertions that are :db.cardinality/one and :db.fulltext. + let mut fts_one: Vec = vec![]; - // Assertions that are :db.cardinality/many and :db.fulltext. - let mut fts_many: Vec = vec![]; + // Assertions that are :db.cardinality/many and :db.fulltext. + let mut fts_many: Vec = vec![]; - // We need to ensure that callers can't blindly transact entities that haven't been - // allocated by this store. + // We need to ensure that callers can't blindly transact entities that haven't been + // allocated by this store. - let errors = tx_checking::type_disagreements(&aev_trie); - if !errors.is_empty() { - bail!(DbErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms: errors })); - } - - let errors = tx_checking::cardinality_conflicts(&aev_trie); - if !errors.is_empty() { - bail!(DbErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::CardinalityConflicts { conflicts: errors })); - } - - // Pipeline stage 4: final terms (after rewriting) -> DB insertions. - // Collect into non_fts_*. - - tx_instant = get_or_insert_tx_instant(&mut aev_trie, &self.schema, self.tx_id)?; - - for ((a, attribute), evs) in aev_trie { - if entids::might_update_metadata(a) { - tx_might_update_metadata = true; + let errors = tx_checking::type_disagreements(&aev_trie); + if !errors.is_empty() { + bail!(DbErrorKind::SchemaConstraintViolation( + errors::SchemaConstraintViolation::TypeDisagreements { + conflicting_datoms: errors + } + )); } - let queue = match (attribute.fulltext, attribute.multival) { - (false, true) => &mut non_fts_many, - (false, false) => &mut non_fts_one, - (true, false) => &mut fts_one, - (true, true) => &mut fts_many, - }; + let errors = tx_checking::cardinality_conflicts(&aev_trie); + if !errors.is_empty() { + bail!(DbErrorKind::SchemaConstraintViolation( + errors::SchemaConstraintViolation::CardinalityConflicts { conflicts: errors } + )); + } - for (e, ars) in evs { - for (added, v) in ars.add.into_iter().map(|v| (true, v)).chain(ars.retract.into_iter().map(|v| (false, v))) { - let op = match added { - true => OpType::Add, - false => OpType::Retract, - }; - self.watcher.datom(op, e, a, &v); - queue.push((e, a, attribute, v, added)); + // Pipeline stage 4: final terms (after rewriting) -> DB insertions. + // Collect into non_fts_*. + + tx_instant = get_or_insert_tx_instant(&mut aev_trie, &self.schema, self.tx_id)?; + + for ((a, attribute), evs) in aev_trie { + if entids::might_update_metadata(a) { + tx_might_update_metadata = true; + } + + let queue = match (attribute.fulltext, attribute.multival) { + (false, true) => &mut non_fts_many, + (false, false) => &mut non_fts_one, + (true, false) => &mut fts_one, + (true, true) => &mut fts_many, + }; + + for (e, ars) in evs { + for (added, v) in ars + .add + .into_iter() + .map(|v| (true, v)) + .chain(ars.retract.into_iter().map(|v| (false, v))) + { + let op = match added { + true => OpType::Add, + false => OpType::Retract, + }; + self.watcher.datom(op, e, a, &v); + queue.push((e, a, attribute, v, added)); + } } } - } - if !non_fts_one.is_empty() { - self.store.insert_non_fts_searches(&non_fts_one[..], db::SearchType::Inexact)?; - } - - if !non_fts_many.is_empty() { - self.store.insert_non_fts_searches(&non_fts_many[..], db::SearchType::Exact)?; - } - - if !fts_one.is_empty() { - self.store.insert_fts_searches(&fts_one[..], db::SearchType::Inexact)?; - } - - if !fts_many.is_empty() { - self.store.insert_fts_searches(&fts_many[..], db::SearchType::Exact)?; - } - - match action { - TransactorAction::Materialize => { - self.store.materialize_mentat_transaction(self.tx_id)?; - }, - TransactorAction::MaterializeAndCommit => { - self.store.materialize_mentat_transaction(self.tx_id)?; - self.store.commit_mentat_transaction(self.tx_id)?; + if !non_fts_one.is_empty() { + self.store + .insert_non_fts_searches(&non_fts_one[..], db::SearchType::Inexact)?; } - } + if !non_fts_many.is_empty() { + self.store + .insert_non_fts_searches(&non_fts_many[..], db::SearchType::Exact)?; + } + + if !fts_one.is_empty() { + self.store + .insert_fts_searches(&fts_one[..], db::SearchType::Inexact)?; + } + + if !fts_many.is_empty() { + self.store + .insert_fts_searches(&fts_many[..], db::SearchType::Exact)?; + } + + match action { + TransactorAction::Materialize => { + self.store.materialize_mentat_transaction(self.tx_id)?; + } + TransactorAction::MaterializeAndCommit => { + self.store.materialize_mentat_transaction(self.tx_id)?; + self.store.commit_mentat_transaction(self.tx_id)?; + } + } } self.watcher.done(&self.tx_id, self.schema)?; @@ -827,10 +938,15 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // Extract changes to metadata from the store. let metadata_assertions = match action { TransactorAction::Materialize => self.store.resolved_metadata_assertions()?, - TransactorAction::MaterializeAndCommit => db::committed_metadata_assertions(self.store, self.tx_id)? + TransactorAction::MaterializeAndCommit => { + db::committed_metadata_assertions(self.store, self.tx_id)? + } }; let mut new_schema = (*self.schema_for_mutation).clone(); // Clone the underlying Schema for modification. - let metadata_report = metadata::update_schema_from_entid_quadruples(&mut new_schema, metadata_assertions)?; + let metadata_report = metadata::update_schema_from_entid_quadruples( + &mut new_schema, + metadata_assertions, + )?; // We might not have made any changes to the schema, even though it looked like we // would. This should not happen, even during bootstrapping: we mutate an empty // `Schema` in this case specifically to run the bootstrapped assertions through the @@ -839,7 +955,12 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { if new_schema != *self.schema_for_mutation { let old_schema = (*self.schema_for_mutation).clone(); // Clone the original Schema for comparison. *self.schema_for_mutation.to_mut() = new_schema; // Store the new Schema. - db::update_metadata(self.store, &old_schema, &*self.schema_for_mutation, &metadata_report)?; + db::update_metadata( + self.store, + &old_schema, + &*self.schema_for_mutation, + &metadata_report, + )?; } } @@ -852,20 +973,36 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } /// Initialize a new Tx object with a new tx id and a tx instant. Kick off the SQLite conn, too. -fn start_tx<'conn, 'a, W>(conn: &'conn rusqlite::Connection, - mut partition_map: PartitionMap, - schema_for_mutation: &'a Schema, - schema: &'a Schema, - watcher: W) -> Result> - where W: TransactWatcher { +fn start_tx<'conn, 'a, W>( + conn: &'conn rusqlite::Connection, + mut partition_map: PartitionMap, + schema_for_mutation: &'a Schema, + schema: &'a Schema, + watcher: W, +) -> Result> +where + W: TransactWatcher, +{ let tx_id = partition_map.allocate_entid(":db.part/tx"); conn.begin_tx_application()?; - Ok(Tx::new(conn, partition_map, schema_for_mutation, schema, watcher, tx_id)) + Ok(Tx::new( + conn, + partition_map, + schema_for_mutation, + schema, + watcher, + tx_id, + )) } -fn conclude_tx(tx: Tx, report: TxReport) -> Result<(TxReport, PartitionMap, Option, W)> -where W: TransactWatcher { +fn conclude_tx( + tx: Tx, + report: TxReport, +) -> Result<(TxReport, PartitionMap, Option, W)> +where + W: TransactWatcher, +{ // If the schema has moved on, return it. let next_schema = match tx.schema_for_mutation { Cow::Borrowed(_) => None, @@ -880,63 +1017,85 @@ where W: TransactWatcher { /// /// This approach is explained in https://github.com/mozilla/mentat/wiki/Transacting. // TODO: move this to the transactor layer. -pub fn transact<'conn, 'a, I, V, W>(conn: &'conn rusqlite::Connection, - partition_map: PartitionMap, - schema_for_mutation: &'a Schema, - schema: &'a Schema, - watcher: W, - entities: I) -> Result<(TxReport, PartitionMap, Option, W)> - where I: IntoIterator>, - V: TransactableValue, - W: TransactWatcher { - +pub fn transact<'conn, 'a, I, V, W>( + conn: &'conn rusqlite::Connection, + partition_map: PartitionMap, + schema_for_mutation: &'a Schema, + schema: &'a Schema, + watcher: W, + entities: I, +) -> Result<(TxReport, PartitionMap, Option, W)> +where + I: IntoIterator>, + V: TransactableValue, + W: TransactWatcher, +{ let mut tx = start_tx(conn, partition_map, schema_for_mutation, schema, watcher)?; let report = tx.transact_entities(entities)?; conclude_tx(tx, report) } /// Just like `transact`, but accepts lower-level inputs to allow bypassing the parser interface. -pub fn transact_terms<'conn, 'a, I, W>(conn: &'conn rusqlite::Connection, - partition_map: PartitionMap, - schema_for_mutation: &'a Schema, - schema: &'a Schema, - watcher: W, - terms: I, - tempid_set: InternSet) -> Result<(TxReport, PartitionMap, Option, W)> - where I: IntoIterator, - W: TransactWatcher { - +pub fn transact_terms<'conn, 'a, I, W>( + conn: &'conn rusqlite::Connection, + partition_map: PartitionMap, + schema_for_mutation: &'a Schema, + schema: &'a Schema, + watcher: W, + terms: I, + tempid_set: InternSet, +) -> Result<(TxReport, PartitionMap, Option, W)> +where + I: IntoIterator, + W: TransactWatcher, +{ transact_terms_with_action( - conn, partition_map, schema_for_mutation, schema, watcher, terms, tempid_set, - TransactorAction::MaterializeAndCommit + conn, + partition_map, + schema_for_mutation, + schema, + watcher, + terms, + tempid_set, + TransactorAction::MaterializeAndCommit, ) } -pub(crate) fn transact_terms_with_action<'conn, 'a, I, W>(conn: &'conn rusqlite::Connection, - partition_map: PartitionMap, - schema_for_mutation: &'a Schema, - schema: &'a Schema, - watcher: W, - terms: I, - tempid_set: InternSet, - action: TransactorAction) -> Result<(TxReport, PartitionMap, Option, W)> - where I: IntoIterator, - W: TransactWatcher { - +pub(crate) fn transact_terms_with_action<'conn, 'a, I, W>( + conn: &'conn rusqlite::Connection, + partition_map: PartitionMap, + schema_for_mutation: &'a Schema, + schema: &'a Schema, + watcher: W, + terms: I, + tempid_set: InternSet, + action: TransactorAction, +) -> Result<(TxReport, PartitionMap, Option, W)> +where + I: IntoIterator, + W: TransactWatcher, +{ let mut tx = start_tx(conn, partition_map, schema_for_mutation, schema, watcher)?; let report = tx.transact_simple_terms_with_action(terms, tempid_set, action)?; conclude_tx(tx, report) } -fn extend_aev_trie<'schema, I>(schema: &'schema Schema, terms: I, trie: &mut AEVTrie<'schema>) -> Result<()> -where I: IntoIterator +fn extend_aev_trie<'schema, I>( + schema: &'schema Schema, + terms: I, + trie: &mut AEVTrie<'schema>, +) -> Result<()> +where + I: IntoIterator, { for Term::AddOrRetract(op, KnownEntid(e), a, v) in terms.into_iter() { let attribute: &Attribute = schema.require_attribute_for_entid(a)?; let a_and_r = trie - .entry((a, attribute)).or_insert(BTreeMap::default()) - .entry(e).or_insert(AddAndRetract::default()); + .entry((a, attribute)) + .or_insert(BTreeMap::default()) + .entry(e) + .or_insert(AddAndRetract::default()); match op { OpType::Add => a_and_r.add.insert(v), @@ -947,21 +1106,36 @@ where I: IntoIterator Ok(()) } -pub(crate) fn into_aev_trie<'schema>(schema: &'schema Schema, final_populations: FinalPopulations, inert_terms: Vec) -> Result> { +pub(crate) fn into_aev_trie<'schema>( + schema: &'schema Schema, + final_populations: FinalPopulations, + inert_terms: Vec, +) -> Result> { let mut trie = AEVTrie::default(); extend_aev_trie(schema, final_populations.resolved, &mut trie)?; extend_aev_trie(schema, final_populations.allocated, &mut trie)?; // Inert terms need to be unwrapped. It is a coding error if a term can't be unwrapped. - extend_aev_trie(schema, inert_terms.into_iter().map(|term| term.unwrap()), &mut trie)?; + extend_aev_trie( + schema, + inert_terms.into_iter().map(|term| term.unwrap()), + &mut trie, + )?; Ok(trie) } /// Transact [:db/add :db/txInstant tx_instant (transaction-tx)] if the trie doesn't contain it /// already. Return the instant from the input or the instant inserted. -fn get_or_insert_tx_instant<'schema>(aev_trie: &mut AEVTrie<'schema>, schema: &'schema Schema, tx_id: Entid) -> Result> { +fn get_or_insert_tx_instant<'schema>( + aev_trie: &mut AEVTrie<'schema>, + schema: &'schema Schema, + tx_id: Entid, +) -> Result> { let ars = aev_trie - .entry((entids::DB_TX_INSTANT, schema.require_attribute_for_entid(entids::DB_TX_INSTANT)?)) + .entry(( + entids::DB_TX_INSTANT, + schema.require_attribute_for_entid(entids::DB_TX_INSTANT)?, + )) .or_insert(BTreeMap::default()) .entry(tx_id) .or_insert(AddAndRetract::default()); @@ -980,6 +1154,6 @@ fn get_or_insert_tx_instant<'schema>(aev_trie: &mut AEVTrie<'schema>, schema: &' let instant = now(); ars.add.insert(instant.into()); Ok(instant) - }, + } } } diff --git a/db/src/tx_checking.rs b/db/src/tx_checking.rs index d078a2e6..f2a6ca09 100644 --- a/db/src/tx_checking.rs +++ b/db/src/tx_checking.rs @@ -8,24 +8,13 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::collections::{ - BTreeSet, - BTreeMap, -}; +use std::collections::{BTreeMap, BTreeSet}; -use core_traits::{ - Entid, - TypedValue, - ValueType, -}; +use core_traits::{Entid, TypedValue, ValueType}; -use db_traits::errors::{ - CardinalityConflict, -}; +use db_traits::errors::CardinalityConflict; -use internal_types::{ - AEVTrie, -}; +use internal_types::AEVTrie; /// Map from found [e a v] to expected type. 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 /// 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. -pub(crate) fn cardinality_conflicts<'schema>(aev_trie: &AEVTrie<'schema>) -> Vec { +pub(crate) fn cardinality_conflicts<'schema>( + aev_trie: &AEVTrie<'schema>, +) -> Vec { let mut errors = vec![]; for (&(a, attribute), evs) in aev_trie { diff --git a/db/src/tx_observer.rs b/db/src/tx_observer.rs index cc1bf9f0..e73dd0f8 100644 --- a/db/src/tx_observer.rs +++ b/db/src/tx_observer.rs @@ -8,64 +8,50 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::sync::{ - Arc, - Weak, -}; +use std::sync::{Arc, Weak}; -use std::sync::mpsc::{ - channel, - Receiver, - RecvError, - Sender, -}; +use std::sync::mpsc::{channel, Receiver, RecvError, Sender}; use std::thread; -use indexmap::{ - IndexMap, -}; +use indexmap::IndexMap; -use core_traits::{ - Entid, - TypedValue, -}; +use core_traits::{Entid, TypedValue}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::entities::{ - OpType, -}; +use edn::entities::OpType; -use db_traits::errors::{ - Result, -}; +use db_traits::errors::Result; -use types::{ - AttributeSet, -}; +use types::AttributeSet; use watcher::TransactWatcher; pub struct TxObserver { - notify_fn: Arc) + Send + Sync>>, + notify_fn: Arc) + Send + Sync>>, attributes: AttributeSet, } impl TxObserver { - pub fn new(attributes: AttributeSet, notify_fn: F) -> TxObserver where F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync { + pub fn new(attributes: AttributeSet, notify_fn: F) -> TxObserver + where + F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync, + { TxObserver { notify_fn: Arc::new(Box::new(notify_fn)), attributes, } } - pub fn applicable_reports<'r>(&self, reports: &'r IndexMap) -> IndexMap<&'r Entid, &'r AttributeSet> { - reports.into_iter() - .filter(|&(_txid, attrs)| !self.attributes.is_disjoint(attrs)) - .collect() + pub fn applicable_reports<'r>( + &self, + reports: &'r IndexMap, + ) -> 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>) { @@ -83,7 +69,10 @@ pub struct TxCommand { } impl TxCommand { - fn new(observers: &Arc>>, reports: IndexMap) -> Self { + fn new( + observers: &Arc>>, + reports: IndexMap, + ) -> Self { TxCommand { reports, observers: Arc::downgrade(observers), @@ -106,7 +95,7 @@ impl Command for TxCommand { pub struct TxObservationService { observers: Arc>>, - executor: Option>>, + executor: Option>>, } impl TxObservationService { @@ -141,7 +130,10 @@ impl TxObservationService { } let executor = self.executor.get_or_insert_with(|| { - let (tx, rx): (Sender>, Receiver>) = channel(); + let (tx, rx): ( + Sender>, + Receiver>, + ) = channel(); let mut worker = CommandExecutor::new(rx); thread::spawn(move || { @@ -182,21 +174,20 @@ impl TransactWatcher for InProgressObserverTransactWatcher { } 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); Ok(()) } } struct CommandExecutor { - receiver: Receiver>, + receiver: Receiver>, } impl CommandExecutor { - fn new(rx: Receiver>) -> Self { - CommandExecutor { - receiver: rx, - } + fn new(rx: Receiver>) -> Self { + CommandExecutor { receiver: rx } } fn main(&mut self) { @@ -207,12 +198,10 @@ impl CommandExecutor { // sync_channel) is disconnected, implying that no further messages will ever be // received." // No need to log here. - return - }, + return; + } - Ok(mut cmd) => { - cmd.execute() - }, + Ok(mut cmd) => cmd.execute(), } } } diff --git a/db/src/types.rs b/db/src/types.rs index 92ee04cb..a9519038 100644 --- a/db/src/types.rs +++ b/db/src/types.rs @@ -10,44 +10,23 @@ #![allow(dead_code)] -use std::collections::{ - BTreeMap, - BTreeSet, - HashMap, -}; -use std::iter::{ - FromIterator, -}; -use std::ops::{ - Deref, - DerefMut, - Range, -}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::iter::FromIterator; +use std::ops::{Deref, DerefMut, Range}; extern crate mentat_core; -use core_traits::{ - Entid, - TypedValue, - ValueType, -}; +use core_traits::{Entid, TypedValue, ValueType}; -pub use self::mentat_core::{ - DateTime, - Schema, - Utc, -}; +pub use self::mentat_core::{DateTime, Schema, Utc}; -use edn::entities::{ - EntityPlace, - TempId, -}; +use edn::entities::{EntityPlace, TempId}; -use db_traits::errors as errors; +use db_traits::errors; /// Represents one partition of the entid space. #[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 { /// The first entid in the partition. pub start: Entid, @@ -61,12 +40,22 @@ pub struct 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!( start <= next_entid_to_allocate && next_entid_to_allocate <= end, "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 { @@ -82,7 +71,10 @@ impl Partition { } 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; } @@ -95,7 +87,7 @@ impl Partition { /// Map partition names to `Partition` instances. #[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); impl Deref for PartitionMap { @@ -113,7 +105,7 @@ impl DerefMut for PartitionMap { } impl FromIterator<(String, Partition)> for PartitionMap { - fn from_iter>(iter: T) -> Self { + fn from_iter>(iter: T) -> Self { 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. /// /// 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 { /// Map partition name->`Partition`. /// @@ -136,7 +128,7 @@ impl DB { pub fn new(partition_map: PartitionMap, schema: Schema) -> DB { DB { partition_map: partition_map, - schema: schema + schema: schema, } } } @@ -163,7 +155,8 @@ pub type AttributeSet = BTreeSet; pub trait TransactableValue: Clone { /// 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. - fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> errors::Result; + fn into_typed_value(self, schema: &Schema, value_type: ValueType) + -> errors::Result; /// Make an entity place out of this value place. This is where we limit values in nested maps /// to valid entity places. diff --git a/db/src/upsert_resolution.rs b/db/src/upsert_resolution.rs index 2d6109bf..5ce21018 100644 --- a/db/src/upsert_resolution.rs +++ b/db/src/upsert_resolution.rs @@ -13,52 +13,31 @@ //! This module implements the upsert resolution algorithm described at //! https://github.com/mozilla/mentat/wiki/Transacting:-upsert-resolution-algorithm. -use std::collections::{ - BTreeMap, - BTreeSet, -}; +use std::collections::{BTreeMap, BTreeSet}; use indexmap; use petgraph::unionfind; -use db_traits::errors::{ - DbErrorKind, - Result, -}; -use types::{ - AVPair, -}; +use db_traits::errors::{DbErrorKind, Result}; use internal_types::{ - Population, - TempIdHandle, - TempIdMap, - Term, - TermWithoutTempIds, - TermWithTempIds, - TypedValueOr, + Population, TempIdHandle, TempIdMap, Term, TermWithTempIds, TermWithoutTempIds, TypedValueOr, }; +use types::AVPair; use mentat_core::util::Either::*; -use core_traits::{ - attribute, - Attribute, - Entid, - TypedValue, -}; +use core_traits::{attribute, Attribute, Entid, TypedValue}; -use mentat_core::{ - Schema, -}; use edn::entities::OpType; +use mentat_core::Schema; use schema::SchemaBuilding; /// 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); /// 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); /// 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 /// 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. -#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] pub(crate) struct Generation { /// "Simple upserts" that look like [:db/add TEMPID a v], where a is :db.unique/identity. upserts_e: Vec, @@ -90,7 +69,7 @@ pub(crate) struct Generation { resolved: Vec, } -#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)] pub(crate) struct FinalPopulations { /// Upserts that upserted. pub upserted: Vec, @@ -105,7 +84,10 @@ pub(crate) struct FinalPopulations { impl Generation { /// 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. - pub(crate) fn from(terms: I, schema: &Schema) -> Result<(Generation, Population)> where I: IntoIterator { + pub(crate) fn from(terms: I, schema: &Schema) -> Result<(Generation, Population)> + where + I: IntoIterator, + { let mut generation = Generation::default(); let mut inert = vec![]; @@ -120,22 +102,28 @@ impl Generation { if op == OpType::Add && is_unique(a)? { generation.upserts_ev.push(UpsertEV(e, a, v)); } 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)) => { if op == OpType::Add && is_unique(a)? { generation.upserts_e.push(UpsertE(e, a, v)); } 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)) => { - 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)) => { 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 { match temp_id_map.get(&*t) { 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 // search the store for the conflict. 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))), - (None, None) => next.upserts_ev.push(UpsertEV(t1, a, t2)) + (Some(&n1), None) => { + 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 { Term::AddOrRetract(op, Right(t1), a, Right(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))), - (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))), + (Some(&n1), Some(&n2)) => { + next.resolved + .push(Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0))) + } + (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)) => { - 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, 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))), + 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))), }, Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), } @@ -232,7 +243,11 @@ impl Generation { let mut upserts_ev = vec![]; ::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(()) } @@ -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 /// of contiguous integer labels. - pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> Result> { + pub(crate) fn temp_ids_in_allocations( + &self, + schema: &Schema, + ) -> Result> { 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!"); let mut temp_ids: BTreeSet = BTreeSet::default(); - let mut tempid_avs: BTreeMap<(Entid, TypedValueOr), Vec> = BTreeMap::default(); + let mut tempid_avs: BTreeMap<(Entid, TypedValueOr), Vec> = + BTreeMap::default(); for term in self.allocations.iter() { match term { @@ -255,24 +274,30 @@ impl Generation { temp_ids.insert(t2.clone()); let attribute: &Attribute = schema.require_attribute_for_entid(a)?; 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(_)) => { temp_ids.insert(t.clone()); let attribute: &Attribute = schema.require_attribute_for_entid(a)?; 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)) => { temp_ids.insert(t.clone()); - }, + } &Term::AddOrRetract(OpType::Add, Left(_), _, Left(_)) => unreachable!(), &Term::AddOrRetract(OpType::Retract, _, _, _) => { // [:db/retract ...] entities never allocate entids; they have to resolve due to // 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 // maintain the map from our tempids to indices ourselves. - let temp_ids: BTreeMap = temp_ids.into_iter().enumerate().map(|(i, tempid)| (tempid, i)).collect(); + let temp_ids: BTreeMap = 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() { - vs.first().and_then(|first| temp_ids.get(first)).map(|&first_index| { - for tempid in vs { - temp_ids.get(tempid).map(|&i| uf.union(first_index, i)); - } - }); + vs.first() + .and_then(|first| temp_ids.get(first)) + .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()); @@ -308,17 +342,26 @@ impl Generation { for (tempid, tempid_index) in temp_ids { let rep = uf.find_mut(tempid_index); 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) } /// After evolution is complete, use the provided allocated entids to segment `self` into /// populations, each with no references to tempids. - pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> Result { + pub(crate) fn into_final_populations( + self, + temp_id_map: &TempIdMap, + ) -> Result { assert!(self.upserts_e.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::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)) => { match (op, temp_id_map.get(&*t)) { (op, Some(&n)) => Term::AddOrRetract(op, n, a, v), (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)) => { match (op, temp_id_map.get(&*t)) { (op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)), (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. }; populations.allocated.push(allocated); diff --git a/db/src/watcher.rs b/db/src/watcher.rs index 62c535ae..d3e42eb8 100644 --- a/db/src/watcher.rs +++ b/db/src/watcher.rs @@ -17,22 +17,13 @@ // - When observers are registered we want to flip some flags as writes occur so that we can // notifying them outside the transaction. -use core_traits::{ - Entid, - TypedValue, -}; +use core_traits::{Entid, TypedValue}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::entities::{ - OpType, -}; +use edn::entities::OpType; -use db_traits::errors::{ - Result, -}; +use db_traits::errors::Result; pub trait TransactWatcher { fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue); @@ -47,8 +38,7 @@ pub trait TransactWatcher { pub struct 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<()> { Ok(()) diff --git a/db/tests/value_tests.rs b/db/tests/value_tests.rs index 995b674b..2d42cde2 100644 --- a/db/tests/value_tests.rs +++ b/db/tests/value_tests.rs @@ -8,8 +8,8 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate edn; extern crate core_traits; +extern crate edn; extern crate mentat_db; extern crate ordered_float; extern crate rusqlite; @@ -18,44 +18,100 @@ use ordered_float::OrderedFloat; use edn::symbols; -use core_traits::{ - TypedValue, - ValueType, -}; +use core_traits::{TypedValue, ValueType}; use mentat_db::db::TypedSQLValue; // It's not possible to test to_sql_value_pair since rusqlite::ToSqlOutput doesn't implement // PartialEq. #[test] 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!(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), 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!(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::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!(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::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!(TypedValue::from_sql_value_pair(rusqlite::types::Value::Text(":db/keyword".into()), 13).unwrap(), TypedValue::typed_ns_keyword("db", "keyword")); + assert_eq!( + 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] 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!(TypedValue::Boolean(true).to_edn_value_pair(), (edn::Value::Boolean(true), ValueType::Boolean)); + assert_eq!( + 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!(TypedValue::Long(1234).to_edn_value_pair(), (edn::Value::Integer(1234), ValueType::Long)); + assert_eq!( + 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!(TypedValue::Double(OrderedFloat(0.5)).to_edn_value_pair(), (edn::Value::Float(OrderedFloat(0.5)), ValueType::Double)); + assert_eq!( + 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!(TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(), (edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")), ValueType::Keyword)); + assert_eq!( + 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 + ) + ); } diff --git a/edn/Cargo.toml b/edn/Cargo.toml index f8a84597..1de50b76 100644 --- a/edn/Cargo.toml +++ b/edn/Cargo.toml @@ -11,12 +11,12 @@ build = "build.rs" readme = "./README.md" [dependencies] -chrono = "0.4" -itertools = "0.7" -num = "0.1" -ordered-float = "0.5" -pretty = "0.2" -uuid = { version = "0.5", features = ["v4", "serde"] } +chrono = "0.4.10" +itertools = "0.8.2" +num = "0.2.1" +ordered-float = "1.0.2" +pretty = "0.9.0" +uuid = { version = "0.8", features = ["v4", "serde"] } serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } diff --git a/edn/src/entities.rs b/edn/src/entities.rs index c6c27704..fb925099 100644 --- a/edn/src/entities.rs +++ b/edn/src/entities.rs @@ -13,18 +13,11 @@ use std::collections::BTreeMap; use std::fmt; -use value_rc::{ - ValueRc, -}; +use value_rc::ValueRc; -use symbols::{ - Keyword, - PlainSymbol, -}; +use symbols::{Keyword, PlainSymbol}; -use types::{ - ValueAndSpan, -}; +use types::ValueAndSpan; /// `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 diff --git a/edn/src/intern_set.rs b/edn/src/intern_set.rs index 65d4e704..f47ac0db 100644 --- a/edn/src/intern_set.rs +++ b/edn/src/intern_set.rs @@ -12,14 +12,9 @@ use std::collections::HashSet; use std::hash::Hash; -use std::ops::{ - Deref, - DerefMut, -}; +use std::ops::{Deref, DerefMut}; -use ::{ - ValueRc, -}; +use ValueRc; /// 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 @@ -29,11 +24,17 @@ use ::{ /// /// See https://en.wikipedia.org/wiki/String_interning for discussion. #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct InternSet where T: Eq + Hash { +pub struct InternSet +where + T: Eq + Hash, +{ inner: HashSet>, } -impl Deref for InternSet where T: Eq + Hash { +impl Deref for InternSet +where + T: Eq + Hash, +{ type Target = HashSet>; fn deref(&self) -> &Self::Target { @@ -41,13 +42,19 @@ impl Deref for InternSet where T: Eq + Hash { } } -impl DerefMut for InternSet where T: Eq + Hash { +impl DerefMut for InternSet +where + T: Eq + Hash, +{ fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } -impl InternSet where T: Eq + Hash { +impl InternSet +where + T: Eq + Hash, +{ pub fn new() -> InternSet { InternSet { inner: HashSet::new(), diff --git a/edn/src/lib.rs b/edn/src/lib.rs index 2d3d7037..b6361b38 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -8,6 +8,8 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +#![allow(ellipsis_inclusive_range_patterns)] + extern crate chrono; extern crate itertools; extern crate num; @@ -24,23 +26,17 @@ extern crate serde_derive; pub mod entities; pub mod intern_set; -pub use intern_set::{ - InternSet, -}; +pub use intern_set::InternSet; // Intentionally not pub. +pub mod matcher; mod namespaceable_name; +pub mod pretty_print; pub mod query; pub mod symbols; pub mod types; -pub mod pretty_print; pub mod utils; -pub mod matcher; pub mod value_rc; -pub use value_rc::{ - Cloned, - FromRc, - ValueRc, -}; +pub use value_rc::{Cloned, FromRc, ValueRc}; pub mod parse { include!(concat!(env!("OUT_DIR"), "/edn.rs")); @@ -54,20 +50,8 @@ pub use uuid::Uuid; // Export from our modules. pub use parse::ParseError; -pub use uuid::ParseError as UuidParseError; pub use types::{ - FromMicros, - FromMillis, - Span, - SpannedValue, - ToMicros, - ToMillis, - Value, - ValueAndSpan, + FromMicros, FromMillis, Span, SpannedValue, ToMicros, ToMillis, Value, ValueAndSpan, }; -pub use symbols::{ - Keyword, - NamespacedSymbol, - PlainSymbol, -}; +pub use symbols::{Keyword, NamespacedSymbol, PlainSymbol}; diff --git a/edn/src/matcher.rs b/edn/src/matcher.rs index 8d462045..f17b7d52 100644 --- a/edn/src/matcher.rs +++ b/edn/src/matcher.rs @@ -8,9 +8,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::collections::HashMap; -use std::cell::RefCell; use itertools::diff_with; +use std::cell::RefCell; +use std::collections::HashMap; use symbols; use types::Value; @@ -21,7 +21,7 @@ trait PatternMatchingRules<'a, T> { fn matches_any(pattern: &T) -> bool; /// 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 @@ -34,14 +34,20 @@ impl<'a> PatternMatchingRules<'a, Value> for DefaultPatternMatchingRules { fn matches_any(pattern: &Value) -> bool { match *pattern { 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 { - Value::PlainSymbol(symbols::PlainSymbol(ref s)) => if s.starts_with('?') { Some(s) } else { None }, - _ => None + Value::PlainSymbol(symbols::PlainSymbol(ref s)) => { + 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; /// * `[?x ?x]` matches `[1 1]` and `[#{} #{}]` but not `[1 2]` or `[[] #{}]`; struct Matcher<'a> { - placeholders: RefCell> + placeholders: RefCell>, } impl<'a> Matcher<'a> { /// Creates a Matcher instance. fn new() -> Matcher<'a> { Matcher { - placeholders: RefCell::default() + placeholders: RefCell::default(), } } @@ -67,7 +73,9 @@ impl<'a> Matcher<'a> { /// and `pattern`) utilizing a specified pattern matching ruleset `T`. /// Returns true if matching succeeds. fn match_with_rules(value: &'a Value, pattern: &'a Value) -> bool - where T: PatternMatchingRules<'a, Value> { + where + T: PatternMatchingRules<'a, Value>, + { let matcher = Matcher::new(); matcher.match_internal::(value, pattern) } @@ -76,7 +84,9 @@ impl<'a> Matcher<'a> { /// performing pattern matching. Note that the internal `placeholders` cache /// might not be empty on invocation. fn match_internal(&self, value: &'a Value, pattern: &'a Value) -> bool - where T: PatternMatchingRules<'a, Value> { + where + T: PatternMatchingRules<'a, Value>, + { use Value::*; if T::matches_any(pattern) { @@ -86,19 +96,35 @@ impl<'a> Matcher<'a> { value == *placeholders.entry(symbol).or_insert(value) } else { match (value, pattern) { - (&Vector(ref v), &Vector(ref p)) => - diff_with(v, p, |a, b| self.match_internal::(a, b)).is_none(), - (&List(ref v), &List(ref p)) => - diff_with(v, p, |a, b| self.match_internal::(a, b)).is_none(), - (&Set(ref v), &Set(ref p)) => - v.len() == p.len() && - v.iter().all(|a| p.iter().any(|b| self.match_internal::(a, b))) && - p.iter().all(|b| v.iter().any(|a| self.match_internal::(a, b))), - (&Map(ref v), &Map(ref p)) => - v.len() == p.len() && - v.iter().all(|a| p.iter().any(|b| self.match_internal::(a.0, b.0) && self.match_internal::(a.1, b.1))) && - p.iter().all(|b| v.iter().any(|a| self.match_internal::(a.0, b.0) && self.match_internal::(a.1, b.1))), - _ => value == pattern + (&Vector(ref v), &Vector(ref p)) => { + diff_with(v, p, |a, b| self.match_internal::(a, b)).is_none() + } + (&List(ref v), &List(ref p)) => { + diff_with(v, p, |a, b| self.match_internal::(a, b)).is_none() + } + (&Set(ref v), &Set(ref p)) => { + v.len() == p.len() + && v.iter() + .all(|a| p.iter().any(|b| self.match_internal::(a, b))) + && p.iter() + .all(|b| v.iter().any(|a| self.match_internal::(a, b))) + } + (&Map(ref v), &Map(ref p)) => { + v.len() == p.len() + && v.iter().all(|a| { + p.iter().any(|b| { + self.match_internal::(a.0, b.0) + && self.match_internal::(a.1, b.1) + }) + }) + && p.iter().all(|b| { + v.iter().any(|a| { + self.match_internal::(a.0, b.0) + && self.match_internal::(a.1, b.1) + }) + }) + } + _ => value == pattern, } } } @@ -127,7 +153,7 @@ mod test { }; ( $pattern:tt !~ $value:tt ) => { assert_match!($pattern, $value, false); - } + }; } #[test] diff --git a/edn/src/namespaceable_name.rs b/edn/src/namespaceable_name.rs index e072c7cc..892f9af6 100644 --- a/edn/src/namespaceable_name.rs +++ b/edn/src/namespaceable_name.rs @@ -8,25 +8,14 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::cmp::{ - Ord, - Ordering, - PartialOrd, -}; +use std::cmp::{Ord, Ordering, PartialOrd}; use std::fmt; #[cfg(feature = "serde_support")] -use serde::de::{ - self, - Deserialize, - Deserializer -}; +use serde::de::{self, Deserialize, Deserializer}; #[cfg(feature = "serde_support")] -use serde::ser::{ - Serialize, - Serializer, -}; +use serde::ser::{Serialize, Serializer}; // Data storage for both NamespaceableKeyword and NamespaceableSymbol. #[derive(Clone, Eq, Hash, PartialEq)] @@ -56,7 +45,10 @@ pub struct NamespaceableName { impl NamespaceableName { #[inline] - pub fn plain(name: T) -> Self where T: Into { + pub fn plain(name: T) -> Self + where + T: Into, + { let n = name.into(); assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed."); @@ -67,14 +59,21 @@ impl NamespaceableName { } #[inline] - pub fn namespaced(namespace: N, name: T) -> Self where N: AsRef, T: AsRef { + pub fn namespaced(namespace: N, name: T) -> Self + where + N: AsRef, + T: AsRef, + { let n = name.as_ref(); let ns = namespace.as_ref(); // Note: These invariants are not required for safety. That is, if we // decide to allow these we can safely remove them. 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()); @@ -90,7 +89,11 @@ impl NamespaceableName { } } - fn new(namespace: Option, name: T) -> Self where N: AsRef, T: AsRef { + fn new(namespace: Option, name: T) -> Self + where + N: AsRef, + T: AsRef, + { if let Some(ns) = namespace { Self::namespaced(ns, name) } else { @@ -143,11 +146,12 @@ impl NamespaceableName { #[inline] pub fn components<'a>(&'a self) -> (&'a str, &'a str) { 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 { - (&self.components[0..0], - &self.components) + (&self.components[0..0], &self.components) } } } @@ -163,7 +167,7 @@ impl PartialOrd for NamespaceableName { (_, _) => { // Just use a lexicographic ordering. self.components().partial_cmp(&other.components()) - }, + } } } } @@ -178,9 +182,9 @@ impl Ord for NamespaceableName { impl fmt::Debug for NamespaceableName { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("NamespaceableName") - .field("namespace", &self.namespace()) - .field("name", &self.name()) - .finish() + .field("namespace", &self.namespace()) + .field("name", &self.name()) + .finish() } } @@ -210,14 +214,19 @@ struct SerializedNamespaceableName<'a> { #[cfg(feature = "serde_support")] impl<'de> Deserialize<'de> for NamespaceableName { - fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { let separated = SerializedNamespaceableName::deserialize(deserializer)?; if separated.name.len() == 0 { return Err(de::Error::custom("Empty name in keyword or symbol")); } if let Some(ns) = separated.namespace { 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 { Ok(NamespaceableName::namespaced(ns, separated.name)) } @@ -229,7 +238,10 @@ impl<'de> Deserialize<'de> for NamespaceableName { #[cfg(feature = "serde_support")] impl Serialize for NamespaceableName { - fn serialize(&self, serializer: S) -> Result where S: Serializer { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { let ser = SerializedNamespaceableName { namespace: self.namespace(), name: self.name(), @@ -245,12 +257,18 @@ mod test { #[test] fn test_new_invariants_maintained() { - assert!(panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(), - "Empty namespace should panic"); - assert!(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"); + assert!( + panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(), + "Empty namespace should panic" + ); + assert!( + 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] @@ -286,19 +304,22 @@ mod test { n3.clone(), n2.clone(), n1.clone(), - n4.clone() + n4.clone(), ]; arr.sort(); - assert_eq!(arr, [ - n0.clone(), - n2.clone(), - n1.clone(), - n3.clone(), - n4.clone(), - n5.clone(), - n6.clone(), - ]); + assert_eq!( + arr, + [ + n0.clone(), + n2.clone(), + n1.clone(), + n3.clone(), + n4.clone(), + n5.clone(), + n6.clone(), + ] + ); } } diff --git a/edn/src/pretty_print.rs b/edn/src/pretty_print.rs index b7a73cae..840240ef 100644 --- a/edn/src/pretty_print.rs +++ b/edn/src/pretty_print.rs @@ -8,15 +8,13 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use chrono::{ - SecondsFormat, -}; +use chrono::SecondsFormat; use itertools::Itertools; use pretty; -use std::io; use std::borrow::Cow; +use std::io; use types::Value; @@ -29,7 +27,10 @@ impl Value { } /// Write a pretty representation of this `Value` to the given writer. - fn write_pretty(&self, width: usize, out: &mut W) -> Result<(), io::Error> where W: io::Write { + fn write_pretty(&self, width: usize, out: &mut W) -> Result<(), io::Error> + where + W: io::Write, + { self.as_doc(&pretty::BoxAllocator).1.render(width, out) } @@ -41,28 +42,50 @@ impl Value { /// [1, /// 2, /// 3]. - fn bracket<'a, A, T, I>(&'a self, allocator: &'a A, open: T, vs: I, close: T) -> pretty::DocBuilder<'a, A> - where A: pretty::DocAllocator<'a>, T: Into>, I: IntoIterator { + fn bracket<'a, A, T, I>( + &'a self, + allocator: &'a A, + open: T, + vs: I, + close: T, + ) -> pretty::DocBuilder<'a, A> + where + A: pretty::DocAllocator<'a>, + >::Doc: std::clone::Clone, + T: Into>, + I: IntoIterator, + { let open = open.into(); - let n = open.len(); - let i = vs.into_iter().map(|v| v.as_doc(allocator)).intersperse(allocator.space()); - allocator.text(open) + let n = open.len() as isize; + let i = vs + .into_iter() + .map(|v| v.as_doc(allocator)) + .intersperse(allocator.space()); + allocator + .text(open) .append(allocator.concat(i).nest(n)) .append(allocator.text(close)) .group() } /// 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. fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A> - where A: pretty::DocAllocator<'a> { + where + A: pretty::DocAllocator<'a>, + >::Doc: std::clone::Clone, + { match *self { Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"), Value::List(ref vs) => self.bracket(pp, "(", vs, ")"), Value::Set(ref vs) => self.bracket(pp, "#{", 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("{") .append(pp.concat(xs).nest(1)) .append(pp.text("}")) @@ -72,9 +95,15 @@ impl Value { Value::PlainSymbol(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::Uuid(ref u) => pp.text("#uuid \"").append(u.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()) + Value::Uuid(ref u) => pp + .text("#uuid \"") + .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(); 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 2 3 4 5 - 6]"); + 6]" + ); } #[test] @@ -120,10 +152,13 @@ mod test { 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(10).unwrap(), "\ + assert_eq!( + data.to_pretty(10).unwrap(), + "\ {:a 1 :b 2 - :c 3}"); + :c 3}" + ); } #[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 data = parse::value(string).unwrap().without_spans(); - assert_eq!(data.to_pretty(40).unwrap(), "\ + assert_eq!( + data.to_pretty(40).unwrap(), + "\ [1 2 (3.14) @@ -147,7 +184,8 @@ mod test { nil #f NaN #f -Infinity - #f +Infinity]"); + #f +Infinity]" + ); } #[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 data = parse::value(string).unwrap().without_spans(); - assert_eq!(data.to_pretty(40).unwrap(), "\ + assert_eq!( + data.to_pretty(40).unwrap(), + "\ [:find ?id ?bar @@ -168,7 +208,8 @@ mod test { ?symbol1 ?symbol2 \"some string\"] - [?tx :db/tx ?ts]]"); + [?tx :db/tx ?ts]]" + ); } #[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 data = parse::value(string).unwrap().without_spans(); - assert_eq!(data.to_pretty(40).unwrap(), "\ + assert_eq!( + data.to_pretty(40).unwrap(), + "\ [:find [?id ?bar ?baz] :in @@ -190,6 +233,7 @@ mod test { [?tx :db/tx ?ts] (not-join [?id] - [?id :session/keyword-bar _])]"); + [?id :session/keyword-bar _])]" + ); } } diff --git a/edn/src/query.rs b/edn/src/query.rs index e4ba9f43..0071357e 100644 --- a/edn/src/query.rs +++ b/edn/src/query.rs @@ -29,37 +29,19 @@ ///! 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 ///! 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::fmt; -use std::rc::{ - Rc, -}; +use std::rc::Rc; -use ::{ - BigInt, - DateTime, - OrderedFloat, - Uuid, - Utc, -}; +use {BigInt, DateTime, OrderedFloat, Utc, Uuid}; -use ::value_rc::{ - FromRc, - ValueRc, -}; +use value_rc::{FromRc, ValueRc}; -pub use ::{ - Keyword, - PlainSymbol, -}; +pub use {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)] pub struct Variable(pub Rc); @@ -167,7 +149,7 @@ pub enum Direction { /// An abstract declaration of ordering: direction and variable. #[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)] pub enum SrcVar { @@ -236,36 +218,26 @@ pub enum FnArg { impl FromValue for FnArg { fn from_value(v: &::ValueAndSpan) -> Option { - use ::SpannedValue::*; + use SpannedValue::*; match v.inner { - Integer(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_var_symbol() => - Variable::from_symbol(x).map(FnArg::Variable), + Integer(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_var_symbol() => { + Variable::from_symbol(x).map(FnArg::Variable) + } PlainSymbol(_) => None, - Keyword(ref x) => - Some(FnArg::IdentOrKeyword(x.clone())), - Instant(x) => - Some(FnArg::Constant(NonIntegerConstant::Instant(x))), - Uuid(x) => - Some(FnArg::Constant(NonIntegerConstant::Uuid(x))), - 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()))), + Keyword(ref x) => Some(FnArg::IdentOrKeyword(x.clone())), + Instant(x) => Some(FnArg::Constant(NonIntegerConstant::Instant(x))), + Uuid(x) => Some(FnArg::Constant(NonIntegerConstant::Uuid(x))), + 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) => - // TODO: intern strings. #398. - Some(FnArg::Constant(x.clone().into())), - Nil | - NamespacedSymbol(_) | - Vector(_) | - List(_) | - Set(_) | - Map(_) => None, + // TODO: intern strings. #398. + { + Some(FnArg::Constant(x.clone().into())) + } + Nil | NamespacedSymbol(_) | Vector(_) | List(_) | Set(_) | Map(_) => None, } } } @@ -281,7 +253,7 @@ impl std::fmt::Display for FnArg { } else { write!(f, "{:?}", var) } - }, + } &FnArg::EntidOrInteger(entid) => write!(f, "{}", entid), &FnArg::IdentOrKeyword(ref kw) => write!(f, "{}", kw), &FnArg::Constant(ref constant) => write!(f, "{:?}", constant), @@ -309,7 +281,7 @@ impl FnArg { pub enum PatternNonValuePlace { Placeholder, Variable(Variable), - Entid(i64), // Will always be +ve. See #190. + Entid(i64), // Will always be +ve. See #190. Ident(ValueRc), } @@ -332,17 +304,17 @@ impl PatternNonValuePlace { match self { PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, PatternNonValuePlace::Variable(x) => PatternValuePlace::Variable(x), - PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), - PatternNonValuePlace::Ident(x) => PatternValuePlace::IdentOrKeyword(x), + PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), + PatternNonValuePlace::Ident(x) => PatternValuePlace::IdentOrKeyword(x), } } fn to_pattern_value_place(&self) -> PatternValuePlace { match *self { - PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, + PatternNonValuePlace::Placeholder => PatternValuePlace::Placeholder, PatternNonValuePlace::Variable(ref x) => PatternValuePlace::Variable(x.clone()), - PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), - PatternNonValuePlace::Ident(ref x) => PatternValuePlace::IdentOrKeyword(x.clone()), + PatternNonValuePlace::Entid(x) => PatternValuePlace::EntidOrInteger(x), + PatternNonValuePlace::Ident(ref x) => PatternValuePlace::IdentOrKeyword(x.clone()), } } } @@ -350,22 +322,25 @@ impl PatternNonValuePlace { impl FromValue for PatternNonValuePlace { fn from_value(v: &::ValueAndSpan) -> Option { match v.inner { - ::SpannedValue::Integer(x) => if x >= 0 { - Some(PatternNonValuePlace::Entid(x)) - } else { - 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)) + ::SpannedValue::Integer(x) => { + if x >= 0 { + Some(PatternNonValuePlace::Entid(x)) } else { None } - }, - ::SpannedValue::Keyword(ref x) => - Some(x.clone().into()), + } + ::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 { + None + } + } + } + ::SpannedValue::Keyword(ref x) => Some(x.clone().into()), _ => None, } } @@ -404,32 +379,39 @@ impl From for PatternValuePlace { impl FromValue for PatternValuePlace { fn from_value(v: &::ValueAndSpan) -> Option { match v.inner { - ::SpannedValue::Integer(x) => - Some(PatternValuePlace::EntidOrInteger(x)), - ::SpannedValue::PlainSymbol(ref x) if x.0.as_str() == "_" => - Some(PatternValuePlace::Placeholder), - ::SpannedValue::PlainSymbol(ref x) => - Variable::from_symbol(x).map(PatternValuePlace::Variable), - ::SpannedValue::Keyword(ref x) if x.is_namespaced() => - Some(x.clone().into()), - ::SpannedValue::Boolean(x) => - Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x))), - ::SpannedValue::Float(x) => - Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))), - ::SpannedValue::BigInteger(ref x) => - Some(PatternValuePlace::Constant(NonIntegerConstant::BigInteger(x.clone()))), - ::SpannedValue::Instant(x) => - Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))), + ::SpannedValue::Integer(x) => Some(PatternValuePlace::EntidOrInteger(x)), + ::SpannedValue::PlainSymbol(ref x) if x.0.as_str() == "_" => { + Some(PatternValuePlace::Placeholder) + } + ::SpannedValue::PlainSymbol(ref x) => { + Variable::from_symbol(x).map(PatternValuePlace::Variable) + } + ::SpannedValue::Keyword(ref x) if x.is_namespaced() => Some(x.clone().into()), + ::SpannedValue::Boolean(x) => { + Some(PatternValuePlace::Constant(NonIntegerConstant::Boolean(x))) + } + ::SpannedValue::Float(x) => { + Some(PatternValuePlace::Constant(NonIntegerConstant::Float(x))) + } + ::SpannedValue::BigInteger(ref x) => Some(PatternValuePlace::Constant( + NonIntegerConstant::BigInteger(x.clone()), + )), + ::SpannedValue::Instant(x) => { + Some(PatternValuePlace::Constant(NonIntegerConstant::Instant(x))) + } ::SpannedValue::Text(ref x) => - // TODO: intern strings. #398. - Some(PatternValuePlace::Constant(x.clone().into())), - ::SpannedValue::Uuid(ref u) => - Some(PatternValuePlace::Constant(NonIntegerConstant::Uuid(u.clone()))), + // TODO: intern strings. #398. + { + Some(PatternValuePlace::Constant(x.clone().into())) + } + ::SpannedValue::Uuid(ref u) => Some(PatternValuePlace::Constant( + NonIntegerConstant::Uuid(u.clone()), + )), // These don't appear in queries. ::SpannedValue::Nil => None, ::SpannedValue::NamespacedSymbol(_) => None, - ::SpannedValue::Keyword(_) => None, // … yet. + ::SpannedValue::Keyword(_) => None, // … yet. ::SpannedValue::Map(_) => None, ::SpannedValue::List(_) => None, ::SpannedValue::Set(_) => None, @@ -443,29 +425,35 @@ impl PatternValuePlace { #[allow(dead_code)] fn into_pattern_non_value_place(self) -> Option { match self { - PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), - PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)), - PatternValuePlace::EntidOrInteger(x) => if x >= 0 { - Some(PatternNonValuePlace::Entid(x)) - } else { - None - }, + PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), + PatternValuePlace::Variable(x) => Some(PatternNonValuePlace::Variable(x)), + PatternValuePlace::EntidOrInteger(x) => { + if x >= 0 { + Some(PatternNonValuePlace::Entid(x)) + } else { + None + } + } PatternValuePlace::IdentOrKeyword(x) => Some(PatternNonValuePlace::Ident(x)), - PatternValuePlace::Constant(_) => None, + PatternValuePlace::Constant(_) => None, } } fn to_pattern_non_value_place(&self) -> Option { match *self { - PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), - PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())), - PatternValuePlace::EntidOrInteger(x) => if x >= 0 { - Some(PatternNonValuePlace::Entid(x)) - } else { - None - }, - PatternValuePlace::IdentOrKeyword(ref x) => Some(PatternNonValuePlace::Ident(x.clone())), - PatternValuePlace::Constant(_) => None, + PatternValuePlace::Placeholder => Some(PatternNonValuePlace::Placeholder), + PatternValuePlace::Variable(ref x) => Some(PatternNonValuePlace::Variable(x.clone())), + PatternValuePlace::EntidOrInteger(x) => { + if x >= 0 { + Some(PatternNonValuePlace::Entid(x)) + } else { + 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 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - &PullConcreteAttribute::Ident(ref k) => { - write!(f, "{}", k) - }, - &PullConcreteAttribute::Entid(i) => { - write!(f, "{}", i) - }, + &PullConcreteAttribute::Ident(ref k) => write!(f, "{}", k), + &PullConcreteAttribute::Entid(i) => write!(f, "{}", i), } } } @@ -530,21 +514,15 @@ impl std::fmt::Display for NamedPullAttribute { } } - impl std::fmt::Display for PullAttributeSpec { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - &PullAttributeSpec::Wildcard => { - write!(f, "*") - }, - &PullAttributeSpec::Attribute(ref attr) => { - write!(f, "{}", attr) - }, + &PullAttributeSpec::Wildcard => write!(f, "*"), + &PullAttributeSpec::Attribute(ref attr) => write!(f, "{}", attr), } } } - #[derive(Clone, Debug, Eq, PartialEq)] pub struct Pull { pub var: Variable, @@ -592,26 +570,23 @@ impl From for Element { impl std::fmt::Display for Element { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - &Element::Variable(ref var) => { - write!(f, "{}", var) - }, - &Element::Pull(Pull { ref var, ref patterns }) => { + &Element::Variable(ref var) => write!(f, "{}", var), + &Element::Pull(Pull { + ref var, + ref patterns, + }) => { write!(f, "(pull {} [ ", var)?; for p in patterns.iter() { write!(f, "{} ", p)?; } 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) => { - 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) - }, + &Element::Corresponding(ref var) => write!(f, "(the {})", var), } } } @@ -675,9 +650,9 @@ impl FindSpec { use self::FindSpec::*; match self { &FindScalar(..) => true, - &FindTuple(..) => true, - &FindRel(..) => false, - &FindColl(..) => false, + &FindTuple(..) => true, + &FindRel(..) => false, + &FindColl(..) => false, } } @@ -685,12 +660,11 @@ impl FindSpec { use self::FindSpec::*; match self { &FindScalar(..) => 1, - &FindColl(..) => 1, + &FindColl(..) => 1, &FindTuple(ref elems) | &FindRel(ref elems) => elems.len(), } } - /// 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 @@ -713,13 +687,13 @@ impl FindSpec { !self.is_unit_limited() } - pub fn columns<'s>(&'s self) -> Box + 's> { + pub fn columns<'s>(&'s self) -> Box + 's> { use self::FindSpec::*; match self { &FindScalar(ref e) => Box::new(std::iter::once(e)), - &FindColl(ref e) => Box::new(std::iter::once(e)), - &FindTuple(ref v) => Box::new(v.iter()), - &FindRel(ref v) => Box::new(v.iter()), + &FindColl(ref e) => Box::new(std::iter::once(e)), + &FindTuple(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 { BindScalar(Variable), BindColl(Variable), @@ -761,7 +735,9 @@ impl Binding { pub fn variables(&self) -> Vec> { match self { &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 { match self { &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 { - pub fn simple(e: PatternNonValuePlace, - a: PatternNonValuePlace, - v: PatternValuePlace) -> Option { + pub fn simple( + e: PatternNonValuePlace, + a: PatternNonValuePlace, + v: PatternValuePlace, + ) -> Option { Pattern::new(None, e, a, v, PatternNonValuePlace::Placeholder) } - pub fn new(src: Option, - e: PatternNonValuePlace, - a: PatternNonValuePlace, - v: PatternValuePlace, - tx: PatternNonValuePlace) -> Option { - let aa = a.clone(); // Too tired of fighting borrow scope for now. + pub fn new( + src: Option, + e: PatternNonValuePlace, + a: PatternNonValuePlace, + v: PatternValuePlace, + tx: PatternNonValuePlace, + ) -> Option { + let aa = a.clone(); // Too tired of fighting borrow scope for now. if let PatternNonValuePlace::Ident(ref k) = aa { if k.is_backward() { // 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 /// (here, `Vec` and `BTreeSet`) in Rust. impl ParsedQuery { - pub(crate) fn from_parts(parts: Vec) -> std::result::Result { + pub(crate) fn from_parts( + parts: Vec, + ) -> std::result::Result { let mut find_spec: Option = None; let mut with: Option> = None; let mut in_vars: Option> = None; @@ -1020,37 +1004,37 @@ impl ParsedQuery { return Err("find query has repeated :find"); } find_spec = Some(x) - }, + } QueryPart::WithVars(x) => { if with.is_some() { return Err("find query has repeated :with"); } with = Some(x) - }, + } QueryPart::InVars(x) => { if in_vars.is_some() { return Err("find query has repeated :in"); } in_vars = Some(x) - }, + } QueryPart::Limit(x) => { if limit.is_some() { return Err("find query has repeated :limit"); } limit = Some(x) - }, + } QueryPart::WhereClauses(x) => { if where_clauses.is_some() { return Err("find query has repeated :where"); } where_clauses = Some(x) - }, + } QueryPart::Order(x) => { if order.is_some() { return Err("find query has repeated :order"); } order = Some(x) - }, + } } } @@ -1110,13 +1094,13 @@ impl ContainsVariables for WhereClause { fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet) { use self::WhereClause::*; match self { - &OrJoin(ref o) => o.accumulate_mentioned_variables(acc), - &Pred(ref p) => p.accumulate_mentioned_variables(acc), - &Pattern(ref p) => p.accumulate_mentioned_variables(acc), - &NotJoin(ref n) => n.accumulate_mentioned_variables(acc), - &WhereFn(ref f) => f.accumulate_mentioned_variables(acc), + &OrJoin(ref o) => o.accumulate_mentioned_variables(acc), + &Pred(ref p) => p.accumulate_mentioned_variables(acc), + &Pattern(ref p) => p.accumulate_mentioned_variables(acc), + &NotJoin(ref n) => n.accumulate_mentioned_variables(acc), + &WhereFn(ref f) => f.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) { use self::OrWhereClause::*; 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), } } @@ -1142,9 +1130,9 @@ impl ContainsVariables for OrJoin { impl OrJoin { pub fn dismember(self) -> (Vec, UnifyVars, BTreeSet) { let vars = match self.mentioned_vars { - Some(m) => m, - None => self.collect_mentioned_variables(), - }; + Some(m) => m, + None => self.collect_mentioned_variables(), + }; (self.clauses, self.unify_vars, vars) } @@ -1189,16 +1177,14 @@ impl ContainsVariables for TypeAnnotation { impl ContainsVariables for Binding { fn accumulate_mentioned_variables(&self, acc: &mut BTreeSet) { match self { - &Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => { - acc_ref(acc, v) - }, + &Binding::BindScalar(ref v) | &Binding::BindColl(ref v) => acc_ref(acc, v), &Binding::BindRel(ref vs) | &Binding::BindTuple(ref vs) => { for v in vs { if let &VariableOrPlaceholder::Variable(ref v) = v { acc_ref(acc, v); } } - }, + } } } } diff --git a/edn/src/symbols.rs b/edn/src/symbols.rs index 95ba3c81..8a60b382 100644 --- a/edn/src/symbols.rs +++ b/edn/src/symbols.rs @@ -8,11 +8,7 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::fmt::{ - Display, - Formatter, - Write, -}; +use std::fmt::{Display, Formatter, Write}; use namespaceable_name::NamespaceableName; @@ -20,14 +16,14 @@ use namespaceable_name::NamespaceableName; macro_rules! ns_keyword { ($ns: expr, $name: expr) => {{ $crate::Keyword::namespaced($ns, $name) - }} + }}; } /// 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); -#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] pub struct NamespacedSymbol(NamespaceableName); /// 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. /// -#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] pub struct Keyword(NamespaceableName); impl PlainSymbol { - pub fn plain(name: T) -> Self where T: Into { + pub fn plain(name: T) -> Self + where + T: Into, + { let n = name.into(); assert!(!n.is_empty(), "Symbols cannot be unnamed."); @@ -107,9 +106,16 @@ impl PlainSymbol { } impl NamespacedSymbol { - pub fn namespaced(namespace: N, name: T) -> Self where N: AsRef, T: AsRef { + pub fn namespaced(namespace: N, name: T) -> Self + where + N: AsRef, + T: AsRef, + { 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)) } @@ -130,7 +136,10 @@ impl NamespacedSymbol { } impl Keyword { - pub fn plain(name: T) -> Self where T: Into { + pub fn plain(name: T) -> Self + where + T: Into, + { Keyword(NamespaceableName::plain(name)) } } @@ -147,9 +156,16 @@ impl Keyword { /// ``` /// /// See also the `kw!` macro in the main `mentat` crate. - pub fn namespaced(namespace: N, name: T) -> Self where N: AsRef, T: AsRef { + pub fn namespaced(namespace: N, name: T) -> Self + where + N: AsRef, + T: AsRef, + { 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)) } @@ -307,8 +323,12 @@ impl Display for Keyword { #[test] fn test_ns_keyword_macro() { - assert_eq!(ns_keyword!("test", "name").to_string(), - Keyword::namespaced("test", "name").to_string()); - assert_eq!(ns_keyword!("ns", "_name").to_string(), - Keyword::namespaced("ns", "_name").to_string()); + assert_eq!( + ns_keyword!("test", "name").to_string(), + Keyword::namespaced("test", "name").to_string() + ); + assert_eq!( + ns_keyword!("ns", "_name").to_string(), + Keyword::namespaced("ns", "_name").to_string() + ); } diff --git a/edn/src/types.rs b/edn/src/types.rs index 51319db1..d5dd6cba 100644 --- a/edn/src/types.rs +++ b/edn/src/types.rs @@ -8,17 +8,17 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // 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::{Ordering, Ord, PartialOrd}; -use std::fmt::{Display, Formatter}; +use std::cmp::{Ord, Ordering, PartialOrd}; +use std::collections::{BTreeMap, BTreeSet, LinkedList}; use std::f64; +use std::fmt::{Display, Formatter}; use chrono::{ DateTime, 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, }; use num::BigInt; @@ -94,7 +94,10 @@ pub struct ValueAndSpan { } impl ValueAndSpan { - pub fn new(spanned_value: SpannedValue, span: I) -> ValueAndSpan where I: Into> { + pub fn new(spanned_value: SpannedValue, span: I) -> ValueAndSpan + where + I: Into>, + { ValueAndSpan { inner: spanned_value, 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. pub fn with_spans(self) -> ValueAndSpan { let s = self.to_pretty(120).unwrap(); - use ::parse; + use parse; let with_spans = parse::value(&s).unwrap(); assert_eq!(self, with_spans.clone().without_spans()); with_spans @@ -157,10 +160,18 @@ impl From for Value { SpannedValue::PlainSymbol(v) => Value::PlainSymbol(v), SpannedValue::NamespacedSymbol(v) => Value::NamespacedSymbol(v), SpannedValue::Keyword(v) => Value::Keyword(v), - SpannedValue::Vector(v) => 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::Vector(v) => { + 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::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.into().map_or_else( || $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 @@ -290,8 +302,9 @@ macro_rules! to_keyword { ( $namespace:expr, $name:expr, $t:tt ) => { $namespace.into().map_or_else( || $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 @@ -508,9 +521,9 @@ macro_rules! def_common_value_ord { (&$t::List(ref a), &$t::List(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), - _ => $value.precedence().cmp(&$other.precedence()) + _ => $value.precedence().cmp(&$other.precedence()), } - } + }; } /// 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::Boolean(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), // TODO: make sure float syntax is correct. $t::Float(ref v) => { @@ -538,7 +555,7 @@ macro_rules! def_common_value_display { } // TODO: EDN escaping. $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::NamespacedSymbol(ref v) => v.fmt($f), $t::Keyword(ref v) => v.fmt($f), @@ -571,7 +588,7 @@ macro_rules! def_common_value_display { write!($f, " }}") } } - } + }; } 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_impl!(Value); @@ -674,22 +691,19 @@ impl ToMillis for DateTime { #[cfg(test)] mod test { extern crate chrono; - extern crate ordered_float; extern crate num; + extern crate ordered_float; use super::*; - use std::collections::{BTreeSet, BTreeMap, LinkedList}; - use std::cmp::{Ordering}; - use std::iter::FromIterator; + use std::cmp::Ordering; + use std::collections::{BTreeMap, BTreeSet, LinkedList}; use std::f64; + use std::iter::FromIterator; use parse; - use chrono::{ - DateTime, - Utc, - }; + use chrono::{DateTime, Utc}; use num::BigInt; use ordered_float::OrderedFloat; @@ -702,9 +716,18 @@ mod test { #[test] fn test_value_from() { - assert_eq!(Value::from_float(42f64), 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))); + assert_eq!( + Value::from_float(42f64), + 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] @@ -716,15 +739,11 @@ mod test { let data = Value::Vector(vec![ Value::Integer(1), Value::Integer(2), - Value::List(LinkedList::from_iter(vec![ - Value::from_float(3.14) - ])), - Value::Set(BTreeSet::from_iter(vec![ - Value::from_bigint("4").unwrap() - ])), + Value::List(LinkedList::from_iter(vec![Value::from_float(3.14)])), + Value::Set(BTreeSet::from_iter(vec![Value::from_bigint("4").unwrap()])), Value::Map(BTreeMap::from_iter(vec![ (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::from_keyword(None, "five"), @@ -741,26 +760,68 @@ mod test { 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().without_spans().to_string()); + assert_eq!( + string, + parse::value(&data.to_string()) + .unwrap() + .without_spans() + .to_string() + ); } #[test] fn test_ord() { // 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::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::from_bigint("1").cmp(&Value::from_bigint("2")), Ordering::Greater); - assert_eq!(Value::from_float(1f64).cmp(&Value::from_float(2f64)), Ordering::Greater); - assert_eq!(Value::Text("1".to_string()).cmp(&Value::Text("2".to_string())), 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); + assert_eq!( + Value::from_bigint("1").cmp(&Value::from_bigint("2")), + Ordering::Greater + ); + assert_eq!( + Value::from_float(1f64).cmp(&Value::from_float(2f64)), + Ordering::Greater + ); + assert_eq!( + Value::Text("1".to_string()).cmp(&Value::Text("2".to_string())), + 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] diff --git a/edn/src/utils.rs b/edn/src/utils.rs index f5ea960f..811d057d 100644 --- a/edn/src/utils.rs +++ b/edn/src/utils.rs @@ -26,6 +26,6 @@ pub fn merge(left: &Value, right: &Value) -> Option { result.extend(r.clone().into_iter()); Some(Value::Map(result)) } - _ => None + _ => None, } } diff --git a/edn/src/value_rc.rs b/edn/src/value_rc.rs index 88a5ba7c..40cfcbf3 100644 --- a/edn/src/value_rc.rs +++ b/edn/src/value_rc.rs @@ -8,20 +8,19 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use ::std::rc::{ - Rc, -}; +use std::rc::Rc; -use ::std::sync::{ - Arc, -}; +use std::sync::Arc; pub trait FromRc { fn from_rc(val: Rc) -> Self; fn from_arc(val: Arc) -> Self; } -impl FromRc for Rc where T: Sized + Clone { +impl FromRc for Rc +where + T: Sized + Clone, +{ fn from_rc(val: Rc) -> Self { val.clone() } @@ -34,7 +33,10 @@ impl FromRc for Rc where T: Sized + Clone { } } -impl FromRc for Arc where T: Sized + Clone { +impl FromRc for Arc +where + T: Sized + Clone, +{ fn from_rc(val: Rc) -> Self { match ::std::rc::Rc::::try_unwrap(val) { Ok(v) => Self::new(v), @@ -47,7 +49,10 @@ impl FromRc for Arc where T: Sized + Clone { } } -impl FromRc for Box where T: Sized + Clone { +impl FromRc for Box +where + T: Sized + Clone, +{ fn from_rc(val: Rc) -> Self { match ::std::rc::Rc::::try_unwrap(val) { Ok(v) => Self::new(v), @@ -69,7 +74,10 @@ pub trait Cloned { fn to_value_rc(&self) -> ValueRc; } -impl Cloned for Rc where T: Sized + Clone { +impl Cloned for Rc +where + T: Sized + Clone, +{ fn cloned(&self) -> T { (*self.as_ref()).clone() } @@ -79,7 +87,10 @@ impl Cloned for Rc where T: Sized + Clone { } } -impl Cloned for Arc where T: Sized + Clone { +impl Cloned for Arc +where + T: Sized + Clone, +{ fn cloned(&self) -> T { (*self.as_ref()).clone() } @@ -89,7 +100,10 @@ impl Cloned for Arc where T: Sized + Clone { } } -impl Cloned for Box where T: Sized + Clone { +impl Cloned for Box +where + T: Sized + Clone, +{ fn cloned(&self) -> T { self.as_ref().clone() } diff --git a/edn/tests/query_tests.rs b/edn/tests/query_tests.rs index ae74140a..fb614a26 100644 --- a/edn/tests/query_tests.rs +++ b/edn/tests/query_tests.rs @@ -10,33 +10,14 @@ extern crate edn; -use edn::{ - Keyword, - PlainSymbol, -}; +use edn::{Keyword, PlainSymbol}; use edn::query::{ - Direction, - Element, - FindSpec, - FnArg, - Limit, - NonIntegerConstant, - Order, - OrJoin, - OrWhereClause, - Pattern, - PatternNonValuePlace, - PatternValuePlace, - Predicate, - UnifyVars, - Variable, - WhereClause, + Direction, Element, FindSpec, FnArg, Limit, NonIntegerConstant, OrJoin, OrWhereClause, Order, + Pattern, PatternNonValuePlace, PatternValuePlace, Predicate, UnifyVars, Variable, WhereClause, }; -use edn::parse::{ - parse_query, -}; +use edn::parse::parse_query; ///! N.B., parsing a query can be done without reference to a DB. ///! 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 p = parse_query(s).unwrap(); - assert_eq!(p.find_spec, - FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x")))); - assert_eq!(p.where_clauses, - vec![ - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - 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(10), - ]}), - ]); + assert_eq!( + p.find_spec, + FindSpec::FindColl(Element::Variable(Variable::from_valid_name("?x"))) + ); + assert_eq!( + p.where_clauses, + vec![ + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + 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(10), + ] + }), + ] + ); } #[test] @@ -70,32 +59,32 @@ fn can_parse_simple_or() { let s = "[:find ?x . :where (or [?x _ 10] [?x _ 15])]"; let p = parse_query(s).unwrap(); - assert_eq!(p.find_spec, - FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); - assert_eq!(p.where_clauses, - vec![ - WhereClause::OrJoin(OrJoin::new( - UnifyVars::Implicit, - vec![ - OrWhereClause::Clause( - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::EntidOrInteger(10), - tx: PatternNonValuePlace::Placeholder, - })), - OrWhereClause::Clause( - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::EntidOrInteger(15), - tx: PatternNonValuePlace::Placeholder, - })), - ], - )), - ]); + assert_eq!( + p.find_spec, + FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) + ); + assert_eq!( + p.where_clauses, + vec![WhereClause::OrJoin(OrJoin::new( + UnifyVars::Implicit, + vec![ + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::EntidOrInteger(10), + tx: PatternNonValuePlace::Placeholder, + })), + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::EntidOrInteger(15), + tx: PatternNonValuePlace::Placeholder, + })), + ], + )),] + ); } #[test] @@ -103,24 +92,23 @@ fn can_parse_unit_or_join() { let s = "[:find ?x . :where (or-join [?x] [?x _ 15])]"; let p = parse_query(s).expect("to be able to parse find"); - assert_eq!(p.find_spec, - FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); - assert_eq!(p.where_clauses, - vec![ - WhereClause::OrJoin(OrJoin::new( - UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), - vec![ - OrWhereClause::Clause( - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::EntidOrInteger(15), - tx: PatternNonValuePlace::Placeholder, - })), - ], - )), - ]); + assert_eq!( + p.find_spec, + FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) + ); + assert_eq!( + p.where_clauses, + vec![WhereClause::OrJoin(OrJoin::new( + UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), + vec![OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::EntidOrInteger(15), + tx: PatternNonValuePlace::Placeholder, + })),], + )),] + ); } #[test] @@ -128,32 +116,32 @@ fn can_parse_simple_or_join() { let s = "[:find ?x . :where (or-join [?x] [?x _ 10] [?x _ -15])]"; let p = parse_query(s).unwrap(); - assert_eq!(p.find_spec, - FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); - assert_eq!(p.where_clauses, - vec![ - WhereClause::OrJoin(OrJoin::new( - UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), - vec![ - OrWhereClause::Clause( - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::EntidOrInteger(10), - tx: PatternNonValuePlace::Placeholder, - })), - OrWhereClause::Clause( - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::EntidOrInteger(-15), - tx: PatternNonValuePlace::Placeholder, - })), - ], - )), - ]); + assert_eq!( + p.find_spec, + FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) + ); + assert_eq!( + p.where_clauses, + vec![WhereClause::OrJoin(OrJoin::new( + UnifyVars::Explicit(std::iter::once(Variable::from_valid_name("?x")).collect()), + vec![ + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::EntidOrInteger(10), + tx: PatternNonValuePlace::Placeholder, + })), + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::EntidOrInteger(-15), + tx: PatternNonValuePlace::Placeholder, + })), + ], + )),] + ); } #[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 p = parse_query(s).unwrap(); - assert_eq!(p.find_spec, - FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x")))); - assert_eq!(p.where_clauses, - vec![ - WhereClause::OrJoin(OrJoin::new( - UnifyVars::Implicit, - vec![ - OrWhereClause::Clause( - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::EntidOrInteger(10), - tx: PatternNonValuePlace::Placeholder, - })), - OrWhereClause::And( - vec![ - WhereClause::OrJoin(OrJoin::new( - UnifyVars::Implicit, - vec![ - OrWhereClause::Clause(WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), - tx: PatternNonValuePlace::Placeholder, - })), - OrWhereClause::Clause(WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?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), - ]}), - ], - ) - ], - )), - ]); + assert_eq!( + p.find_spec, + FindSpec::FindScalar(Element::Variable(Variable::from_valid_name("?x"))) + ); + assert_eq!( + p.where_clauses, + vec![WhereClause::OrJoin(OrJoin::new( + UnifyVars::Implicit, + vec![ + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::EntidOrInteger(10), + tx: PatternNonValuePlace::Placeholder, + })), + OrWhereClause::And(vec![ + WhereClause::OrJoin(OrJoin::new( + UnifyVars::Implicit, + vec![ + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?x" + )), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Variable(Variable::from_valid_name("?y")), + tx: PatternNonValuePlace::Placeholder, + })), + OrWhereClause::Clause(WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?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), + ] + }), + ],) + ], + )),] + ); } #[test] @@ -220,21 +214,40 @@ fn can_parse_order_by() { // Defaults to ascending. let default = "[:find ?x :where [?x :foo/baz ?y] :order ?y]"; - assert_eq!(parse_query(default).unwrap().order, - Some(vec![Order(Direction::Ascending, Variable::from_valid_name("?y"))])); + assert_eq!( + 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)]"; - assert_eq!(parse_query(ascending).unwrap().order, - Some(vec![Order(Direction::Ascending, Variable::from_valid_name("?y"))])); + assert_eq!( + 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)]"; - assert_eq!(parse_query(descending).unwrap().order, - Some(vec![Order(Direction::Descending, Variable::from_valid_name("?y"))])); + assert_eq!( + 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)]"; - assert_eq!(parse_query(mixed).unwrap().order, - Some(vec![Order(Direction::Descending, Variable::from_valid_name("?y")), - Order(Direction::Ascending, Variable::from_valid_name("?x"))])); + assert_eq!( + parse_query(mixed).unwrap().order, + Some(vec![ + Order(Direction::Descending, Variable::from_valid_name("?y")), + Order(Direction::Ascending, Variable::from_valid_name("?x")) + ]) + ); } #[test] @@ -246,55 +259,76 @@ fn can_parse_limit() { assert!(parse_query(zero_invalid).is_err()); let none = "[:find ?x :where [?x :foo/baz ?y]]"; - assert_eq!(parse_query(none).unwrap().limit, - Limit::None); + assert_eq!(parse_query(none).unwrap().limit, Limit::None); let one = "[:find ?x :where [?x :foo/baz ?y] :limit 1]"; - assert_eq!(parse_query(one).unwrap().limit, - Limit::Fixed(1)); + assert_eq!(parse_query(one).unwrap().limit, Limit::Fixed(1)); let onethousand = "[:find ?x :where [?x :foo/baz ?y] :limit 1000]"; - assert_eq!(parse_query(onethousand).unwrap().limit, - Limit::Fixed(1000)); + assert_eq!(parse_query(onethousand).unwrap().limit, Limit::Fixed(1000)); let variable_with_in = "[:find ?x :in ?limit :where [?x :foo/baz ?y] :limit ?limit]"; - assert_eq!(parse_query(variable_with_in).unwrap().limit, - Limit::Variable(Variable::from_valid_name("?limit"))); + assert_eq!( + 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]"; - assert_eq!(parse_query(variable_with_in_used).unwrap().limit, - Limit::Variable(Variable::from_valid_name("?limit"))); + assert_eq!( + parse_query(variable_with_in_used).unwrap().limit, + Limit::Variable(Variable::from_valid_name("?limit")) + ); } #[test] 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\"]]"; - assert_eq!(parse_query(s).expect("parsed").where_clauses.pop().expect("a where clause"), - WhereClause::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"))); + assert_eq!( + parse_query(s) + .expect("parsed") + .where_clauses + .pop() + .expect("a where clause"), + WhereClause::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] 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. let s = r#"[:find ?x ,, :where, ;atest [?x :foo/baz #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644", ;testa ,],, ,],;"#; - assert_eq!(parse_query(s).expect("parsed").where_clauses.pop().expect("a where clause"), - WhereClause::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"))); + assert_eq!( + parse_query(s) + .expect("parsed") + .where_clauses + .pop() + .expect("a where clause"), + WhereClause::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") + ) + ); } diff --git a/edn/tests/serde_support.rs b/edn/tests/serde_support.rs index 7a460b52..078c282c 100644 --- a/edn/tests/serde_support.rs +++ b/edn/tests/serde_support.rs @@ -8,11 +8,10 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. - #![cfg(feature = "serde_support")] -extern crate serde_test; extern crate serde_json; +extern crate serde_test; extern crate edn; use edn::symbols::Keyword; @@ -22,19 +21,24 @@ use serde_test::{assert_tokens, Token}; #[test] fn test_serialize_keyword() { let kw = Keyword::namespaced("foo", "bar"); - assert_tokens(&kw, &[ - Token::NewtypeStruct { name: "Keyword" }, - Token::Struct { name: "NamespaceableName", len: 2 }, - Token::Str("namespace"), - Token::Some, - Token::BorrowedStr("foo"), - Token::Str("name"), - Token::BorrowedStr("bar"), - Token::StructEnd, - ]); + assert_tokens( + &kw, + &[ + Token::NewtypeStruct { name: "Keyword" }, + Token::Struct { + name: "NamespaceableName", + len: 2, + }, + Token::Str("namespace"), + Token::Some, + Token::BorrowedStr("foo"), + Token::Str("name"), + Token::BorrowedStr("bar"), + Token::StructEnd, + ], + ); } - #[cfg(feature = "serde_support")] #[test] fn test_deserialize_keyword() { @@ -51,6 +55,3 @@ fn test_deserialize_keyword() { let not_kw = serde_json::from_str::(bad_ns_json); assert!(not_kw.is_err()); } - - - diff --git a/edn/tests/tests.rs b/edn/tests/tests.rs index 54cf87b0..f864c87c 100644 --- a/edn/tests/tests.rs +++ b/edn/tests/tests.rs @@ -14,26 +14,18 @@ extern crate num; extern crate ordered_float; extern crate uuid; -use std::collections::{BTreeSet, BTreeMap, LinkedList}; -use std::iter::FromIterator; +use std::collections::{BTreeMap, BTreeSet, LinkedList}; use std::f64; +use std::iter::FromIterator; use num::bigint::ToBigInt; -use num::traits::{Zero, One}; +use num::traits::{One, Zero}; use ordered_float::OrderedFloat; +use chrono::{TimeZone, Utc}; use edn::parse::{self, ParseError}; -use edn::types::{ - Value, - ValueAndSpan, - Span, - SpannedValue, -}; -use chrono::{ - TimeZone, - Utc, -}; use edn::symbols; +use edn::types::{Span, SpannedValue, Value, ValueAndSpan}; use edn::utils; // Helper for making wrapped keywords with a namespace. @@ -59,10 +51,13 @@ fn s_plain(name: &str) -> Value { // Helpers for parsing strings and converting them into edn::Value. macro_rules! fn_parse_into_value { ($name: ident) => { - fn $name<'a, T>(src: T) -> Result where T: Into<&'a str> { + fn $name<'a, T>(src: T) -> Result + where + T: Into<&'a str>, + { parse::$name(src.into()).map(|x| x.into()) } - } + }; } // These look exactly like their `parse::foo` counterparts, but @@ -98,10 +93,13 @@ fn test_nil() { #[test] fn test_span_nil() { - assert_eq!(parse::value("nil").unwrap(), ValueAndSpan { - inner: SpannedValue::Nil, - span: Span(0, 3) - }); + assert_eq!( + parse::value("nil").unwrap(), + ValueAndSpan { + inner: SpannedValue::Nil, + span: Span(0, 3) + } + ); } #[test] @@ -120,10 +118,13 @@ fn test_nan() { #[test] fn test_span_nan() { - assert_eq!(parse::value("#f NaN").unwrap(), ValueAndSpan { - inner: SpannedValue::Float(OrderedFloat(f64::NAN)), - span: Span(0, 6) - }); + assert_eq!( + parse::value("#f NaN").unwrap(), + ValueAndSpan { + inner: SpannedValue::Float(OrderedFloat(f64::NAN)), + span: Span(0, 6) + } + ); } #[test] @@ -136,28 +137,52 @@ fn test_infinity() { assert!(infinity("#f;x\n-Infinity").is_err()); assert!(infinity("#f;x\n+Infinity").is_err()); - assert_eq!(infinity("#f -Infinity").unwrap(), Float(OrderedFloat(f64::NEG_INFINITY))); - assert_eq!(infinity("#f +Infinity").unwrap(), Float(OrderedFloat(f64::INFINITY))); + assert_eq!( + infinity("#f -Infinity").unwrap(), + Float(OrderedFloat(f64::NEG_INFINITY)) + ); + assert_eq!( + infinity("#f +Infinity").unwrap(), + Float(OrderedFloat(f64::INFINITY)) + ); - assert_eq!(infinity("#f\t -Infinity").unwrap(), Float(OrderedFloat(f64::NEG_INFINITY))); - assert_eq!(infinity("#f\t +Infinity").unwrap(), Float(OrderedFloat(f64::INFINITY))); + assert_eq!( + infinity("#f\t -Infinity").unwrap(), + Float(OrderedFloat(f64::NEG_INFINITY)) + ); + assert_eq!( + infinity("#f\t +Infinity").unwrap(), + Float(OrderedFloat(f64::INFINITY)) + ); - assert_eq!(infinity("#f,-Infinity").unwrap(), Float(OrderedFloat(f64::NEG_INFINITY))); - assert_eq!(infinity("#f,+Infinity").unwrap(), Float(OrderedFloat(f64::INFINITY))); + assert_eq!( + infinity("#f,-Infinity").unwrap(), + Float(OrderedFloat(f64::NEG_INFINITY)) + ); + assert_eq!( + infinity("#f,+Infinity").unwrap(), + Float(OrderedFloat(f64::INFINITY)) + ); assert!(infinity("true").is_err()); } #[test] fn test_span_infinity() { - assert_eq!(parse::value("#f -Infinity").unwrap(), ValueAndSpan { - inner: SpannedValue::Float(OrderedFloat(f64::NEG_INFINITY)), - span: Span(0, 12) - }); - assert_eq!(parse::value("#f +Infinity").unwrap(), ValueAndSpan { - inner: SpannedValue::Float(OrderedFloat(f64::INFINITY)), - span: Span(0, 12) - }); + assert_eq!( + parse::value("#f -Infinity").unwrap(), + ValueAndSpan { + inner: SpannedValue::Float(OrderedFloat(f64::NEG_INFINITY)), + span: Span(0, 12) + } + ); + assert_eq!( + parse::value("#f +Infinity").unwrap(), + ValueAndSpan { + inner: SpannedValue::Float(OrderedFloat(f64::INFINITY)), + span: Span(0, 12) + } + ); } #[test] @@ -172,15 +197,21 @@ fn test_boolean() { #[test] fn test_span_boolean() { - assert_eq!(parse::value("true").unwrap(), ValueAndSpan { - inner: SpannedValue::Boolean(true), - span: Span(0, 4) - }); + assert_eq!( + parse::value("true").unwrap(), + ValueAndSpan { + inner: SpannedValue::Boolean(true), + span: Span(0, 4) + } + ); - assert_eq!(parse::value("false").unwrap(), ValueAndSpan { - inner: SpannedValue::Boolean(false), - span: Span(0, 5) - }); + assert_eq!( + parse::value("false").unwrap(), + ValueAndSpan { + inner: SpannedValue::Boolean(false), + span: Span(0, 5) + } + ); } #[test] @@ -235,39 +266,49 @@ fn test_octalinteger() { #[test] fn test_span_integer() { - assert_eq!(parse::value("42").unwrap(), ValueAndSpan { - inner: SpannedValue::Integer(42), - span: Span(0, 2) - }); - assert_eq!(parse::value("0xabc111").unwrap(), ValueAndSpan { - inner: SpannedValue::Integer(11256081), - span: Span(0, 8) - }); - assert_eq!(parse::value("2r111").unwrap(), ValueAndSpan { - inner: SpannedValue::Integer(7), - span: Span(0, 5) - }); - assert_eq!(parse::value("011").unwrap(), ValueAndSpan { - inner: SpannedValue::Integer(9), - span: Span(0, 3) - }); + assert_eq!( + parse::value("42").unwrap(), + ValueAndSpan { + inner: SpannedValue::Integer(42), + span: Span(0, 2) + } + ); + assert_eq!( + parse::value("0xabc111").unwrap(), + ValueAndSpan { + inner: SpannedValue::Integer(11256081), + span: Span(0, 8) + } + ); + assert_eq!( + parse::value("2r111").unwrap(), + ValueAndSpan { + inner: SpannedValue::Integer(7), + span: Span(0, 5) + } + ); + assert_eq!( + parse::value("011").unwrap(), + ValueAndSpan { + inner: SpannedValue::Integer(9), + span: Span(0, 3) + } + ); } #[test] fn test_uuid() { - assert!(parse::uuid("#uuid\"550e8400-e29b-41d4-a716-446655440000\"").is_err()); // No whitespace. - assert!(parse::uuid("#uuid \"z50e8400-e29b-41d4-a716-446655440000\"").is_err()); // Not hex. - assert!(parse::uuid("\"z50e8400-e29b-41d4-a716-446655440000\"").is_err()); // No tag. - assert!(parse::uuid("#uuid \"aaaaaaaae29b-41d4-a716-446655440000\"").is_err()); // Hyphens. - assert!(parse::uuid("#uuid \"aaaaaaaa-e29b-41d4-a716-446655440\"").is_err()); // Truncated. - assert!(parse::uuid("#uuid \"A50e8400-e29b-41d4-a716-446655440000\"").is_err()); // Capital. + assert!(parse::uuid("#uuid\"550e8400-e29b-41d4-a716-446655440000\"").is_err()); // No whitespace. + assert!(parse::uuid("#uuid \"z50e8400-e29b-41d4-a716-446655440000\"").is_err()); // Not hex. + assert!(parse::uuid("\"z50e8400-e29b-41d4-a716-446655440000\"").is_err()); // No tag. + assert!(parse::uuid("#uuid \"aaaaaaaae29b-41d4-a716-446655440000\"").is_err()); // Hyphens. + assert!(parse::uuid("#uuid \"aaaaaaaa-e29b-41d4-a716-446655440\"").is_err()); // Truncated. + assert!(parse::uuid("#uuid \"A50e8400-e29b-41d4-a716-446655440000\"").is_err()); // Capital. - let expected = uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000") - .expect("valid UUID"); + let expected = + uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").expect("valid UUID"); let s = "#uuid \"550e8400-e29b-41d4-a716-446655440000\""; - let actual = parse::uuid(s) - .expect("parse success") - .into(); + let actual = parse::uuid(s).expect("parse success").into(); let value = self::Value::Uuid(expected); assert_eq!(value, actual); assert_eq!(format!("{}", value), s); @@ -276,15 +317,13 @@ fn test_uuid() { #[test] fn test_inst() { - assert!(parse::value("#inst\"2016-01-01T11:00:00.000Z\"").is_err()); // No whitespace. - assert!(parse::value("#inst \"2016-01-01T11:00:00.000\"").is_err()); // No timezone. - assert!(parse::value("#inst \"2016-01-01T11:00:00.000z\"").is_err()); // Lowercase timezone. + assert!(parse::value("#inst\"2016-01-01T11:00:00.000Z\"").is_err()); // No whitespace. + assert!(parse::value("#inst \"2016-01-01T11:00:00.000\"").is_err()); // No timezone. + assert!(parse::value("#inst \"2016-01-01T11:00:00.000z\"").is_err()); // Lowercase timezone. let expected = Utc.timestamp(1493410985, 187000000); let s = "#inst \"2017-04-28T20:23:05.187Z\""; - let actual = parse::value(s) - .expect("parse success") - .into(); + let actual = parse::value(s).expect("parse success").into(); let value = self::Value::Instant(expected); assert_eq!(value, actual); assert_eq!(format!("{}", value), s); @@ -301,7 +340,10 @@ fn test_bigint() { assert_eq!(bigint("0N").unwrap(), BigInteger(Zero::zero())); assert_eq!(bigint("1N").unwrap(), BigInteger(One::one())); assert_eq!(bigint("9223372036854775807N").unwrap(), BigInteger(max_i64)); - assert_eq!(bigint("85070591730234615847396907784232501249N").unwrap(), BigInteger(bigger)); + assert_eq!( + bigint("85070591730234615847396907784232501249N").unwrap(), + BigInteger(bigger) + ); assert!(bigint("nil").is_err()); } @@ -311,10 +353,13 @@ fn test_span_bigint() { let max_i64 = i64::max_value().to_bigint().unwrap(); let bigger = &max_i64 * &max_i64; - assert_eq!(parse::value("85070591730234615847396907784232501249N").unwrap(), ValueAndSpan { - inner: SpannedValue::BigInteger(bigger), - span: Span(0, 39) - }); + assert_eq!( + parse::value("85070591730234615847396907784232501249N").unwrap(), + ValueAndSpan { + inner: SpannedValue::BigInteger(bigger), + span: Span(0, 39) + } + ); } #[test] @@ -333,17 +378,23 @@ fn test_float() { #[test] fn test_span_float() { - assert_eq!(parse::value("42.0").unwrap(), ValueAndSpan { - inner: SpannedValue::Float(OrderedFloat(42f64)), - span: Span(0, 4) - }); + assert_eq!( + parse::value("42.0").unwrap(), + ValueAndSpan { + inner: SpannedValue::Float(OrderedFloat(42f64)), + span: Span(0, 4) + } + ); } #[test] fn test_text() { use self::Value::*; - assert_eq!(text("\"hello world\"").unwrap(), Text("hello world".to_string())); + assert_eq!( + text("\"hello world\"").unwrap(), + Text("hello world".to_string()) + ); assert_eq!(text("\"\"").unwrap(), Text("".to_string())); assert!(text("\"").is_err()); @@ -351,19 +402,24 @@ fn test_text() { let raw_edn = r#""This string contains a \" and a \\""#; let raw_string = r#"This string contains a " and a \"#; - assert_eq!(parse::value(raw_edn).unwrap(), - ValueAndSpan { - inner: SpannedValue::Text(raw_string.to_string()), - span: Span(0, raw_edn.len() as u32) - }); + assert_eq!( + parse::value(raw_edn).unwrap(), + ValueAndSpan { + inner: SpannedValue::Text(raw_string.to_string()), + span: Span(0, raw_edn.len() as u32) + } + ); } #[test] fn test_span_text() { - assert_eq!(parse::value("\"hello world\"").unwrap(), ValueAndSpan { - inner: SpannedValue::Text("hello world".to_string()), - span: Span(0, 13) - }); + assert_eq!( + parse::value("\"hello world\"").unwrap(), + ValueAndSpan { + inner: SpannedValue::Text("hello world".to_string()), + span: Span(0, 13) + } + ); } #[test] @@ -373,11 +429,23 @@ fn test_symbol() { assert_eq!(symbol("...").unwrap(), s_plain("...")); assert_eq!(symbol("hello/world").unwrap(), s_ns("hello", "world")); - assert_eq!(symbol("foo-bar/baz-boz").unwrap(), s_ns("foo-bar", "baz-boz")); + assert_eq!( + symbol("foo-bar/baz-boz").unwrap(), + s_ns("foo-bar", "baz-boz") + ); - assert_eq!(symbol("foo-bar/baz_boz").unwrap(), s_ns("foo-bar", "baz_boz")); - assert_eq!(symbol("foo_bar/baz-boz").unwrap(), s_ns("foo_bar", "baz-boz")); - assert_eq!(symbol("foo_bar/baz_boz").unwrap(), s_ns("foo_bar", "baz_boz")); + assert_eq!( + symbol("foo-bar/baz_boz").unwrap(), + s_ns("foo-bar", "baz_boz") + ); + assert_eq!( + symbol("foo_bar/baz-boz").unwrap(), + s_ns("foo_bar", "baz-boz") + ); + assert_eq!( + symbol("foo_bar/baz_boz").unwrap(), + s_ns("foo_bar", "baz_boz") + ); assert_eq!(symbol("symbol").unwrap(), s_plain("symbol")); assert_eq!(symbol("hello").unwrap(), s_plain("hello")); @@ -387,24 +455,42 @@ fn test_symbol() { #[test] fn test_span_symbol() { - assert_eq!(parse::value("hello").unwrap(), ValueAndSpan { - inner: SpannedValue::from_symbol(None, "hello"), - span: Span(0, 5) - }); - assert_eq!(parse::value("hello/world").unwrap(), ValueAndSpan { - inner: SpannedValue::from_symbol("hello", "world"), - span: Span(0, 11) - }); + assert_eq!( + parse::value("hello").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_symbol(None, "hello"), + span: Span(0, 5) + } + ); + assert_eq!( + parse::value("hello/world").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_symbol("hello", "world"), + span: Span(0, 11) + } + ); } #[test] fn test_keyword() { assert_eq!(keyword(":hello/world").unwrap(), k_ns("hello", "world")); - assert_eq!(keyword(":foo-bar/baz-boz").unwrap(), k_ns("foo-bar", "baz-boz")); + assert_eq!( + keyword(":foo-bar/baz-boz").unwrap(), + k_ns("foo-bar", "baz-boz") + ); - assert_eq!(keyword(":foo-bar/baz_boz").unwrap(), k_ns("foo-bar", "baz_boz")); - assert_eq!(keyword(":foo_bar/baz-boz").unwrap(), k_ns("foo_bar", "baz-boz")); - assert_eq!(keyword(":foo_bar/baz_boz").unwrap(), k_ns("foo_bar", "baz_boz")); + assert_eq!( + keyword(":foo-bar/baz_boz").unwrap(), + k_ns("foo-bar", "baz_boz") + ); + assert_eq!( + keyword(":foo_bar/baz-boz").unwrap(), + k_ns("foo_bar", "baz-boz") + ); + assert_eq!( + keyword(":foo_bar/baz_boz").unwrap(), + k_ns("foo_bar", "baz_boz") + ); assert_eq!(keyword(":symbol").unwrap(), k_plain("symbol")); assert_eq!(keyword(":hello").unwrap(), k_plain("hello")); @@ -414,20 +500,32 @@ fn test_keyword() { assert!(keyword(":").is_err()); assert!(keyword(":foo/").is_err()); - assert_eq!(keyword(":foo*!_?$%&=<>-+").unwrap(), k_plain("foo*!_?$%&=<>-+")); - assert_eq!(keyword(":foo/bar*!_?$%&=<>-+").unwrap(), k_ns("foo", "bar*!_?$%&=<>-+")); + assert_eq!( + keyword(":foo*!_?$%&=<>-+").unwrap(), + k_plain("foo*!_?$%&=<>-+") + ); + assert_eq!( + keyword(":foo/bar*!_?$%&=<>-+").unwrap(), + k_ns("foo", "bar*!_?$%&=<>-+") + ); } #[test] fn test_span_keyword() { - assert_eq!(parse::value(":hello").unwrap(), ValueAndSpan { - inner: SpannedValue::from_keyword(None, "hello"), - span: Span(0, 6) - }); - assert_eq!(parse::value(":hello/world").unwrap(), ValueAndSpan { - inner: SpannedValue::from_keyword("hello", "world"), - span: Span(0, 12) - }); + assert_eq!( + parse::value(":hello").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_keyword(None, "hello"), + span: Span(0, 6) + } + ); + assert_eq!( + parse::value(":hello/world").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_keyword("hello", "world"), + span: Span(0, 12) + } + ); } #[test] @@ -443,23 +541,48 @@ fn test_value() { assert_eq!(value("0xabc111").unwrap(), Integer(11256081)); assert_eq!(value("2r111").unwrap(), Integer(7)); assert_eq!(value("011").unwrap(), Integer(9)); - assert_eq!(value("85070591730234615847396907784232501249N").unwrap(), BigInteger(bigger)); + assert_eq!( + value("85070591730234615847396907784232501249N").unwrap(), + BigInteger(bigger) + ); assert_eq!(value("111.222").unwrap(), Float(OrderedFloat(111.222f64))); - assert_eq!(value("\"hello world\"").unwrap(), Text("hello world".to_string())); + assert_eq!( + value("\"hello world\"").unwrap(), + Text("hello world".to_string()) + ); assert_eq!(value("$").unwrap(), s_plain("$")); assert_eq!(value(".").unwrap(), s_plain(".")); assert_eq!(value("$symbol").unwrap(), s_plain("$symbol")); assert_eq!(value(":hello").unwrap(), k_plain("hello")); assert_eq!(value("[1]").unwrap(), Vector(vec![Integer(1)])); - assert_eq!(value("(1)").unwrap(), List(LinkedList::from_iter(vec![Integer(1)]))); - assert_eq!(value("#{1}").unwrap(), Set(BTreeSet::from_iter(vec![Integer(1)]))); - assert_eq!(value("{1 2}").unwrap(), Map(BTreeMap::from_iter(vec![(Integer(1), Integer(2))]))); - assert_eq!(value("#uuid \"e43c6f3e-3123-49b7-8098-9b47a7bc0fa4\"").unwrap(), - Uuid(uuid::Uuid::parse_str("e43c6f3e-3123-49b7-8098-9b47a7bc0fa4").unwrap())); - assert_eq!(value("#instmillis 1493410985187").unwrap(), Instant(Utc.timestamp(1493410985, 187000000))); - assert_eq!(value("#instmicros 1493410985187123").unwrap(), Instant(Utc.timestamp(1493410985, 187123000))); - assert_eq!(value("#inst \"2017-04-28T20:23:05.187Z\"").unwrap(), - Instant(Utc.timestamp(1493410985, 187000000))); + assert_eq!( + value("(1)").unwrap(), + List(LinkedList::from_iter(vec![Integer(1)])) + ); + assert_eq!( + value("#{1}").unwrap(), + Set(BTreeSet::from_iter(vec![Integer(1)])) + ); + assert_eq!( + value("{1 2}").unwrap(), + Map(BTreeMap::from_iter(vec![(Integer(1), Integer(2))])) + ); + assert_eq!( + value("#uuid \"e43c6f3e-3123-49b7-8098-9b47a7bc0fa4\"").unwrap(), + Uuid(uuid::Uuid::parse_str("e43c6f3e-3123-49b7-8098-9b47a7bc0fa4").unwrap()) + ); + assert_eq!( + value("#instmillis 1493410985187").unwrap(), + Instant(Utc.timestamp(1493410985, 187000000)) + ); + assert_eq!( + value("#instmicros 1493410985187123").unwrap(), + Instant(Utc.timestamp(1493410985, 187123000)) + ); + assert_eq!( + value("#inst \"2017-04-28T20:23:05.187Z\"").unwrap(), + Instant(Utc.timestamp(1493410985, 187000000)) + ); } #[test] @@ -467,86 +590,122 @@ fn test_span_value() { let max_i64 = i64::max_value().to_bigint().unwrap(); let bigger = &max_i64 * &max_i64; - assert_eq!(parse::value("nil").unwrap(), ValueAndSpan { - inner: SpannedValue::Nil, - span: Span(0,3) - }); - assert_eq!(parse::value("true").unwrap(), ValueAndSpan { - inner: SpannedValue::Boolean(true), - span: Span(0,4) - }); - assert_eq!(parse::value("1").unwrap(), ValueAndSpan { - inner: SpannedValue::Integer(1i64), - span: Span(0,1) - }); - assert_eq!(parse::value("85070591730234615847396907784232501249N").unwrap(), ValueAndSpan { - inner: SpannedValue::BigInteger(bigger), - span: Span(0,39) - }); - assert_eq!(parse::value("111.222").unwrap(), ValueAndSpan { - inner: SpannedValue::Float(OrderedFloat(111.222f64)), - span: Span(0,7) - }); - assert_eq!(parse::value("\"hello world\"").unwrap(), ValueAndSpan { - inner: SpannedValue::Text("hello world".to_string()), - span: Span(0,13) - }); - assert_eq!(parse::value("$").unwrap(), ValueAndSpan { - inner: SpannedValue::from_symbol(None, "$"), - span: Span(0,1) - }); - assert_eq!(parse::value(".").unwrap(), ValueAndSpan { - inner: SpannedValue::from_symbol(None, "."), - span: Span(0,1) - }); - assert_eq!(parse::value("$symbol").unwrap(), ValueAndSpan { - inner: SpannedValue::from_symbol(None, "$symbol"), - span: Span(0,7) - }); - assert_eq!(parse::value(":hello").unwrap(), ValueAndSpan { - inner: SpannedValue::from_keyword(None, "hello"), - span: Span(0,6) - }); - assert_eq!(parse::value("[1]").unwrap(), ValueAndSpan { - inner: SpannedValue::Vector(vec![ - ValueAndSpan { + assert_eq!( + parse::value("nil").unwrap(), + ValueAndSpan { + inner: SpannedValue::Nil, + span: Span(0, 3) + } + ); + assert_eq!( + parse::value("true").unwrap(), + ValueAndSpan { + inner: SpannedValue::Boolean(true), + span: Span(0, 4) + } + ); + assert_eq!( + parse::value("1").unwrap(), + ValueAndSpan { + inner: SpannedValue::Integer(1i64), + span: Span(0, 1) + } + ); + assert_eq!( + parse::value("85070591730234615847396907784232501249N").unwrap(), + ValueAndSpan { + inner: SpannedValue::BigInteger(bigger), + span: Span(0, 39) + } + ); + assert_eq!( + parse::value("111.222").unwrap(), + ValueAndSpan { + inner: SpannedValue::Float(OrderedFloat(111.222f64)), + span: Span(0, 7) + } + ); + assert_eq!( + parse::value("\"hello world\"").unwrap(), + ValueAndSpan { + inner: SpannedValue::Text("hello world".to_string()), + span: Span(0, 13) + } + ); + assert_eq!( + parse::value("$").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_symbol(None, "$"), + span: Span(0, 1) + } + ); + assert_eq!( + parse::value(".").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_symbol(None, "."), + span: Span(0, 1) + } + ); + assert_eq!( + parse::value("$symbol").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_symbol(None, "$symbol"), + span: Span(0, 7) + } + ); + assert_eq!( + parse::value(":hello").unwrap(), + ValueAndSpan { + inner: SpannedValue::from_keyword(None, "hello"), + span: Span(0, 6) + } + ); + assert_eq!( + parse::value("[1]").unwrap(), + ValueAndSpan { + inner: SpannedValue::Vector(vec![ValueAndSpan { inner: SpannedValue::Integer(1), - span: Span(1,2) - } - ]), - span: Span(0,3) - }); - assert_eq!(parse::value("(1)").unwrap(), ValueAndSpan { - inner: SpannedValue::List(LinkedList::from_iter(vec![ - ValueAndSpan { + span: Span(1, 2) + }]), + span: Span(0, 3) + } + ); + assert_eq!( + parse::value("(1)").unwrap(), + ValueAndSpan { + inner: SpannedValue::List(LinkedList::from_iter(vec![ValueAndSpan { inner: SpannedValue::Integer(1), - span: Span(1,2) - } - ])), - span: Span(0,3) - }); - assert_eq!(parse::value("#{1}").unwrap(), ValueAndSpan { - inner: SpannedValue::Set(BTreeSet::from_iter(vec![ - ValueAndSpan { + span: Span(1, 2) + }])), + span: Span(0, 3) + } + ); + assert_eq!( + parse::value("#{1}").unwrap(), + ValueAndSpan { + inner: SpannedValue::Set(BTreeSet::from_iter(vec![ValueAndSpan { inner: SpannedValue::Integer(1), - span: Span(2,3) - } - ])), - span: Span(0,4) - }); - assert_eq!(parse::value("{1 2}").unwrap(), ValueAndSpan { - inner: SpannedValue::Map(BTreeMap::from_iter(vec![ - (ValueAndSpan { - inner: SpannedValue::Integer(1), - span: Span(1,2) - }, - ValueAndSpan { - inner: SpannedValue::Integer(2), - span: Span(3,4) - }) - ])), - span: Span(0,5) - }); + span: Span(2, 3) + }])), + span: Span(0, 4) + } + ); + assert_eq!( + parse::value("{1 2}").unwrap(), + ValueAndSpan { + inner: SpannedValue::Map(BTreeMap::from_iter(vec![( + ValueAndSpan { + inner: SpannedValue::Integer(1), + span: Span(1, 2) + }, + ValueAndSpan { + inner: SpannedValue::Integer(2), + span: Span(3, 4) + } + )])), + span: Span(0, 5) + } + ); } #[test] @@ -557,38 +716,27 @@ fn test_vector() { let bigger = &max_i64 * &max_i64; let test = "[]"; - let value = Vector(vec![ - ]); + let value = Vector(vec![]); assert_eq!(vector(test).unwrap(), value); let test = "[ ]"; - let value = Vector(vec![ - ]); + let value = Vector(vec![]); assert_eq!(vector(test).unwrap(), value); let test = "[1]"; - let value = Vector(vec![ - Integer(1), - ]); + let value = Vector(vec![Integer(1)]); assert_eq!(vector(test).unwrap(), value); let test = "[ 1 ]"; - let value = Vector(vec![ - Integer(1), - ]); + let value = Vector(vec![Integer(1)]); assert_eq!(vector(test).unwrap(), value); let test = "[nil]"; - let value = Vector(vec![ - Nil, - ]); + let value = Vector(vec![Nil]); assert_eq!(vector(test).unwrap(), value); let test = "[1 2]"; - let value = Vector(vec![ - Integer(1), - Integer(2), - ]); + let value = Vector(vec![Integer(1), Integer(2)]); assert_eq!(vector(test).unwrap(), value); let test = "[1 2 3.4 85070591730234615847396907784232501249N]"; @@ -601,21 +749,13 @@ fn test_vector() { assert_eq!(vector(test).unwrap(), value); let test = "[1 0 nil \"nil\"]"; - let value = Vector(vec![ - Integer(1), - Integer(0), - Nil, - Text("nil".to_string()), - ]); + let value = Vector(vec![Integer(1), Integer(0), Nil, Text("nil".to_string())]); assert_eq!(vector(test).unwrap(), value); let test = "[1 [0 nil] \"nil\"]"; let value = Vector(vec![ Integer(1), - Vector(vec![ - Integer(0), - Nil, - ]), + Vector(vec![Integer(0), Nil]), Text("nil".to_string()), ]); assert_eq!(vector(test).unwrap(), value); @@ -631,38 +771,27 @@ fn test_list() { use self::Value::*; let test = "()"; - let value = List(LinkedList::from_iter(vec![ - ])); + let value = List(LinkedList::from_iter(vec![])); assert_eq!(list(test).unwrap(), value); let test = "( )"; - let value = List(LinkedList::from_iter(vec![ - ])); + let value = List(LinkedList::from_iter(vec![])); assert_eq!(list(test).unwrap(), value); let test = "(1)"; - let value = List(LinkedList::from_iter(vec![ - Integer(1), - ])); + let value = List(LinkedList::from_iter(vec![Integer(1)])); assert_eq!(list(test).unwrap(), value); let test = "( 1 )"; - let value = List(LinkedList::from_iter(vec![ - Integer(1), - ])); + let value = List(LinkedList::from_iter(vec![Integer(1)])); assert_eq!(list(test).unwrap(), value); let test = "(nil)"; - let value = List(LinkedList::from_iter(vec![ - Nil, - ])); + let value = List(LinkedList::from_iter(vec![Nil])); assert_eq!(list(test).unwrap(), value); let test = "(1 2)"; - let value = List(LinkedList::from_iter(vec![ - Integer(1), - Integer(2), - ])); + let value = List(LinkedList::from_iter(vec![Integer(1), Integer(2)])); assert_eq!(list(test).unwrap(), value); let test = "(1 2 3.4)"; @@ -685,10 +814,7 @@ fn test_list() { let test = "(1 (0 nil) \"nil\")"; let value = List(LinkedList::from_iter(vec![ Integer(1), - List(LinkedList::from_iter(vec![ - Integer(0), - Nil, - ])), + List(LinkedList::from_iter(vec![Integer(0), Nil])), Text("nil".to_string()), ])); assert_eq!(list(test).unwrap(), value); @@ -704,31 +830,23 @@ fn test_set() { use self::Value::*; let test = "#{}"; - let value = Set(BTreeSet::from_iter(vec![ - ])); + let value = Set(BTreeSet::from_iter(vec![])); assert_eq!(set(test).unwrap(), value); let test = "#{ }"; - let value = Set(BTreeSet::from_iter(vec![ - ])); + let value = Set(BTreeSet::from_iter(vec![])); assert_eq!(set(test).unwrap(), value); let test = "#{1}"; - let value = Set(BTreeSet::from_iter(vec![ - Integer(1), - ])); + let value = Set(BTreeSet::from_iter(vec![Integer(1)])); assert_eq!(set(test).unwrap(), value); let test = "#{ 1 }"; - let value = Set(BTreeSet::from_iter(vec![ - Integer(1), - ])); + let value = Set(BTreeSet::from_iter(vec![Integer(1)])); assert_eq!(set(test).unwrap(), value); let test = "#{nil}"; - let value = Set(BTreeSet::from_iter(vec![ - Nil, - ])); + let value = Set(BTreeSet::from_iter(vec![Nil])); assert_eq!(set(test).unwrap(), value); // These tests assume the implementation of Ord for Value, (specifically the sort order which @@ -736,10 +854,7 @@ fn test_set() { // (BTreeSet) assumes sorting this seems pointless. // See the notes in types.rs for why we use BTreeSet rather than HashSet let test = "#{2 1}"; - let value = Set(BTreeSet::from_iter(vec![ - Integer(1), - Integer(2), - ])); + let value = Set(BTreeSet::from_iter(vec![Integer(1), Integer(2)])); assert_eq!(set(test).unwrap(), value); let test = "#{3.4 2 1}"; @@ -762,10 +877,7 @@ fn test_set() { let test = "#{1 #{0 nil} \"nil\"}"; let value = Set(BTreeSet::from_iter(vec![ Integer(1), - Set(BTreeSet::from_iter(vec![ - Nil, - Integer(0), - ])), + Set(BTreeSet::from_iter(vec![Nil, Integer(0)])), Text("nil".to_string()), ])); assert_eq!(set(test).unwrap(), value); @@ -781,25 +893,25 @@ fn test_map() { use self::Value::*; let test = "{}"; - let value = Map(BTreeMap::from_iter(vec![ - ])); + let value = Map(BTreeMap::from_iter(vec![])); assert_eq!(map(test).unwrap(), value); let test = "{ }"; - let value = Map(BTreeMap::from_iter(vec![ - ])); + let value = Map(BTreeMap::from_iter(vec![])); assert_eq!(map(test).unwrap(), value); let test = "{\"a\" 1}"; - let value = Map(BTreeMap::from_iter(vec![ - (Text("a".to_string()), Integer(1)), - ])); + let value = Map(BTreeMap::from_iter(vec![( + Text("a".to_string()), + Integer(1), + )])); assert_eq!(map(test).unwrap(), value); let test = "{ \"a\" 1 }"; - let value = Map(BTreeMap::from_iter(vec![ - (Text("a".to_string()), Integer(1)), - ])); + let value = Map(BTreeMap::from_iter(vec![( + Text("a".to_string()), + Integer(1), + )])); assert_eq!(map(test).unwrap(), value); let test = "{nil 1, \"b\" 2}"; @@ -835,15 +947,21 @@ fn test_map() { let test = "{:a 1, $b {:b/a nil, :b/b #{nil 5}}, c [1 2], d (3 4)}"; let value = Map(BTreeMap::from_iter(vec![ (Keyword(symbols::Keyword::plain("a")), Integer(1)), - (s_plain("$b"), Map(BTreeMap::from_iter(vec![ - (k_ns("b", "a"), Nil), - (k_ns("b", "b"), Set(BTreeSet::from_iter(vec![ - Nil, - Integer(5) - ]))), - ]))), - (s_plain("c"), Vector(vec![Integer(1), Integer(2),])), - (s_plain("d"), List(LinkedList::from_iter(vec![Integer(3), Integer(4),]))), + ( + s_plain("$b"), + Map(BTreeMap::from_iter(vec![ + (k_ns("b", "a"), Nil), + ( + k_ns("b", "b"), + Set(BTreeSet::from_iter(vec![Nil, Integer(5)])), + ), + ])), + ), + (s_plain("c"), Vector(vec![Integer(1), Integer(2)])), + ( + s_plain("d"), + List(LinkedList::from_iter(vec![Integer(3), Integer(4)])), + ), ])); assert_eq!(map(test).unwrap(), value); @@ -891,9 +1009,7 @@ fn test_query_active_sessions() { ]), List(LinkedList::from_iter(vec![ s_plain("not-join"), - Vector(vec![ - s_plain("?id"), - ]), + Vector(vec![s_plain("?id")]), Vector(vec![ s_plain("?id"), k_ns("session", "endReason"), @@ -961,13 +1077,8 @@ fn test_query_starred_pages() { List(LinkedList::from_iter(vec![ s_plain("if"), s_plain("since"), - Vector(vec![ - s_plain("$"), - s_plain("?since"), - ]), - Vector(vec![ - s_plain("$"), - ]), + Vector(vec![s_plain("$"), s_plain("?since")]), + Vector(vec![s_plain("$")]), ])), k_plain("where"), s_plain("where"), @@ -1009,11 +1120,7 @@ fn test_query_saved_pages() { k_ns("save", "savedAt"), s_plain("?instant"), ]), - Vector(vec![ - s_plain("?page"), - k_ns("page", "url"), - s_plain("?url"), - ]), + Vector(vec![s_plain("?page"), k_ns("page", "url"), s_plain("?url")]), Vector(vec![ List(LinkedList::from_iter(vec![ s_plain("get-else"), @@ -1064,14 +1171,9 @@ fn test_query_pages_matching_string_1() { let reply = Vector(vec![ k_plain("find"), - Vector(vec![ - s_plain("?url"), - s_plain("?title"), - ]), + Vector(vec![s_plain("?url"), s_plain("?title")]), k_plain("in"), - Vector(vec![ - s_plain("$"), - ]), + Vector(vec![s_plain("$")]), k_plain("where"), Vector(vec![ Vector(vec![ @@ -1085,11 +1187,7 @@ fn test_query_pages_matching_string_1() { ])), s_plain("string"), ])), - Vector(vec![ - Vector(vec![ - s_plain("?page"), - ]), - ]), + Vector(vec![Vector(vec![s_plain("?page")])]), ]), Vector(vec![ List(LinkedList::from_iter(vec![ @@ -1152,9 +1250,7 @@ fn test_query_pages_matching_string_2() { s_plain("?excerpt"), ]), k_plain("in"), - Vector(vec![ - s_plain("$"), - ]), + Vector(vec![s_plain("$")]), k_plain("where"), Vector(vec![ Vector(vec![ @@ -1169,22 +1265,14 @@ fn test_query_pages_matching_string_2() { ])), s_plain("string"), ])), - Vector(vec![ - Vector(vec![ - s_plain("?save"), - ]), - ]), + Vector(vec![Vector(vec![s_plain("?save")])]), ]), Vector(vec![ s_plain("?save"), k_ns("save", "page"), s_plain("?page"), ]), - Vector(vec![ - s_plain("?page"), - k_ns("page", "url"), - s_plain("?url"), - ]), + Vector(vec![s_plain("?page"), k_ns("page", "url"), s_plain("?url")]), Vector(vec![ List(LinkedList::from_iter(vec![ s_plain("get-else"), @@ -1240,13 +1328,8 @@ fn test_query_visited() { List(LinkedList::from_iter(vec![ s_plain("if"), s_plain("since"), - Vector(vec![ - s_plain("$"), - s_plain("?since"), - ]), - Vector(vec![ - s_plain("$"), - ]), + Vector(vec![s_plain("$"), s_plain("?since")]), + Vector(vec![s_plain("$")]), ])), k_plain("where"), s_plain("where"), @@ -1282,11 +1365,7 @@ fn test_query_find_title() { s_plain("$"), s_plain("?url"), k_plain("where"), - Vector(vec![ - s_plain("?page"), - k_ns("page", "url"), - s_plain("?url"), - ]), + Vector(vec![s_plain("?page"), k_ns("page", "url"), s_plain("?url")]), Vector(vec![ List(LinkedList::from_iter(vec![ s_plain("get-else"), @@ -1355,7 +1434,9 @@ fn test_spurious_commas() { #[test] fn test_utils_merge() { // Take BTreeMap instances, wrap into Value::Map instances. - let test = |left: &BTreeMap, right: &BTreeMap, expected: &BTreeMap| { + let test = |left: &BTreeMap, + right: &BTreeMap, + expected: &BTreeMap| { let l = Value::Map(left.clone()); let r = Value::Map(right.clone()); let result = utils::merge(&l, &r).unwrap(); @@ -1394,7 +1475,7 @@ macro_rules! def_test_as_value_type { assert_eq!($value.$method().unwrap(), $expected) } assert_eq!($value.$method().is_some(), $is_some); - } + }; } macro_rules! def_test_as_type { ($value: ident, $method: ident, $is_some: expr, $expected: expr) => { @@ -1402,7 +1483,7 @@ macro_rules! def_test_as_type { assert_eq!(*$value.$method().unwrap(), $expected) } assert_eq!($value.$method().is_some(), $is_some); - } + }; } macro_rules! def_test_into_type { @@ -1411,7 +1492,7 @@ macro_rules! def_test_into_type { assert_eq!($value.clone().$method().unwrap(), $expected) } assert_eq!($value.clone().$method().is_some(), $is_some); - } + }; } #[test] @@ -1434,9 +1515,10 @@ fn test_is_and_as_type_helper_functions() { Value::Vector(vec![Value::Integer(1)]), Value::List(LinkedList::from_iter(vec![])), Value::Set(BTreeSet::from_iter(vec![])), - Value::Map(BTreeMap::from_iter(vec![ - (Value::Text("a".to_string()), Value::Integer(1)), - ])), + Value::Map(BTreeMap::from_iter(vec![( + Value::Text("a".to_string()), + Value::Integer(1), + )])), ]; for (i, value) in values.iter().enumerate() { @@ -1460,7 +1542,14 @@ fn test_is_and_as_type_helper_functions() { assert_eq!(values.len(), is_result.len()); for (j, result) in is_result.iter().enumerate() { - assert_eq!(j == i, *result, "Expected {} = {} to equal {}", j, i, result); + assert_eq!( + j == i, + *result, + "Expected {} = {} to equal {}", + j, + i, + result + ); } if i == 0 { @@ -1477,16 +1566,39 @@ fn test_is_and_as_type_helper_functions() { def_test_as_type!(value, as_big_integer, i == 3, &max_i64 * &max_i64); def_test_as_type!(value, as_ordered_float, i == 4, OrderedFloat(22.22f64)); def_test_as_type!(value, as_text, i == 5, "hello world".to_string()); - def_test_as_type!(value, as_symbol, i == 6, symbols::PlainSymbol::plain("$symbol")); - def_test_as_type!(value, as_namespaced_symbol, i == 7, symbols::NamespacedSymbol::namespaced("$ns", "$symbol")); - def_test_as_type!(value, as_plain_keyword, i == 8, symbols::Keyword::plain("hello")); - def_test_as_type!(value, as_namespaced_keyword, i == 9, symbols::Keyword::namespaced("hello", "world")); + def_test_as_type!( + value, + as_symbol, + i == 6, + symbols::PlainSymbol::plain("$symbol") + ); + def_test_as_type!( + value, + as_namespaced_symbol, + i == 7, + symbols::NamespacedSymbol::namespaced("$ns", "$symbol") + ); + def_test_as_type!( + value, + as_plain_keyword, + i == 8, + symbols::Keyword::plain("hello") + ); + def_test_as_type!( + value, + as_namespaced_keyword, + i == 9, + symbols::Keyword::namespaced("hello", "world") + ); def_test_as_type!(value, as_vector, i == 10, vec![Value::Integer(1)]); def_test_as_type!(value, as_list, i == 11, LinkedList::from_iter(vec![])); def_test_as_type!(value, as_set, i == 12, BTreeSet::from_iter(vec![])); - def_test_as_type!(value, as_map, i == 13, BTreeMap::from_iter(vec![ - (Value::Text("a".to_string()), Value::Integer(1)), - ])); + def_test_as_type!( + value, + as_map, + i == 13, + BTreeMap::from_iter(vec![(Value::Text("a".to_string()), Value::Integer(1)),]) + ); def_test_into_type!(value, into_boolean, i == 1, false); def_test_into_type!(value, into_integer, i == 2, 1i64); @@ -1495,16 +1607,39 @@ fn test_is_and_as_type_helper_functions() { def_test_into_type!(value, into_big_integer, i == 3, &max_i64 * &max_i64); def_test_into_type!(value, into_ordered_float, i == 4, OrderedFloat(22.22f64)); def_test_into_type!(value, into_text, i == 5, "hello world".to_string()); - def_test_into_type!(value, into_symbol, i == 6, symbols::PlainSymbol::plain("$symbol")); - def_test_into_type!(value, into_namespaced_symbol, i == 7, symbols::NamespacedSymbol::namespaced("$ns", "$symbol")); - def_test_into_type!(value, into_plain_keyword, i == 8, symbols::Keyword::plain("hello")); - def_test_into_type!(value, into_namespaced_keyword, i == 9, symbols::Keyword::namespaced("hello", "world")); + def_test_into_type!( + value, + into_symbol, + i == 6, + symbols::PlainSymbol::plain("$symbol") + ); + def_test_into_type!( + value, + into_namespaced_symbol, + i == 7, + symbols::NamespacedSymbol::namespaced("$ns", "$symbol") + ); + def_test_into_type!( + value, + into_plain_keyword, + i == 8, + symbols::Keyword::plain("hello") + ); + def_test_into_type!( + value, + into_namespaced_keyword, + i == 9, + symbols::Keyword::namespaced("hello", "world") + ); def_test_into_type!(value, into_vector, i == 10, vec![Value::Integer(1)]); def_test_into_type!(value, into_list, i == 11, LinkedList::from_iter(vec![])); def_test_into_type!(value, into_set, i == 12, BTreeSet::from_iter(vec![])); - def_test_into_type!(value, into_map, i == 13, BTreeMap::from_iter(vec![ - (Value::Text("a".to_string()), Value::Integer(1)), - ])); + def_test_into_type!( + value, + into_map, + i == 13, + BTreeMap::from_iter(vec![(Value::Text("a".to_string()), Value::Integer(1)),]) + ); } } diff --git a/ffi/src/android.rs b/ffi/src/android.rs index acf9cd04..d5f4301f 100644 --- a/ffi/src/android.rs +++ b/ffi/src/android.rs @@ -21,4 +21,6 @@ pub enum LogLevel { 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; +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index c1fb63b7..d568a3a6 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -69,73 +69,35 @@ //! `translate_result`, `translate_opt_result` (for `Result>`) and `translate_void_result` //! (for `Result<(), T>`). Callers are responsible for freeing the `message` field of `ExternError`. +#![allow(unused_doc_comments)] + extern crate core; extern crate libc; extern crate mentat; use core::fmt::Display; -use std::collections::{ - BTreeSet, -}; -use std::os::raw::{ - c_char, - c_int, - c_longlong, - c_ulonglong, - c_void, -}; -use std::slice; -use std::sync::{ - Arc, -}; -use std::vec; +use std::collections::BTreeSet; use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_longlong, c_ulonglong, c_void}; +use std::slice; +use std::sync::Arc; +use std::vec; pub use mentat::{ - Binding, - CacheDirection, - Entid, - FindSpec, - HasSchema, - InProgress, - KnownEntid, - Queryable, - QueryBuilder, - QueryInputs, - QueryOutput, - QueryResults, - RelResult, - Store, - TypedValue, - TxObserver, - TxReport, - Uuid, - ValueType, - Variable, + Binding, CacheDirection, Entid, FindSpec, HasSchema, InProgress, KnownEntid, QueryBuilder, + QueryInputs, QueryOutput, QueryResults, Queryable, RelResult, Store, TxObserver, TxReport, + TypedValue, Uuid, ValueType, Variable, }; -pub use mentat::entity_builder::{ - BuildTerms, - EntityBuilder, - InProgressBuilder, -}; +pub use mentat::entity_builder::{BuildTerms, EntityBuilder, InProgressBuilder}; pub mod android; pub mod utils; -pub use utils::strings::{ - c_char_to_string, - kw_from_string, - string_to_c_char, -}; +pub use utils::strings::{c_char_to_string, kw_from_string, string_to_c_char}; -use utils::error::{ - ExternError, - translate_result, - translate_opt_result, - translate_void_result, -}; +use utils::error::{translate_opt_result, translate_result, translate_void_result, ExternError}; pub use utils::log; @@ -190,7 +152,7 @@ impl<'a, 'c> InProgressTransactResult<'a, 'c> { InProgressTransactResult { in_progress: Box::into_raw(Box::new(in_progress)), tx_report, - err + err, } } } @@ -216,7 +178,11 @@ pub unsafe extern "C" fn store_open(uri: *const c_char, error: *mut ExternError) /// Variant of store_open that opens an encrypted database. #[cfg(feature = "sqlcipher")] #[no_mangle] -pub unsafe extern "C" fn store_open_encrypted(uri: *const c_char, key: *const c_char, error: *mut ExternError) -> *mut Store { +pub unsafe extern "C" fn store_open_encrypted( + uri: *const c_char, + key: *const c_char, + error: *mut ExternError, +) -> *mut Store { let uri = c_char_to_string(uri); let key = c_char_to_string(key); translate_result(Store::open_with_key(&uri, &key), error) @@ -244,7 +210,10 @@ pub unsafe extern "C" fn store_open_encrypted(uri: *const c_char, key: *const c_ /// /// TODO: Document the errors that can result from begin_transaction #[no_mangle] -pub unsafe extern "C" fn store_begin_transaction<'a, 'c>(store: *mut Store, error: *mut ExternError) -> *mut InProgress<'a, 'c> { +pub unsafe extern "C" fn store_begin_transaction<'a, 'c>( + store: *mut Store, + error: *mut ExternError, +) -> *mut InProgress<'a, 'c> { assert_not_null!(store); let store = &mut *store; translate_result(store.begin_transaction(), error) @@ -263,7 +232,11 @@ pub unsafe extern "C" fn store_begin_transaction<'a, 'c>(store: *mut Store, erro /// /// TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn in_progress_transact<'m>(in_progress: *mut InProgress<'m, 'm>, transaction: *const c_char, error: *mut ExternError) -> *mut TxReport { +pub unsafe extern "C" fn in_progress_transact<'m>( + in_progress: *mut InProgress<'m, 'm>, + transaction: *const c_char, + error: *mut ExternError, +) -> *mut TxReport { assert_not_null!(in_progress); let in_progress = &mut *in_progress; let transaction = c_char_to_string(transaction); @@ -275,7 +248,10 @@ pub unsafe extern "C" fn in_progress_transact<'m>(in_progress: *mut InProgress<' /// /// TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn in_progress_commit<'m>(in_progress: *mut InProgress<'m, 'm>, error: *mut ExternError) { +pub unsafe extern "C" fn in_progress_commit<'m>( + in_progress: *mut InProgress<'m, 'm>, + error: *mut ExternError, +) { assert_not_null!(in_progress); let in_progress = Box::from_raw(in_progress); translate_void_result(in_progress.commit(), error); @@ -286,7 +262,10 @@ pub unsafe extern "C" fn in_progress_commit<'m>(in_progress: *mut InProgress<'m, /// /// TODO: Document the errors that can result from rollback #[no_mangle] -pub unsafe extern "C" fn in_progress_rollback<'m>(in_progress: *mut InProgress<'m, 'm>, error: *mut ExternError) { +pub unsafe extern "C" fn in_progress_rollback<'m>( + in_progress: *mut InProgress<'m, 'm>, + error: *mut ExternError, +) { assert_not_null!(in_progress); let in_progress = Box::from_raw(in_progress); translate_void_result(in_progress.rollback(), error); @@ -301,7 +280,9 @@ pub unsafe extern "C" fn in_progress_rollback<'m>(in_progress: *mut InProgress<' /// A destructor `in_progress_builder_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn in_progress_builder<'m>(in_progress: *mut InProgress<'m, 'm>) -> *mut InProgressBuilder { +pub unsafe extern "C" fn in_progress_builder<'m>( + in_progress: *mut InProgress<'m, 'm>, +) -> *mut InProgressBuilder { assert_not_null!(in_progress); let in_progress = Box::from_raw(in_progress); Box::into_raw(Box::new(in_progress.builder())) @@ -316,7 +297,10 @@ pub unsafe extern "C" fn in_progress_builder<'m>(in_progress: *mut InProgress<'m /// A destructor `entity_builder_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn in_progress_entity_builder_from_temp_id<'m>(in_progress: *mut InProgress<'m, 'm>, temp_id: *const c_char) -> *mut EntityBuilder { +pub unsafe extern "C" fn in_progress_entity_builder_from_temp_id<'m>( + in_progress: *mut InProgress<'m, 'm>, + temp_id: *const c_char, +) -> *mut EntityBuilder { assert_not_null!(in_progress); let in_progress = Box::from_raw(in_progress); let temp_id = c_char_to_string(temp_id); @@ -332,7 +316,10 @@ pub unsafe extern "C" fn in_progress_entity_builder_from_temp_id<'m>(in_progress /// A destructor `entity_builder_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn in_progress_entity_builder_from_entid<'m>(in_progress: *mut InProgress<'m, 'm>, entid: c_longlong) -> *mut EntityBuilder { +pub unsafe extern "C" fn in_progress_entity_builder_from_entid<'m>( + in_progress: *mut InProgress<'m, 'm>, + entid: c_longlong, +) -> *mut EntityBuilder { assert_not_null!(in_progress); let in_progress = Box::from_raw(in_progress); Box::into_raw(Box::new(in_progress.builder().describe(KnownEntid(entid)))) @@ -347,12 +334,15 @@ pub unsafe extern "C" fn in_progress_entity_builder_from_entid<'m>(in_progress: /// A destructor `in_progress_builder_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn store_in_progress_builder<'a, 'c>(store: *mut Store, error: *mut ExternError) -> *mut InProgressBuilder<'a, 'c> { +pub unsafe extern "C" fn store_in_progress_builder<'a, 'c>( + store: *mut Store, + error: *mut ExternError, +) -> *mut InProgressBuilder<'a, 'c> { assert_not_null!(store); let store = &mut *store; - let result = store.begin_transaction().and_then(|in_progress| { - Ok(in_progress.builder()) - }); + let result = store + .begin_transaction() + .and_then(|in_progress| Ok(in_progress.builder())); translate_result(result, error) } @@ -365,13 +355,17 @@ pub unsafe extern "C" fn store_in_progress_builder<'a, 'c>(store: *mut Store, er /// A destructor `entity_builder_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn store_entity_builder_from_temp_id<'a, 'c>(store: *mut Store, temp_id: *const c_char, error: *mut ExternError) -> *mut EntityBuilder> { +pub unsafe extern "C" fn store_entity_builder_from_temp_id<'a, 'c>( + store: *mut Store, + temp_id: *const c_char, + error: *mut ExternError, +) -> *mut EntityBuilder> { assert_not_null!(store); let store = &mut *store; let temp_id = c_char_to_string(temp_id); - let result = store.begin_transaction().and_then(|in_progress| { - Ok(in_progress.builder().describe_tempid(&temp_id)) - }); + let result = store + .begin_transaction() + .and_then(|in_progress| Ok(in_progress.builder().describe_tempid(&temp_id))); translate_result(result, error) } @@ -384,12 +378,16 @@ pub unsafe extern "C" fn store_entity_builder_from_temp_id<'a, 'c>(store: *mut S /// A destructor `entity_builder_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn store_entity_builder_from_entid<'a, 'c>(store: *mut Store, entid: c_longlong, error: *mut ExternError) -> *mut EntityBuilder> { +pub unsafe extern "C" fn store_entity_builder_from_entid<'a, 'c>( + store: *mut Store, + entid: c_longlong, + error: *mut ExternError, +) -> *mut EntityBuilder> { assert_not_null!(store); let store = &mut *store; - let result = store.begin_transaction().and_then(|in_progress| { - Ok(in_progress.builder().describe(KnownEntid(entid))) - }); + let result = store + .begin_transaction() + .and_then(|in_progress| Ok(in_progress.builder().describe(KnownEntid(entid)))); translate_result(result, error) } @@ -408,7 +406,7 @@ pub unsafe extern "C" fn in_progress_builder_add_string<'a, 'c>( entid: c_longlong, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -431,7 +429,7 @@ pub unsafe extern "C" fn in_progress_builder_add_long<'a, 'c>( entid: c_longlong, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -455,7 +453,7 @@ pub unsafe extern "C" fn in_progress_builder_add_ref<'a, 'c>( entid: c_longlong, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -480,7 +478,7 @@ pub unsafe extern "C" fn in_progress_builder_add_keyword<'a, 'c>( entid: c_longlong, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -504,7 +502,7 @@ pub unsafe extern "C" fn in_progress_builder_add_boolean<'a, 'c>( entid: c_longlong, kw: *const c_char, value: bool, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -528,7 +526,7 @@ pub unsafe extern "C" fn in_progress_builder_add_double<'a, 'c>( entid: c_longlong, kw: *const c_char, value: f64, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -552,7 +550,7 @@ pub unsafe extern "C" fn in_progress_builder_add_timestamp<'a, 'c>( entid: c_longlong, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -576,13 +574,13 @@ pub unsafe extern "C" fn in_progress_builder_add_uuid<'a, 'c>( entid: c_longlong, kw: *const c_char, value: *const [u8; 16], - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder, value); let builder = &mut *builder; let kw = kw_from_string(c_char_to_string(kw)); let value = &*value; - let value = Uuid::from_bytes(value).expect("valid uuid"); + let value = Uuid::from_slice(value).expect("valid uuid"); let value: TypedValue = value.into(); translate_void_result(builder.add(KnownEntid(entid), kw, value), error); } @@ -602,7 +600,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_string<'a, 'c>( entid: c_longlong, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -626,7 +624,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_long<'a, 'c>( entid: c_longlong, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -650,7 +648,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_ref<'a, 'c>( entid: c_longlong, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -659,7 +657,6 @@ pub unsafe extern "C" fn in_progress_builder_retract_ref<'a, 'c>( translate_void_result(builder.retract(KnownEntid(entid), kw, value), error); } - /// Uses `builder` to retract `value` for `kw` on entity `entid`. /// /// # Errors @@ -675,7 +672,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_keyword<'a, 'c>( entid: c_longlong, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -699,7 +696,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_boolean<'a, 'c>( entid: c_longlong, kw: *const c_char, value: bool, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -723,7 +720,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_double<'a, 'c>( entid: c_longlong, kw: *const c_char, value: f64, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -747,7 +744,7 @@ pub unsafe extern "C" fn in_progress_builder_retract_timestamp<'a, 'c>( entid: c_longlong, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -773,13 +770,13 @@ pub unsafe extern "C" fn in_progress_builder_retract_uuid<'a, 'c>( entid: c_longlong, kw: *const c_char, value: *const [u8; 16], - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder, value); let builder = &mut *builder; let kw = kw_from_string(c_char_to_string(kw)); let value = &*value; - let value = Uuid::from_bytes(value).expect("valid uuid"); + let value = Uuid::from_slice(value).expect("valid uuid"); let value: TypedValue = value.into(); translate_void_result(builder.retract(KnownEntid(entid), kw, value), error); } @@ -791,7 +788,10 @@ pub unsafe extern "C" fn in_progress_builder_retract_uuid<'a, 'c>( /// // TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn in_progress_builder_commit<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>, error: *mut ExternError) -> *mut TxReport { +pub unsafe extern "C" fn in_progress_builder_commit<'a, 'c>( + builder: *mut InProgressBuilder<'a, 'c>, + error: *mut ExternError, +) -> *mut TxReport { assert_not_null!(builder); let builder = Box::from_raw(builder); translate_result(builder.commit(), error) @@ -812,7 +812,9 @@ pub unsafe extern "C" fn in_progress_builder_commit<'a, 'c>(builder: *mut InProg /// // TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn in_progress_builder_transact<'a, 'c>(builder: *mut InProgressBuilder<'a, 'c>) -> InProgressTransactResult<'a, 'c> { +pub unsafe extern "C" fn in_progress_builder_transact<'a, 'c>( + builder: *mut InProgressBuilder<'a, 'c>, +) -> InProgressTransactResult<'a, 'c> { assert_not_null!(builder); let builder = Box::from_raw(builder); InProgressTransactResult::from_transact(builder.transact()) @@ -832,7 +834,7 @@ pub unsafe extern "C" fn entity_builder_add_string<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -855,7 +857,7 @@ pub unsafe extern "C" fn entity_builder_add_long<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -878,7 +880,7 @@ pub unsafe extern "C" fn entity_builder_add_ref<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -901,7 +903,7 @@ pub unsafe extern "C" fn entity_builder_add_keyword<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -924,7 +926,7 @@ pub unsafe extern "C" fn entity_builder_add_boolean<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: bool, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -947,7 +949,7 @@ pub unsafe extern "C" fn entity_builder_add_double<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: f64, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -970,7 +972,7 @@ pub unsafe extern "C" fn entity_builder_add_timestamp<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -993,13 +995,13 @@ pub unsafe extern "C" fn entity_builder_add_uuid<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: *const [u8; 16], - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; let kw = kw_from_string(c_char_to_string(kw)); let value = &*value; - let value = Uuid::from_bytes(value).expect("valid uuid"); + let value = Uuid::from_slice(value).expect("valid uuid"); let value: TypedValue = value.into(); translate_void_result(builder.add(kw, value), error); } @@ -1018,7 +1020,7 @@ pub unsafe extern "C" fn entity_builder_retract_string<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1041,7 +1043,7 @@ pub unsafe extern "C" fn entity_builder_retract_long<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1064,7 +1066,7 @@ pub unsafe extern "C" fn entity_builder_retract_ref<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1087,7 +1089,7 @@ pub unsafe extern "C" fn entity_builder_retract_keyword<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: *const c_char, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1110,7 +1112,7 @@ pub unsafe extern "C" fn entity_builder_retract_boolean<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: bool, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1133,7 +1135,7 @@ pub unsafe extern "C" fn entity_builder_retract_double<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: f64, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1156,7 +1158,7 @@ pub unsafe extern "C" fn entity_builder_retract_timestamp<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: c_longlong, - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder); let builder = &mut *builder; @@ -1180,13 +1182,13 @@ pub unsafe extern "C" fn entity_builder_retract_uuid<'a, 'c>( builder: *mut EntityBuilder>, kw: *const c_char, value: *const [u8; 16], - error: *mut ExternError + error: *mut ExternError, ) { assert_not_null!(builder, value); let builder = &mut *builder; let kw = kw_from_string(c_char_to_string(kw)); let value = &*value; - let value = Uuid::from_bytes(value).expect("valid uuid"); + let value = Uuid::from_slice(value).expect("valid uuid"); let value: TypedValue = value.into(); translate_void_result(builder.retract(kw, value), error); } @@ -1206,7 +1208,9 @@ pub unsafe extern "C" fn entity_builder_retract_uuid<'a, 'c>( /// /// TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn entity_builder_transact<'a, 'c>(builder: *mut EntityBuilder>) -> InProgressTransactResult<'a, 'c> { +pub unsafe extern "C" fn entity_builder_transact<'a, 'c>( + builder: *mut EntityBuilder>, +) -> InProgressTransactResult<'a, 'c> { assert_not_null!(builder); let builder = Box::from_raw(builder); InProgressTransactResult::from_transact(builder.transact()) @@ -1219,7 +1223,10 @@ pub unsafe extern "C" fn entity_builder_transact<'a, 'c>(builder: *mut EntityBui /// /// TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn entity_builder_commit<'a, 'c>(builder: *mut EntityBuilder>, error: *mut ExternError) -> *mut TxReport { +pub unsafe extern "C" fn entity_builder_commit<'a, 'c>( + builder: *mut EntityBuilder>, + error: *mut ExternError, +) -> *mut TxReport { assert_not_null!(builder); let builder = Box::from_raw(builder); translate_result(builder.commit(), error) @@ -1229,15 +1236,18 @@ pub unsafe extern "C" fn entity_builder_commit<'a, 'c>(builder: *mut EntityBuild /// /// TODO: Document the errors that can result from transact #[no_mangle] -pub unsafe extern "C" fn store_transact(store: *mut Store, transaction: *const c_char, error: *mut ExternError) -> *mut TxReport { +pub unsafe extern "C" fn store_transact( + store: *mut Store, + transaction: *const c_char, + error: *mut ExternError, +) -> *mut TxReport { assert_not_null!(store); let store = &mut *store; let transaction = c_char_to_string(transaction); let result = store.begin_transaction().and_then(|mut in_progress| { - in_progress.transact(transaction).and_then(|tx_report| { - in_progress.commit() - .map(|_| tx_report) - }) + in_progress + .transact(transaction) + .and_then(|tx_report| in_progress.commit().map(|_| tx_report)) }); translate_result(result, error) } @@ -1265,7 +1275,10 @@ pub unsafe extern "C" fn tx_report_get_tx_instant(tx_report: *mut TxReport) -> c /// freeing with `destroy()`. // TODO: This is gross and unnecessary #[no_mangle] -pub unsafe extern "C" fn tx_report_entity_for_temp_id(tx_report: *mut TxReport, tempid: *const c_char) -> *mut c_longlong { +pub unsafe extern "C" fn tx_report_entity_for_temp_id( + tx_report: *mut TxReport, + tempid: *const c_char, +) -> *mut c_longlong { assert_not_null!(tx_report); let tx_report = &*tx_report; let key = c_char_to_string(tempid); @@ -1280,7 +1293,11 @@ pub unsafe extern "C" fn tx_report_entity_for_temp_id(tx_report: *mut TxReport, /// `store_cache_attribute_forward` caches values for an attribute keyed by entity /// (i.e. find values and entities that have this attribute, or find values of attribute for an entity) #[no_mangle] -pub unsafe extern "C" fn store_cache_attribute_forward(store: *mut Store, attribute: *const c_char, error: *mut ExternError) { +pub unsafe extern "C" fn store_cache_attribute_forward( + store: *mut Store, + attribute: *const c_char, + error: *mut ExternError, +) { assert_not_null!(store); let store = &mut *store; let kw = kw_from_string(c_char_to_string(attribute)); @@ -1291,7 +1308,11 @@ pub unsafe extern "C" fn store_cache_attribute_forward(store: *mut Store, attrib /// `store_cache_attribute_reverse` caches entities for an attribute keyed by value. /// (i.e. find entities that have a particular value for an attribute). #[no_mangle] -pub unsafe extern "C" fn store_cache_attribute_reverse(store: *mut Store, attribute: *const c_char, error: *mut ExternError) { +pub unsafe extern "C" fn store_cache_attribute_reverse( + store: *mut Store, + attribute: *const c_char, + error: *mut ExternError, +) { assert_not_null!(store); let store = &mut *store; let kw = kw_from_string(c_char_to_string(attribute)); @@ -1307,7 +1328,11 @@ pub unsafe extern "C" fn store_cache_attribute_reverse(store: *mut Store, attrib /// `Reverse` caches entities for an attribute keyed by value. /// (i.e. find entities that have a particular value for an attribute). #[no_mangle] -pub unsafe extern "C" fn store_cache_attribute_bi_directional(store: *mut Store, attribute: *const c_char, error: *mut ExternError) { +pub unsafe extern "C" fn store_cache_attribute_bi_directional( + store: *mut Store, + attribute: *const c_char, + error: *mut ExternError, +) { assert_not_null!(store); let store = &mut *store; let kw = kw_from_string(c_char_to_string(attribute)); @@ -1324,7 +1349,10 @@ pub unsafe extern "C" fn store_cache_attribute_bi_directional(store: *mut Store, /// /// TODO: Update QueryBuilder so it only takes a [Store](mentat::Store) pointer on execution #[no_mangle] -pub unsafe extern "C" fn store_query<'a>(store: *mut Store, query: *const c_char) -> *mut QueryBuilder<'a> { +pub unsafe extern "C" fn store_query<'a>( + store: *mut Store, + query: *const c_char, +) -> *mut QueryBuilder<'a> { assert_not_null!(store); let query = c_char_to_string(query); let store = &mut *store; @@ -1335,7 +1363,11 @@ pub unsafe extern "C" fn store_query<'a>(store: *mut Store, query: *const c_char /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_long(query_builder: *mut QueryBuilder, var: *const c_char, value: c_longlong) { +pub unsafe extern "C" fn query_builder_bind_long( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: c_longlong, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let query_builder = &mut *query_builder; @@ -1346,7 +1378,11 @@ pub unsafe extern "C" fn query_builder_bind_long(query_builder: *mut QueryBuilde /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_ref(query_builder: *mut QueryBuilder, var: *const c_char, value: c_longlong) { +pub unsafe extern "C" fn query_builder_bind_ref( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: c_longlong, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let query_builder = &mut *query_builder; @@ -1362,7 +1398,11 @@ pub unsafe extern "C" fn query_builder_bind_ref(query_builder: *mut QueryBuilder /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_ref_kw(query_builder: *mut QueryBuilder, var: *const c_char, value: *const c_char) { +pub unsafe extern "C" fn query_builder_bind_ref_kw( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: *const c_char, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let kw = kw_from_string(c_char_to_string(value)); @@ -1377,7 +1417,11 @@ pub unsafe extern "C" fn query_builder_bind_ref_kw(query_builder: *mut QueryBuil /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_kw(query_builder: *mut QueryBuilder, var: *const c_char, value: *const c_char) { +pub unsafe extern "C" fn query_builder_bind_kw( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: *const c_char, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let query_builder = &mut *query_builder; @@ -1389,7 +1433,11 @@ pub unsafe extern "C" fn query_builder_bind_kw(query_builder: *mut QueryBuilder, /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_boolean(query_builder: *mut QueryBuilder, var: *const c_char, value: bool) { +pub unsafe extern "C" fn query_builder_bind_boolean( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: bool, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let query_builder = &mut *query_builder; @@ -1400,7 +1448,11 @@ pub unsafe extern "C" fn query_builder_bind_boolean(query_builder: *mut QueryBui /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_double(query_builder: *mut QueryBuilder, var: *const c_char, value: f64) { +pub unsafe extern "C" fn query_builder_bind_double( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: f64, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let query_builder = &mut *query_builder; @@ -1412,7 +1464,11 @@ pub unsafe extern "C" fn query_builder_bind_double(query_builder: *mut QueryBuil /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_timestamp(query_builder: *mut QueryBuilder, var: *const c_char, value: c_longlong) { +pub unsafe extern "C" fn query_builder_bind_timestamp( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: c_longlong, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let query_builder = &mut *query_builder; @@ -1423,7 +1479,11 @@ pub unsafe extern "C" fn query_builder_bind_timestamp(query_builder: *mut QueryB /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_string(query_builder: *mut QueryBuilder, var: *const c_char, value: *const c_char) { +pub unsafe extern "C" fn query_builder_bind_string( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: *const c_char, +) { assert_not_null!(query_builder); let var = c_char_to_string(var); let value = c_char_to_string(value); @@ -1436,11 +1496,15 @@ pub unsafe extern "C" fn query_builder_bind_string(query_builder: *mut QueryBuil /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn query_builder_bind_uuid(query_builder: *mut QueryBuilder, var: *const c_char, value: *const [u8; 16]) { +pub unsafe extern "C" fn query_builder_bind_uuid( + query_builder: *mut QueryBuilder, + var: *const c_char, + value: *const [u8; 16], +) { assert_not_null!(query_builder, value); let var = c_char_to_string(var); let value = &*value; - let value = Uuid::from_bytes(value).expect("valid uuid"); + let value = Uuid::from_slice(value).expect("valid uuid"); let query_builder = &mut *query_builder; query_builder.bind_value(&var, value); } @@ -1457,7 +1521,10 @@ pub unsafe extern "C" fn query_builder_bind_uuid(query_builder: *mut QueryBuilde /// A destructor `typed_value_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn query_builder_execute_scalar(query_builder: *mut QueryBuilder, error: *mut ExternError) -> *mut Binding { +pub unsafe extern "C" fn query_builder_execute_scalar( + query_builder: *mut QueryBuilder, + error: *mut ExternError, +) -> *mut Binding { assert_not_null!(query_builder); let query_builder = &mut *query_builder; let results = query_builder.execute_scalar(); @@ -1476,7 +1543,10 @@ pub unsafe extern "C" fn query_builder_execute_scalar(query_builder: *mut QueryB /// A destructor `typed_value_list_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn query_builder_execute_coll(query_builder: *mut QueryBuilder, error: *mut ExternError) -> *mut Vec { +pub unsafe extern "C" fn query_builder_execute_coll( + query_builder: *mut QueryBuilder, + error: *mut ExternError, +) -> *mut Vec { assert_not_null!(query_builder); let query_builder = &mut *query_builder; let results = query_builder.execute_coll(); @@ -1495,7 +1565,10 @@ pub unsafe extern "C" fn query_builder_execute_coll(query_builder: *mut QueryBui /// A destructor `typed_value_list_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn query_builder_execute_tuple(query_builder: *mut QueryBuilder, error: *mut ExternError) -> *mut Vec { +pub unsafe extern "C" fn query_builder_execute_tuple( + query_builder: *mut QueryBuilder, + error: *mut ExternError, +) -> *mut Vec { assert_not_null!(query_builder); let query_builder = &mut *query_builder; let results = query_builder.execute_tuple(); @@ -1514,7 +1587,10 @@ pub unsafe extern "C" fn query_builder_execute_tuple(query_builder: *mut QueryBu /// A destructor `typed_value_result_set_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn query_builder_execute(query_builder: *mut QueryBuilder, error: *mut ExternError) -> *mut RelResult { +pub unsafe extern "C" fn query_builder_execute( + query_builder: *mut QueryBuilder, + error: *mut ExternError, +) -> *mut RelResult { assert_not_null!(query_builder); let query_builder = &mut *query_builder; let results = query_builder.execute_rel(); @@ -1524,7 +1600,7 @@ pub unsafe extern "C" fn query_builder_execute(query_builder: *mut QueryBuilder, fn unwrap_conversion(value: Option, expected_type: ValueType) -> T { match value { Some(v) => v, - None => panic!("Typed value cannot be coerced into a {}", expected_type) + None => panic!("Typed value cannot be coerced into a {}", expected_type), } } @@ -1586,7 +1662,11 @@ pub unsafe extern "C" fn typed_value_into_kw(typed_value: *mut Binding) -> *mut pub unsafe extern "C" fn typed_value_into_boolean(typed_value: *mut Binding) -> i32 { assert_not_null!(typed_value); let typed_value = Box::from_raw(typed_value); - if unwrap_conversion(typed_value.into_boolean(), ValueType::Boolean) { 1 } else { 0 } + if unwrap_conversion(typed_value.into_boolean(), ValueType::Boolean) { + 1 + } else { + 0 + } } /// Consumes a [Binding](mentat::Binding) and returns the value as a `f64`. @@ -1655,7 +1735,9 @@ pub unsafe extern "C" fn typed_value_into_uuid(typed_value: *mut Binding) -> *mu #[no_mangle] pub unsafe extern "C" fn typed_value_value_type(typed_value: *mut Binding) -> ValueType { let typed_value = &*typed_value; - typed_value.value_type().unwrap_or_else(|| panic!("Binding is not Scalar and has no ValueType")) + typed_value + .value_type() + .unwrap_or_else(|| panic!("Binding is not Scalar and has no ValueType")) } /// Returns the value at the provided `index` as a `Vec`. @@ -1667,10 +1749,15 @@ pub unsafe extern "C" fn typed_value_value_type(typed_value: *mut Binding) -> Va /// A destructor `typed_value_result_set_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn row_at_index(rows: *mut RelResult, index: c_int) -> *mut Vec { +pub unsafe extern "C" fn row_at_index( + rows: *mut RelResult, + index: c_int, +) -> *mut Vec { assert_not_null!(rows); let result = &*rows; - result.row(index as usize).map_or_else(std::ptr::null_mut, |v| Box::into_raw(Box::new(v.to_vec()))) + result + .row(index as usize) + .map_or_else(std::ptr::null_mut, |v| Box::into_raw(Box::new(v.to_vec()))) } /// Consumes the `RelResult` and returns an iterator over the values. @@ -1681,7 +1768,9 @@ pub unsafe extern "C" fn row_at_index(rows: *mut RelResult, index: c_in /// A destructor `typed_value_result_set_iter_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn typed_value_result_set_into_iter(rows: *mut RelResult) -> *mut BindingListIterator { +pub unsafe extern "C" fn typed_value_result_set_into_iter( + rows: *mut RelResult, +) -> *mut BindingListIterator { assert_not_null!(rows); let result = &*rows; let rows = result.rows(); @@ -1697,10 +1786,14 @@ pub unsafe extern "C" fn typed_value_result_set_into_iter(rows: *mut RelResult *mut Vec { +pub unsafe extern "C" fn typed_value_result_set_iter_next( + iter: *mut BindingListIterator, +) -> *mut Vec { assert_not_null!(iter); let iter = &mut *iter; - iter.next().map_or(std::ptr::null_mut(), |v| Box::into_raw(Box::new(v.to_vec()))) + iter.next().map_or(std::ptr::null_mut(), |v| { + Box::into_raw(Box::new(v.to_vec())) + }) } /// Consumes the `Vec` and returns an iterator over the values. @@ -1711,7 +1804,9 @@ pub unsafe extern "C" fn typed_value_result_set_iter_next(iter: *mut BindingList /// A destructor `typed_value_list_iter_destroy` is provided for releasing the memory for this /// pointer type. #[no_mangle] -pub unsafe extern "C" fn typed_value_list_into_iter(values: *mut Vec) -> *mut BindingIterator { +pub unsafe extern "C" fn typed_value_list_into_iter( + values: *mut Vec, +) -> *mut BindingIterator { assert_not_null!(values); let result = Box::from_raw(values); Box::into_raw(Box::new(result.into_iter())) @@ -1729,7 +1824,8 @@ pub unsafe extern "C" fn typed_value_list_into_iter(values: *mut Vec) - pub unsafe extern "C" fn typed_value_list_iter_next(iter: *mut BindingIterator) -> *mut Binding { assert_not_null!(iter); let iter = &mut *iter; - iter.next().map_or(std::ptr::null_mut(), |v| Box::into_raw(Box::new(v))) + iter.next() + .map_or(std::ptr::null_mut(), |v| Box::into_raw(Box::new(v))) } /// Returns the value at the provided `index` as a [Binding](mentat::Binding). @@ -1763,7 +1859,10 @@ pub unsafe extern "C" fn value_at_index(values: *mut Vec, index: c_int) /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_long(values: *mut Vec, index: c_int) -> c_longlong { +pub unsafe extern "C" fn value_at_index_into_long( + values: *mut Vec, + index: c_int, +) -> c_longlong { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1779,7 +1878,10 @@ pub unsafe extern "C" fn value_at_index_into_long(values: *mut Vec, ind /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_entid(values: *mut Vec, index: c_int) -> Entid { +pub unsafe extern "C" fn value_at_index_into_entid( + values: *mut Vec, + index: c_int, +) -> Entid { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1795,7 +1897,10 @@ pub unsafe extern "C" fn value_at_index_into_entid(values: *mut Vec, in /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_kw(values: *mut Vec, index: c_int) -> *mut c_char { +pub unsafe extern "C" fn value_at_index_into_kw( + values: *mut Vec, + index: c_int, +) -> *mut c_char { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1813,11 +1918,18 @@ pub unsafe extern "C" fn value_at_index_into_kw(values: *mut Vec, index /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_boolean(values: *mut Vec, index: c_int) -> i32 { +pub unsafe extern "C" fn value_at_index_into_boolean( + values: *mut Vec, + index: c_int, +) -> i32 { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); - if unwrap_conversion(value.clone().into_boolean(), ValueType::Boolean) { 1 } else { 0 } + if unwrap_conversion(value.clone().into_boolean(), ValueType::Boolean) { + 1 + } else { + 0 + } } /// Returns the value of the [Binding](mentat::Binding) at `index` as an `f64`. @@ -1829,7 +1941,10 @@ pub unsafe extern "C" fn value_at_index_into_boolean(values: *mut Vec, /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_double(values: *mut Vec, index: c_int) -> f64 { +pub unsafe extern "C" fn value_at_index_into_double( + values: *mut Vec, + index: c_int, +) -> f64 { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1845,7 +1960,10 @@ pub unsafe extern "C" fn value_at_index_into_double(values: *mut Vec, i /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_timestamp(values: *mut Vec, index: c_int) -> c_longlong { +pub unsafe extern "C" fn value_at_index_into_timestamp( + values: *mut Vec, + index: c_int, +) -> c_longlong { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1861,7 +1979,10 @@ pub unsafe extern "C" fn value_at_index_into_timestamp(values: *mut Vec /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_string(values: *mut Vec, index: c_int) -> *mut c_char { +pub unsafe extern "C" fn value_at_index_into_string( + values: *mut Vec, + index: c_int, +) -> *mut c_char { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1877,7 +1998,10 @@ pub unsafe extern "C" fn value_at_index_into_string(values: *mut Vec, i /// // TODO Generalise with macro https://github.com/mozilla/mentat/issues/703 #[no_mangle] -pub unsafe extern "C" fn value_at_index_into_uuid(values: *mut Vec, index: c_int) -> *mut [u8; 16] { +pub unsafe extern "C" fn value_at_index_into_uuid( + values: *mut Vec, + index: c_int, +) -> *mut [u8; 16] { assert_not_null!(values); let result = &*values; let value = result.get(index as usize).expect("No value at index"); @@ -1901,11 +2025,18 @@ pub unsafe extern "C" fn value_at_index_into_uuid(values: *mut Vec, ind /// /// TODO: list the types of error that can be caused by this function #[no_mangle] -pub unsafe extern "C" fn store_value_for_attribute(store: *mut Store, entid: c_longlong, attribute: *const c_char, error: *mut ExternError) -> *mut Binding { +pub unsafe extern "C" fn store_value_for_attribute( + store: *mut Store, + entid: c_longlong, + attribute: *const c_char, + error: *mut ExternError, +) -> *mut Binding { assert_not_null!(store); let store = &*store; let kw = kw_from_string(c_char_to_string(attribute)); - let result = store.lookup_value_for_attribute(entid, &kw).map(|o| o.map(Binding::from)); + let result = store + .lookup_value_for_attribute(entid, &kw) + .map(|o| o.map(Binding::from)); translate_opt_result(result, error) } @@ -1918,11 +2049,13 @@ pub unsafe extern "C" fn store_value_for_attribute(store: *mut Store, entid: c_l /// If there is no [Attribute](mentat::Attribute) in the [Schema](mentat::Schema) for a given `attribute`. /// #[no_mangle] -pub unsafe extern "C" fn store_register_observer(store: *mut Store, - key: *const c_char, - attributes: *const Entid, - attributes_len: usize, - callback: extern fn(key: *const c_char, reports: &TxChangeList)) { +pub unsafe extern "C" fn store_register_observer( + store: *mut Store, + key: *const c_char, + attributes: *const Entid, + attributes_len: usize, + callback: extern "C" fn(key: *const c_char, reports: &TxChangeList), +) { assert_not_null!(store, attributes); let store = &mut *store; let mut attribute_set = BTreeSet::new(); @@ -1930,16 +2063,23 @@ pub unsafe extern "C" fn store_register_observer(store: *mut Store, attribute_set.extend(slice.iter()); let key = c_char_to_string(key); let tx_observer = Arc::new(TxObserver::new(attribute_set, move |obs_key, batch| { - let reports: Vec<(Entid, Vec)> = batch.into_iter().map(|(tx_id, changes)| { - (*tx_id, changes.into_iter().map(|eid| *eid as c_longlong).collect()) - }).collect(); - let extern_reports = reports.iter().map(|item| { - TransactionChange { + let reports: Vec<(Entid, Vec)> = batch + .into_iter() + .map(|(tx_id, changes)| { + ( + *tx_id, + changes.into_iter().map(|eid| *eid as c_longlong).collect(), + ) + }) + .collect(); + let extern_reports = reports + .iter() + .map(|item| TransactionChange { txid: item.0, changes: item.1.as_ptr(), - changes_len: item.1.len() as c_ulonglong - } - }).collect::>(); + changes_len: item.1.len() as c_ulonglong, + }) + .collect::>(); let len = extern_reports.len(); let change_list = TxChangeList { reports: extern_reports.as_ptr(), @@ -1967,14 +2107,20 @@ pub unsafe extern "C" fn store_unregister_observer(store: *mut Store, key: *cons /// /// If there is no [Attribute](mentat::Attribute) in the [Schema](mentat::Schema) for `attr`. #[no_mangle] -pub unsafe extern "C" fn store_entid_for_attribute(store: *mut Store, attr: *const c_char) -> Entid { +pub unsafe extern "C" fn store_entid_for_attribute( + store: *mut Store, + attr: *const c_char, +) -> Entid { assert_not_null!(store); let store = &mut *store; let keyword_string = c_char_to_string(attr); let kw = kw_from_string(keyword_string); let conn = store.conn(); let current_schema = conn.current_schema(); - current_schema.get_entid(&kw).expect("Unable to find entid for invalid attribute").into() + current_schema + .get_entid(&kw) + .expect("Unable to find entid for invalid attribute") + .into() } /// Returns the value at the provided `index` as a [TransactionChange](TransactionChange) . @@ -1984,7 +2130,10 @@ pub unsafe extern "C" fn store_entid_for_attribute(store: *mut Store, attr: *con /// If there is no value present at the `index`. /// #[no_mangle] -pub unsafe extern "C" fn tx_change_list_entry_at(tx_report_list: *mut TxChangeList, index: c_int) -> *const TransactionChange { +pub unsafe extern "C" fn tx_change_list_entry_at( + tx_report_list: *mut TxChangeList, + index: c_int, +) -> *const TransactionChange { assert_not_null!(tx_report_list); let tx_report_list = &*tx_report_list; assert!(0 <= index && (index as usize) < (tx_report_list.len as usize)); @@ -1997,7 +2146,10 @@ pub unsafe extern "C" fn tx_change_list_entry_at(tx_report_list: *mut TxChangeLi /// /// If there is no value present at the `index`. #[no_mangle] -pub unsafe extern "C" fn changelist_entry_at(tx_report: *mut TransactionChange, index: c_int) -> Entid { +pub unsafe extern "C" fn changelist_entry_at( + tx_report: *mut TransactionChange, + index: c_int, +) -> Entid { assert_not_null!(tx_report); let tx_report = &*tx_report; assert!(0 <= index && (index as usize) < (tx_report.changes_len as usize)); @@ -2051,7 +2203,10 @@ define_destructor!(uuid_destroy, [u8; 16]); define_destructor_with_lifetimes!(in_progress_builder_destroy, InProgressBuilder<'a, 'c>); /// Destructor for releasing the memory of [EntityBuilder](mentat::EntityBuilder). -define_destructor_with_lifetimes!(entity_builder_destroy, EntityBuilder>); +define_destructor_with_lifetimes!( + entity_builder_destroy, + EntityBuilder> +); /// Destructor for releasing the memory of [QueryBuilder](mentat::QueryBuilder) . define_destructor!(query_builder_destroy, QueryBuilder); diff --git a/ffi/src/utils.rs b/ffi/src/utils.rs index ed805aa4..c5f2f978 100644 --- a/ffi/src/utils.rs +++ b/ffi/src/utils.rs @@ -9,15 +9,10 @@ // specific language governing permissions and limitations under the License. pub mod strings { - use std::ffi::{ - CString, - CStr - }; + use std::ffi::{CStr, CString}; use std::os::raw::c_char; - use mentat::{ - Keyword, - }; + use mentat::Keyword; pub fn c_char_to_string(cchar: *const c_char) -> &'static str { assert!(!cchar.is_null()); @@ -25,35 +20,38 @@ pub mod strings { c_str.to_str().unwrap_or("") } - pub fn string_to_c_char(r_string: T) -> *mut c_char where T: Into { + pub fn string_to_c_char(r_string: T) -> *mut c_char + where + T: Into, + { CString::new(r_string.into()).unwrap().into_raw() } pub fn kw_from_string(keyword_string: &'static str) -> 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(); Keyword::namespaced(parts[0], parts[1]) } } pub mod log { - #[cfg(all(target_os="android", not(test)))] + #[cfg(all(target_os = "android", not(test)))] use std::ffi::CString; - #[cfg(all(target_os="android", not(test)))] + #[cfg(all(target_os = "android", not(test)))] use android; // 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) {} - #[cfg(all(target_os="ios", not(test)))] + #[cfg(all(target_os = "ios", not(test)))] pub fn d(message: &str) { eprintln!("{}", message); } - #[cfg(all(target_os="android", not(test)))] + #[cfg(all(target_os = "android", not(test)))] pub fn d(message: &str) { let message = CString::new(message).unwrap(); let message = message.as_ptr(); @@ -65,9 +63,9 @@ pub mod log { pub mod error { use super::strings::string_to_c_char; - use std::os::raw::c_char; use std::boxed::Box; use std::fmt::Display; + use std::os::raw::c_char; use std::ptr; /// 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 { 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 /// `error.message` pub unsafe fn translate_result(result: Result, error: *mut ExternError) -> *mut T - where E: Display { + where + E: Display, + { // TODO: can't unwind across FFI... assert!(!error.is_null(), "Error output parameter is not optional"); 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 /// message (which was allocated on the heap and should eventually be freed) into /// `error.message` - pub unsafe fn translate_opt_result(result: Result, E>, error: *mut ExternError) -> *mut T - where E: Display { + pub unsafe fn translate_opt_result( + result: Result, E>, + error: *mut ExternError, + ) -> *mut T + where + E: Display, + { assert!(!error.is_null(), "Error output parameter is not optional"); let error = &mut *error; 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 /// a `Result<(), E>` (which we're about to drop on the floor). - pub unsafe fn translate_void_result(result: Result<(), E>, error: *mut ExternError) where E: Display { + pub unsafe fn translate_void_result(result: Result<(), E>, error: *mut ExternError) + where + E: Display, + { // Note that Box 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. translate_result(result, error); } } - diff --git a/public-traits/Cargo.toml b/public-traits/Cargo.toml index 17d9974b..cb559a7f 100644 --- a/public-traits/Cargo.toml +++ b/public-traits/Cargo.toml @@ -13,12 +13,14 @@ sqlcipher = ["rusqlite/sqlcipher"] syncable = ["tolstoy_traits", "hyper", "serde_json"] [dependencies] -failure = "0.1.1" -failure_derive = "0.1.1" -uuid = "0.5" +failure = "0.1.6" +failure_derive = "0.1.6" +http = "0.2.0" +tokio-core = "0.1.17" +uuid = "0.8" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.edn] @@ -47,7 +49,7 @@ path = "../tolstoy-traits" optional = true [dependencies.hyper] -version = "0.11" +version = "0.13.1" optional = true [dependencies.serde_json] diff --git a/public-traits/errors.rs b/public-traits/errors.rs index f8906baa..b3db80fb 100644 --- a/public-traits/errors.rs +++ b/public-traits/errors.rs @@ -20,31 +20,16 @@ use uuid; use edn; -use core_traits::{ - Attribute, - ValueType, -}; +use core_traits::{Attribute, ValueType}; -use db_traits::errors::{ - DbError, -}; -use query_algebrizer_traits::errors::{ - AlgebrizerError, -}; -use query_projector_traits::errors::{ - ProjectorError, -}; -use query_pull_traits::errors::{ - PullError, -}; -use sql_traits::errors::{ - SQLError, -}; +use db_traits::errors::DbError; +use query_algebrizer_traits::errors::AlgebrizerError; +use query_projector_traits::errors::ProjectorError; +use query_pull_traits::errors::PullError; +use sql_traits::errors::SQLError; #[cfg(feature = "syncable")] -use tolstoy_traits::errors::{ - TolstoyError, -}; +use tolstoy_traits::errors::TolstoyError; #[cfg(feature = "syncable")] use hyper; @@ -74,10 +59,16 @@ pub enum MentatError { #[fail(display = "invalid vocabulary version")] 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), - #[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), #[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")] 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), #[fail(display = "{}", _0)] @@ -127,7 +121,7 @@ pub enum MentatError { SQLError(#[cause] SQLError), #[fail(display = "{}", _0)] - UuidError(#[cause] uuid::ParseError), + UuidError(#[cause] uuid::Error), #[cfg(feature = "syncable")] #[fail(display = "{}", _0)] @@ -139,7 +133,7 @@ pub enum MentatError { #[cfg(feature = "syncable")] #[fail(display = "{}", _0)] - UriError(#[cause] hyper::error::UriError), + UriError(#[cause] http::uri::InvalidUri), #[cfg(feature = "syncable")] #[fail(display = "{}", _0)] @@ -147,87 +141,87 @@ pub enum MentatError { } impl From for MentatError { - fn from(error: std::io::Error) -> MentatError { + fn from(error: std::io::Error) -> Self { MentatError::IoError(error) } } impl From for MentatError { - fn from(error: rusqlite::Error) -> MentatError { + fn from(error: rusqlite::Error) -> Self { let cause = match error.source() { Some(e) => e.to_string(), - None => "".to_string() + None => "".to_string(), }; MentatError::RusqliteError(error.to_string(), cause) } } -impl From for MentatError { - fn from(error: uuid::ParseError) -> MentatError { +impl From for MentatError { + fn from(error: uuid::Error) -> Self { MentatError::UuidError(error) } } impl From for MentatError { - fn from(error: edn::ParseError) -> MentatError { + fn from(error: edn::ParseError) -> Self { MentatError::EdnParseError(error) } } impl From for MentatError { - fn from(error: DbError) -> MentatError { + fn from(error: DbError) -> Self { MentatError::DbError(error) } } impl From for MentatError { - fn from(error: AlgebrizerError) -> MentatError { + fn from(error: AlgebrizerError) -> Self { MentatError::AlgebrizerError(error) } } impl From for MentatError { - fn from(error: ProjectorError) -> MentatError { + fn from(error: ProjectorError) -> Self { MentatError::ProjectorError(error) } } impl From for MentatError { - fn from(error: PullError) -> MentatError { + fn from(error: PullError) -> Self { MentatError::PullError(error) } } impl From for MentatError { - fn from(error: SQLError) -> MentatError { + fn from(error: SQLError) -> Self { MentatError::SQLError(error) } } #[cfg(feature = "syncable")] impl From for MentatError { - fn from(error: TolstoyError) -> MentatError { + fn from(error: TolstoyError) -> Self { MentatError::TolstoyError(error) } } #[cfg(feature = "syncable")] impl From for MentatError { - fn from(error: serde_json::Error) -> MentatError { + fn from(error: serde_json::Error) -> Self { MentatError::SerializationError(error) } } #[cfg(feature = "syncable")] impl From for MentatError { - fn from(error: hyper::Error) -> MentatError { + fn from(error: hyper::Error) -> Self { MentatError::NetworkError(error) } } #[cfg(feature = "syncable")] -impl From for MentatError { - fn from(error: hyper::error::UriError) -> MentatError { +impl From for MentatError { + fn from(error: http::uri::InvalidUri) -> Self { MentatError::UriError(error) } } diff --git a/public-traits/lib.rs b/public-traits/lib.rs index 15598803..24bc0a81 100644 --- a/public-traits/lib.rs +++ b/public-traits/lib.rs @@ -14,12 +14,12 @@ extern crate failure_derive; extern crate rusqlite; -extern crate edn; extern crate core_traits; extern crate db_traits; -extern crate query_pull_traits; -extern crate query_projector_traits; +extern crate edn; extern crate query_algebrizer_traits; +extern crate query_projector_traits; +extern crate query_pull_traits; extern crate sql_traits; extern crate uuid; diff --git a/query-algebrizer-traits/errors.rs b/query-algebrizer-traits/errors.rs index c1b360ef..91c38327 100644 --- a/query-algebrizer-traits/errors.rs +++ b/query-algebrizer-traits/errors.rs @@ -10,18 +10,11 @@ use std; // To refer to std::result::Result. -use core_traits::{ - ValueType, - ValueTypeSet, -}; +use core_traits::{ValueType, ValueTypeSet}; -use edn::parse::{ - ParseError, -}; +use edn::parse::ParseError; -use edn::query::{ - PlainSymbol, -}; +use edn::query::PlainSymbol; pub type Result = std::result::Result; @@ -42,7 +35,10 @@ pub enum BindingError { /// 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. - InvalidNumberOfBindings { number: usize, expected: usize }, + InvalidNumberOfBindings { + number: usize, + expected: usize, + }, } #[derive(Clone, Debug, Eq, Fail, PartialEq)] @@ -53,23 +49,38 @@ pub enum AlgebrizerError { #[fail(display = "unexpected FnArg")] 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), - #[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), - #[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), - #[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), // TODO: flesh this out. #[fail(display = "invalid expression in ground constant")] 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), #[fail(display = "mismatched bindings in ground")] diff --git a/query-algebrizer/Cargo.toml b/query-algebrizer/Cargo.toml index cfc28a9b..98cb41fb 100644 --- a/query-algebrizer/Cargo.toml +++ b/query-algebrizer/Cargo.toml @@ -19,4 +19,4 @@ path = "../core-traits" path = "../query-algebrizer-traits" [dev-dependencies] -itertools = "0.7" +itertools = "0.8" diff --git a/query-algebrizer/src/clauses/convert.rs b/query-algebrizer/src/clauses/convert.rs index d501bf91..10ade215 100644 --- a/query-algebrizer/src/clauses/convert.rs +++ b/query-algebrizer/src/clauses/convert.rs @@ -8,49 +8,30 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, - ValueTypeSet, - TypedValue, -}; +use core_traits::{TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - HasSchema, - Schema, - SQLValueType, -}; +use mentat_core::{HasSchema, SQLValueType, Schema}; -use edn::query::{ - FnArg, - NonIntegerConstant, - Variable, -}; +use edn::query::{FnArg, NonIntegerConstant, Variable}; -use clauses::{ - ConjoiningClauses, -}; +use clauses::ConjoiningClauses; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; -use types::{ - EmptyBecause, -}; +use types::EmptyBecause; 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) { - Impossible(EmptyBecause::TypeMismatch { - var: $var.clone(), - existing: $types, - desired: ValueTypeSet::of_one($type), - }) - } else { - Val($constructor($val).into()) - }) - } } + Impossible(EmptyBecause::TypeMismatch { + var: $var.clone(), + existing: $types, + desired: ValueTypeSet::of_one($type), + }) + } else { + Val($constructor($val).into()) + }) + }}; } pub(crate) trait ValueTypes { @@ -60,43 +41,48 @@ pub(crate) trait ValueTypes { impl ValueTypes for FnArg { fn potential_types(&self, schema: &Schema) -> Result { Ok(match self { - &FnArg::EntidOrInteger(x) => { - if ValueType::Ref.accommodates_integer(x) { - // TODO: also see if it's a valid entid? - ValueTypeSet::of_longs() - } else { - ValueTypeSet::of_one(ValueType::Long) - } - }, + &FnArg::EntidOrInteger(x) => { + if ValueType::Ref.accommodates_integer(x) { + // TODO: also see if it's a valid entid? + ValueTypeSet::of_longs() + } else { + ValueTypeSet::of_one(ValueType::Long) + } + } - &FnArg::IdentOrKeyword(ref x) => { - if schema.get_entid(x).is_some() { - ValueTypeSet::of_keywords() - } else { - ValueTypeSet::of_one(ValueType::Keyword) - } - }, + &FnArg::IdentOrKeyword(ref x) => { + if schema.get_entid(x).is_some() { + ValueTypeSet::of_keywords() + } else { + ValueTypeSet::of_one(ValueType::Keyword) + } + } - &FnArg::Variable(_) => { - ValueTypeSet::any() - }, + &FnArg::Variable(_) => ValueTypeSet::any(), - &FnArg::Constant(NonIntegerConstant::BigInteger(_)) => { - // Not yet implemented. - bail!(AlgebrizerError::UnsupportedArgument) - }, + &FnArg::Constant(NonIntegerConstant::BigInteger(_)) => { + // Not yet implemented. + bail!(AlgebrizerError::UnsupportedArgument) + } - // These don't make sense here. TODO: split FnArg into scalar and non-scalar… - &FnArg::Vector(_) | - &FnArg::SrcVar(_) => bail!(AlgebrizerError::UnsupportedArgument), + // These don't make sense here. TODO: split FnArg into scalar and non-scalar… + &FnArg::Vector(_) | &FnArg::SrcVar(_) => bail!(AlgebrizerError::UnsupportedArgument), - // These are all straightforward. - &FnArg::Constant(NonIntegerConstant::Boolean(_)) => ValueTypeSet::of_one(ValueType::Boolean), - &FnArg::Constant(NonIntegerConstant::Instant(_)) => 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), - }) + // These are all straightforward. + &FnArg::Constant(NonIntegerConstant::Boolean(_)) => { + ValueTypeSet::of_one(ValueType::Boolean) + } + &FnArg::Constant(NonIntegerConstant::Instant(_)) => { + 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: /// - Existing known types of a variable to which this arg will be bound. /// - 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 { + pub(crate) fn typed_value_from_arg<'s>( + &self, + schema: &'s Schema, + var: &Variable, + arg: FnArg, + known_types: ValueTypeSet, + ) -> Result { use self::ValueConversion::*; if known_types.is_empty() { // If this happens, it likely means the pattern has already failed! @@ -132,22 +124,24 @@ impl ConjoiningClauses { match arg { // Longs are potentially ambiguous: they might be longs or entids. FnArg::EntidOrInteger(x) => { - match (ValueType::Ref.accommodates_integer(x), - constrained_types.contains(ValueType::Ref), - constrained_types.contains(ValueType::Long)) { + match ( + ValueType::Ref.accommodates_integer(x), + constrained_types.contains(ValueType::Ref), + constrained_types.contains(ValueType::Long), + ) { (true, true, true) => { // Ambiguous: this arg could be an entid or a long. // We default to long. Ok(Val(TypedValue::Long(x))) - }, + } (true, true, false) => { // This can only be a ref. Ok(Val(TypedValue::Ref(x))) - }, + } (_, false, true) => { // This can only be a long. Ok(Val(TypedValue::Long(x))) - }, + } (false, true, _) => { // This isn't a valid ref, but that's the type to which this must conform! Ok(Impossible(EmptyBecause::TypeMismatch { @@ -155,7 +149,7 @@ impl ConjoiningClauses { existing: known_types, desired: ValueTypeSet::of_longs(), })) - }, + } (_, false, false) => { // Non-overlapping type sets. Ok(Impossible(EmptyBecause::TypeMismatch { @@ -163,38 +157,36 @@ impl ConjoiningClauses { existing: known_types, desired: ValueTypeSet::of_longs(), })) - }, + } } - }, + } // If you definitely want to look up an ident, do it before running the query. FnArg::IdentOrKeyword(x) => { - match (constrained_types.contains(ValueType::Ref), - constrained_types.contains(ValueType::Keyword)) { + match ( + constrained_types.contains(ValueType::Ref), + constrained_types.contains(ValueType::Keyword), + ) { (true, true) => { // Ambiguous: this could be a keyword or an ident. // Default to keyword. Ok(Val(x.into())) - }, + } (true, false) => { // This can only be an ident. Look it up! match schema.get_entid(&x).map(|k| k.into()) { Some(e) => Ok(Val(e)), None => Ok(Impossible(EmptyBecause::UnresolvedIdent(x.clone()))), } - }, - (false, true) => { - Ok(Val(TypedValue::Keyword(x.into()))) - }, - (false, false) => { - Ok(Impossible(EmptyBecause::TypeMismatch { - var: var.clone(), - existing: known_types, - desired: ValueTypeSet::of_keywords(), - })) - }, + } + (false, true) => Ok(Val(TypedValue::Keyword(x.into()))), + (false, false) => Ok(Impossible(EmptyBecause::TypeMismatch { + var: var.clone(), + existing: known_types, + desired: ValueTypeSet::of_keywords(), + })), } - }, + } FnArg::Variable(in_var) => { // 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 // to collect variables as part of a computed table or substitution. bail!(AlgebrizerError::UnboundVariable((*in_var.0).clone())) - }, + } } - }, + } // This isn't implemented yet. FnArg::Constant(NonIntegerConstant::BigInteger(_)) => unimplemented!(), // These don't make sense here. - FnArg::Vector(_) | - FnArg::SrcVar(_) => bail!(AlgebrizerError::InvalidGroundConstant), + FnArg::Vector(_) | FnArg::SrcVar(_) => bail!(AlgebrizerError::InvalidGroundConstant), // These are all straightforward. FnArg::Constant(NonIntegerConstant::Boolean(x)) => { coerce_to_typed_value!(var, x, known_types, ValueType::Boolean, TypedValue::Boolean) - }, + } FnArg::Constant(NonIntegerConstant::Instant(x)) => { coerce_to_typed_value!(var, x, known_types, ValueType::Instant, TypedValue::Instant) - }, + } FnArg::Constant(NonIntegerConstant::Uuid(x)) => { coerce_to_typed_value!(var, x, known_types, ValueType::Uuid, TypedValue::Uuid) - }, + } FnArg::Constant(NonIntegerConstant::Float(x)) => { coerce_to_typed_value!(var, x, known_types, ValueType::Double, TypedValue::Double) - }, + } FnArg::Constant(NonIntegerConstant::Text(x)) => { coerce_to_typed_value!(var, x, known_types, ValueType::String, TypedValue::String) - }, + } } } } diff --git a/query-algebrizer/src/clauses/fulltext.rs b/query-algebrizer/src/clauses/fulltext.rs index 5c8ec74e..43331b3e 100644 --- a/query-algebrizer/src/clauses/fulltext.rs +++ b/query-algebrizer/src/clauses/fulltext.rs @@ -8,46 +8,21 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, - TypedValue, -}; +use core_traits::{TypedValue, ValueType}; -use mentat_core::{ - HasSchema, -}; +use mentat_core::HasSchema; use mentat_core::util::Either; -use edn::query::{ - Binding, - FnArg, - NonIntegerConstant, - SrcVar, - VariableOrPlaceholder, - WhereFn, -}; +use edn::query::{Binding, FnArg, NonIntegerConstant, SrcVar, VariableOrPlaceholder, WhereFn}; -use clauses::{ - ConjoiningClauses, -}; +use clauses::ConjoiningClauses; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - BindingError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result}; use types::{ - Column, - ColumnConstraint, - DatomsColumn, - DatomsTable, - EmptyBecause, - FulltextColumn, - QualifiedAlias, - QueryValue, - SourceAlias, + Column, ColumnConstraint, DatomsColumn, DatomsTable, EmptyBecause, FulltextColumn, + QualifiedAlias, QueryValue, SourceAlias, }; use Known; @@ -56,17 +31,27 @@ impl ConjoiningClauses { #[allow(unused_variables)] pub(crate) fn apply_fulltext(&mut self, known: Known, where_fn: WhereFn) -> Result<()> { 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() { // 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() { // 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. @@ -74,31 +59,45 @@ impl ConjoiningClauses { Binding::BindRel(bindings) => { let bindings_count = bindings.len(); if bindings_count < 1 || bindings_count > 4 { - bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), + bail!(AlgebrizerError::InvalidBinding( + where_fn.operator.clone(), BindingError::InvalidNumberOfBindings { number: bindings.len(), expected: 4, - }) - ); + } + )); } bindings - }, - Binding::BindScalar(_) | - Binding::BindTuple(_) | - Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)), + } + Binding::BindScalar(_) | Binding::BindTuple(_) | Binding::BindColl(_) => { + bail!(AlgebrizerError::InvalidBinding( + where_fn.operator.clone(), + BindingError::ExpectedBindRel + )) + } }; let mut bindings = bindings.into_iter(); let b_entity = bindings.next().unwrap(); - let b_value = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); - let b_tx = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); - let b_score = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); + let b_value = bindings + .next() + .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(); // TODO: process source variables. match args.next().unwrap() { - FnArg::SrcVar(SrcVar::DefaultSrc) => {}, - _ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)), + FnArg::SrcVar(SrcVar::DefaultSrc) => {} + _ => bail!(AlgebrizerError::InvalidArgument( + where_fn.operator.clone(), + "source variable", + 0 + )), } let schema = known.schema; @@ -117,25 +116,34 @@ impl ConjoiningClauses { // TODO: allow non-constant attributes. match self.bound_value(&v) { Some(TypedValue::Ref(entid)) => Some(entid), - Some(tv) => { - bail!(AlgebrizerError::InputTypeDisagreement(v.name().clone(), ValueType::Ref, tv.value_type())) - }, - None => { - bail!(AlgebrizerError::UnboundVariable((*v.0).clone())) - } + Some(tv) => bail!(AlgebrizerError::InputTypeDisagreement( + v.name().clone(), + ValueType::Ref, + tv.value_type() + )), + None => bail!(AlgebrizerError::UnboundVariable((*v.0).clone())), } - }, + } _ => None, }; // 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 // marking the pattern as known-empty. - let a = a.ok_or(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "attribute", 1))?; - let attribute = schema.attribute_for_entid(a) - .cloned() - .ok_or(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), - "attribute", 1))?; + let a = a.ok_or(AlgebrizerError::InvalidArgument( + where_fn.operator.clone(), + "attribute", + 1, + ))?; + let attribute = + schema + .attribute_for_entid(a) + .cloned() + .ok_or(AlgebrizerError::InvalidArgument( + where_fn.operator.clone(), + "attribute", + 1, + ))?; if !attribute.fulltext { // 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 // 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(DatomsTable::Datoms, datoms_table_alias.clone())); + self.from.push(SourceAlias( + 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). self.constrain_attribute(datoms_table_alias.clone(), a); // Join the datoms table to the fulltext values table. self.wheres.add_intersection(ColumnConstraint::Equals( - QualifiedAlias(datoms_table_alias.clone(), Column::Fixed(DatomsColumn::Value)), - QueryValue::Column(QualifiedAlias(fulltext_values_alias.clone(), Column::Fulltext(FulltextColumn::Rowid))))); + QualifiedAlias( + 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. // 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 bound. The query cannot be algebrized. let search: Either = match args.next().unwrap() { - FnArg::Constant(NonIntegerConstant::Text(s)) => { - Either::Left(TypedValue::String(s)) - }, + FnArg::Constant(NonIntegerConstant::Text(s)) => Either::Left(TypedValue::String(s)), FnArg::Variable(in_var) => { match self.bound_value(&in_var) { 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 => { // Regardless of whether we'll be providing a string later, or the value // comes from a column, it must be a 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) { @@ -188,18 +213,24 @@ impl ConjoiningClauses { } else { // It must be bound earlier in the query. We already established that // it must be a string column. - if let Some(binding) = self.column_bindings - .get(&in_var) - .and_then(|bindings| bindings.get(0).cloned()) { + if let Some(binding) = self + .column_bindings + .get(&in_var) + .and_then(|bindings| bindings.get(0).cloned()) + { Either::Right(binding) } else { 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 { @@ -207,9 +238,13 @@ impl ConjoiningClauses { Either::Right(qa) => QueryValue::Column(qa), }; - let constraint = ColumnConstraint::Matches(QualifiedAlias(fulltext_values_alias.clone(), - Column::Fulltext(FulltextColumn::Text)), - qv); + let constraint = ColumnConstraint::Matches( + QualifiedAlias( + fulltext_values_alias.clone(), + Column::Fulltext(FulltextColumn::Text), + ), + qv, + ); self.wheres.add_intersection(constraint); if let VariableOrPlaceholder::Variable(ref var) = b_entity { @@ -219,7 +254,12 @@ impl ConjoiningClauses { 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 { @@ -229,7 +269,12 @@ impl ConjoiningClauses { 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 { @@ -239,7 +284,12 @@ impl ConjoiningClauses { 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 { @@ -248,7 +298,10 @@ impl ConjoiningClauses { // We do not allow the score to be bound. 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. @@ -264,27 +317,13 @@ impl ConjoiningClauses { mod testing { use super::*; - use core_traits::{ - Attribute, - ValueType, - }; + use core_traits::{Attribute, ValueType}; - use mentat_core::{ - Schema, - }; + use mentat_core::Schema; - use edn::query::{ - Binding, - FnArg, - Keyword, - PlainSymbol, - Variable, - }; + use edn::query::{Binding, FnArg, Keyword, PlainSymbol, Variable}; - use clauses::{ - add_attribute, - associate_ident, - }; + use clauses::{add_attribute, associate_ident}; #[test] fn test_apply_fulltext() { @@ -292,35 +331,49 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 101); - add_attribute(&mut schema, 101, Attribute { - value_type: ValueType::String, - fulltext: false, - ..Default::default() - }); + add_attribute( + &mut schema, + 101, + Attribute { + value_type: ValueType::String, + fulltext: false, + ..Default::default() + }, + ); associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100); - add_attribute(&mut schema, 100, Attribute { - value_type: ValueType::String, - index: true, - fulltext: true, - ..Default::default() - }); + add_attribute( + &mut schema, + 100, + Attribute { + value_type: ValueType::String, + index: true, + fulltext: true, + ..Default::default() + }, + ); let known = Known::for_schema(&schema); let op = PlainSymbol::plain("fulltext"); - cc.apply_fulltext(known, WhereFn { - operator: op, - args: vec![ - FnArg::SrcVar(SrcVar::DefaultSrc), - FnArg::IdentOrKeyword(Keyword::namespaced("foo", "fts")), - FnArg::Constant("needle".into()), - ], - binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")), - 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"); + cc.apply_fulltext( + known, + WhereFn { + operator: op, + args: vec![ + FnArg::SrcVar(SrcVar::DefaultSrc), + FnArg::IdentOrKeyword(Keyword::namespaced("foo", "fts")), + FnArg::Constant("needle".into()), + ], + binding: Binding::BindRel(vec![ + VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")), + 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()); @@ -331,54 +384,136 @@ mod testing { let clauses = cc.wheres; assert_eq!(clauses.len(), 3); - assert_eq!(clauses.0[0], ColumnConstraint::Equals(QualifiedAlias("datoms01".to_string(), 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()); + assert_eq!( + clauses.0[0], + ColumnConstraint::Equals( + QualifiedAlias( + "datoms01".to_string(), + 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; assert_eq!(bindings.len(), 3); - assert_eq!(bindings.get(&Variable::from_valid_name("?entity")).expect("column binding for ?entity").clone(), - 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))]); + assert_eq!( + bindings + .get(&Variable::from_valid_name("?entity")) + .expect("column binding for ?entity") + .clone(), + 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. let values = cc.value_bindings; - assert_eq!(values.get(&Variable::from_valid_name("?score")).expect("column binding for ?score").clone(), - TypedValue::Double(0.0.into())); + assert_eq!( + values + .get(&Variable::from_valid_name("?score")) + .expect("column binding for ?score") + .clone(), + TypedValue::Double(0.0.into()) + ); let known_types = cc.known_types; assert_eq!(known_types.len(), 4); - assert_eq!(known_types.get(&Variable::from_valid_name("?entity")).expect("known types for ?entity").clone(), - vec![ValueType::Ref].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()); + assert_eq!( + known_types + .get(&Variable::from_valid_name("?entity")) + .expect("known types for ?entity") + .clone(), + vec![ValueType::Ref].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 op = PlainSymbol::plain("fulltext"); - cc.apply_fulltext(known, WhereFn { - operator: op, - args: vec![ - FnArg::SrcVar(SrcVar::DefaultSrc), - FnArg::IdentOrKeyword(Keyword::namespaced("foo", "bar")), - FnArg::Constant("needle".into()), - ], - binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")), - 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"); + cc.apply_fulltext( + known, + WhereFn { + operator: op, + args: vec![ + FnArg::SrcVar(SrcVar::DefaultSrc), + FnArg::IdentOrKeyword(Keyword::namespaced("foo", "bar")), + FnArg::Constant("needle".into()), + ], + binding: Binding::BindRel(vec![ + VariableOrPlaceholder::Variable(Variable::from_valid_name("?entity")), + 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. assert!(cc.is_known_empty()); diff --git a/query-algebrizer/src/clauses/ground.rs b/query-algebrizer/src/clauses/ground.rs index df11e3ba..801c2b13 100644 --- a/query-algebrizer/src/clauses/ground.rs +++ b/query-algebrizer/src/clauses/ground.rs @@ -8,43 +8,19 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, - ValueTypeSet, - TypedValue, -}; +use core_traits::{TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::query::{ - Binding, - FnArg, - Variable, - VariableOrPlaceholder, - WhereFn, -}; +use edn::query::{Binding, FnArg, Variable, VariableOrPlaceholder, WhereFn}; -use clauses::{ - ConjoiningClauses, - PushComputed, -}; +use clauses::{ConjoiningClauses, PushComputed}; use clauses::convert::ValueConversion; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - BindingError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result}; -use types::{ - ComputedTable, - EmptyBecause, - SourceAlias, - VariableColumn, -}; +use types::{ComputedTable, EmptyBecause, SourceAlias, VariableColumn}; use Known; @@ -53,7 +29,13 @@ impl ConjoiningClauses { /// the provided types. /// Construct a computed table to yield this relation. /// This function will panic if some invariants are not met. - fn collect_named_bindings<'s>(&mut self, schema: &'s Schema, names: Vec, types: Vec, values: Vec) { + fn collect_named_bindings<'s>( + &mut self, + schema: &'s Schema, + names: Vec, + types: Vec, + values: Vec, + ) { if values.is_empty() { return; } @@ -61,7 +43,7 @@ impl ConjoiningClauses { assert!(!names.is_empty()); assert_eq!(names.len(), types.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 { names: names.clone(), @@ -74,13 +56,23 @@ impl ConjoiningClauses { // Stitch the computed table into column_bindings, so we get cross-linking. for (name, ty) in names.iter().zip(types.into_iter()) { 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)); } - 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 { VariableOrPlaceholder::Placeholder => Ok(()), 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. /// 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); match self.typed_value_from_arg(schema, &var, arg, known_types)? { ValueConversion::Val(value) => self.apply_ground_value(var, value), ValueConversion::Impossible(because) => { self.mark_known_empty(because); Ok(()) - }, + } } } @@ -109,7 +106,7 @@ impl ConjoiningClauses { existing: existing.clone(), desired: value, }); - return Ok(()) + return Ok(()); } } else { 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<()> { 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(); if where_fn.binding.is_empty() { // 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() { // 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; @@ -141,8 +148,7 @@ impl ConjoiningClauses { // we can immediately substitute the value as a known value in the CC, additionally // generating a WHERE clause if columns have already been bound. match (where_fn.binding, args.next().unwrap()) { - (Binding::BindScalar(var), constant) => - self.apply_ground_var(schema, var, constant), + (Binding::BindScalar(var), constant) => self.apply_ground_var(schema, var, constant), (Binding::BindTuple(places), FnArg::Vector(children)) => { // Just the same, but we bind more than one column at a time. @@ -151,10 +157,10 @@ impl ConjoiningClauses { bail!(AlgebrizerError::GroundBindingsMismatch) } 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(()) - }, + } // 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. @@ -170,30 +176,32 @@ impl ConjoiningClauses { // Check that every value has the same type. let mut accumulated_types = ValueTypeSet::none(); let mut skip: Option = None; - let values = children.into_iter() - .filter_map(|arg| -> Option> { - // We need to get conversion errors out. - // We also want to mark known-empty on impossibilty, but - // still detect serious errors. - match self.typed_value_from_arg(schema, &var, arg, known_types) { - Ok(ValueConversion::Val(tv)) => { - if accumulated_types.insert(tv.value_type()) && - !accumulated_types.is_unit() { - // Values not all of the same type. - Some(Err(AlgebrizerError::InvalidGroundConstant.into())) - } else { - Some(Ok(tv)) - } - }, - Ok(ValueConversion::Impossible(because)) => { - // Skip this value. - skip = Some(because); - None - }, - Err(e) => Some(Err(e.into())), - } - }) - .collect::>>()?; + let values = children + .into_iter() + .filter_map(|arg| -> Option> { + // We need to get conversion errors out. + // We also want to mark known-empty on impossibilty, but + // still detect serious errors. + match self.typed_value_from_arg(schema, &var, arg, known_types) { + Ok(ValueConversion::Val(tv)) => { + if accumulated_types.insert(tv.value_type()) + && !accumulated_types.is_unit() + { + // Values not all of the same type. + Some(Err(AlgebrizerError::InvalidGroundConstant.into())) + } else { + Some(Ok(tv)) + } + } + Ok(ValueConversion::Impossible(because)) => { + // Skip this value. + skip = Some(because); + None + } + Err(e) => Some(Err(e.into())), + } + }) + .collect::>>()?; if values.is_empty() { 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); Ok(()) - }, + } (Binding::BindRel(places), FnArg::Vector(rows)) => { if rows.is_empty() { @@ -216,17 +224,20 @@ impl ConjoiningClauses { // Grab the known types to which these args must conform, and track // the places that won't be bound in the output. - let template: Vec> = - places.iter() - .map(|x| match x { - &VariableOrPlaceholder::Placeholder => None, - &VariableOrPlaceholder::Variable(ref v) => Some((v.clone(), self.known_type_set(v))), - }) - .collect(); + let template: Vec> = places + .iter() + .map(|x| match x { + &VariableOrPlaceholder::Placeholder => None, + &VariableOrPlaceholder::Variable(ref v) => { + Some((v.clone(), self.known_type_set(v))) + } + }) + .collect(); // The expected 'width' of the matrix is the number of named variables. let full_width = places.len(); - let names: Vec = places.into_iter().filter_map(|x| x.into_var()).collect(); + let names: Vec = + places.into_iter().filter_map(|x| x.into_var()).collect(); let expected_width = names.len(); let expected_rows = rows.len(); @@ -267,7 +278,7 @@ impl ConjoiningClauses { // Skip this row. It cannot produce bindings. skip = Some(because); break; - }, + } } } } @@ -279,7 +290,10 @@ impl ConjoiningClauses { } // 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()); if inserted && !acc.is_unit() { // Heterogeneous types. @@ -287,8 +301,7 @@ impl ConjoiningClauses { } matrix.push(val); } - - }, + } _ => bail!(AlgebrizerError::InvalidGroundConstant), } } @@ -309,12 +322,13 @@ impl ConjoiningClauses { // 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 // with little penalty. - let types = accumulated_types_for_columns.into_iter() - .map(|x| x.exemplar().unwrap()) - .collect(); + let types = accumulated_types_for_columns + .into_iter() + .map(|x| x.exemplar().unwrap()) + .collect(); self.collect_named_bindings(schema, names, types, matrix); Ok(()) - }, + } (_, _) => bail!(AlgebrizerError::InvalidGroundConstant), } } @@ -324,23 +338,11 @@ impl ConjoiningClauses { mod testing { use super::*; - use core_traits::{ - Attribute, - ValueType, - }; + use core_traits::{Attribute, ValueType}; - use edn::query::{ - Binding, - FnArg, - Keyword, - PlainSymbol, - Variable, - }; + use edn::query::{Binding, FnArg, Keyword, PlainSymbol, Variable}; - use clauses::{ - add_attribute, - associate_ident, - }; + use clauses::{add_attribute, associate_ident}; #[test] fn test_apply_ground() { @@ -350,25 +352,31 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100); - add_attribute(&mut schema, 100, Attribute { - value_type: ValueType::String, - index: true, - fulltext: true, - ..Default::default() - }); + add_attribute( + &mut schema, + 100, + Attribute { + value_type: ValueType::String, + index: true, + fulltext: true, + ..Default::default() + }, + ); let known = Known::for_schema(&schema); // 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. let op = PlainSymbol::plain("ground"); - cc.apply_ground(known, WhereFn { - operator: op, - args: vec![ - FnArg::EntidOrInteger(10), - ], - binding: Binding::BindScalar(vz.clone()), - }).expect("to be able to apply_ground"); + cc.apply_ground( + known, + WhereFn { + operator: op, + args: vec![FnArg::EntidOrInteger(10)], + binding: Binding::BindScalar(vz.clone()), + }, + ) + .expect("to be able to apply_ground"); assert!(!cc.is_known_empty()); @@ -380,16 +388,20 @@ mod testing { assert_eq!(clauses.len(), 0); 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; assert_eq!(known_types.len(), 1); - assert_eq!(known_types.get(&vz).expect("to know the type of ?z"), - &ValueTypeSet::of_one(ValueType::Long)); + assert_eq!( + known_types.get(&vz).expect("to know the type of ?z"), + &ValueTypeSet::of_one(ValueType::Long) + ); let value_bindings = cc.value_bindings; assert_eq!(value_bindings.len(), 1); - assert_eq!(value_bindings.get(&vz).expect("to have a value for ?z"), - &TypedValue::Long(10)); // We default to Long instead of entid. + assert_eq!( + value_bindings.get(&vz).expect("to have a value for ?z"), + &TypedValue::Long(10) + ); // We default to Long instead of entid. } } diff --git a/query-algebrizer/src/clauses/inputs.rs b/query-algebrizer/src/clauses/inputs.rs index a0e07efe..8f758eb6 100644 --- a/query-algebrizer/src/clauses/inputs.rs +++ b/query-algebrizer/src/clauses/inputs.rs @@ -10,19 +10,11 @@ use std::collections::BTreeMap; -use core_traits::{ - ValueType, - TypedValue, -}; +use core_traits::{TypedValue, ValueType}; -use edn::query::{ - Variable, -}; +use edn::query::Variable; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; /// Define the inputs to a query. This is in two parts: a set of values known now, and a set of /// types known now. @@ -59,13 +51,18 @@ impl QueryInputs { pub fn with_values(values: BTreeMap) -> 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, } } - pub fn new(mut types: BTreeMap, - values: BTreeMap) -> Result { + pub fn new( + mut types: BTreeMap, + values: BTreeMap, + ) -> Result { // Make sure that the types of the values agree with those in types, and collect. for (var, v) in values.iter() { let t = v.value_type(); @@ -76,6 +73,9 @@ impl QueryInputs { } } } - Ok(QueryInputs { types: types, values: values }) + Ok(QueryInputs { + types: types, + values: values, + }) } } diff --git a/query-algebrizer/src/clauses/mod.rs b/query-algebrizer/src/clauses/mod.rs index 51bd9866..379b645d 100644 --- a/query-algebrizer/src/clauses/mod.rs +++ b/query-algebrizer/src/clauses/mod.rs @@ -10,89 +10,42 @@ use std::cmp; -use std::collections::{ - BTreeMap, - BTreeSet, - VecDeque, -}; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; -use std::collections::btree_map::{ - Entry, -}; +use std::collections::btree_map::Entry; -use std::fmt::{ - Debug, - Formatter, -}; +use std::fmt::{Debug, Formatter}; -use core_traits::{ - Attribute, - Entid, - KnownEntid, - ValueType, - ValueTypeSet, - TypedValue, -}; +use core_traits::{Attribute, Entid, KnownEntid, TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - Cloned, - HasSchema, - Schema, -}; +use mentat_core::{Cloned, HasSchema, Schema}; use mentat_core::counter::RcCounter; -use edn::query::{ - Element, - FindSpec, - Keyword, - Pull, - Variable, - WhereClause, - PatternNonValuePlace, -}; +use edn::query::{Element, FindSpec, Keyword, PatternNonValuePlace, Pull, Variable, WhereClause}; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; use types::{ - ColumnConstraint, - ColumnIntersection, - ComputedTable, - Column, - DatomsColumn, - DatomsTable, - EmptyBecause, - EvolvedNonValuePlace, - EvolvedPattern, - EvolvedValuePlace, - FulltextColumn, - PlaceOrEmpty, - QualifiedAlias, - QueryValue, - SourceAlias, - TableAlias, + Column, ColumnConstraint, ColumnIntersection, ComputedTable, 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 or; mod not; +mod or; mod pattern; mod predicate; mod resolve; -mod ground; mod fulltext; +mod ground; mod tx_log_api; mod where_fn; -use validate::{ - validate_not_join, - validate_or_join, -}; +use validate::{validate_not_join, validate_or_join}; pub use self::inputs::QueryInputs; @@ -227,16 +180,16 @@ pub struct ConjoiningClauses { impl PartialEq for ConjoiningClauses { fn eq(&self, other: &ConjoiningClauses) -> bool { - self.empty_because.eq(&other.empty_because) && - self.from.eq(&other.from) && - self.computed_tables.eq(&other.computed_tables) && - self.wheres.eq(&other.wheres) && - self.column_bindings.eq(&other.column_bindings) && - self.input_variables.eq(&other.input_variables) && - self.value_bindings.eq(&other.value_bindings) && - self.known_types.eq(&other.known_types) && - self.extracted_types.eq(&other.extracted_types) && - self.required_types.eq(&other.required_types) + self.empty_because.eq(&other.empty_because) + && self.from.eq(&other.from) + && self.computed_tables.eq(&other.computed_tables) + && self.wheres.eq(&other.wheres) + && self.column_bindings.eq(&other.column_bindings) + && self.input_variables.eq(&other.input_variables) + && self.value_bindings.eq(&other.value_bindings) + && self.known_types.eq(&other.known_types) + && self.extracted_types.eq(&other.extracted_types) + && self.required_types.eq(&other.required_types) } } @@ -278,9 +231,7 @@ impl Default for ConjoiningClauses { } } -pub struct VariableIterator<'a>( - ::std::collections::btree_map::Keys<'a, Variable, TypedValue>, -); +pub struct VariableIterator<'a>(::std::collections::btree_map::Keys<'a, Variable, TypedValue>); impl<'a> Iterator for VariableIterator<'a> { type Item = &'a Variable; @@ -303,17 +254,26 @@ impl ConjoiningClauses { #[cfg(test)] pub fn with_inputs(in_variables: BTreeSet, inputs: T) -> ConjoiningClauses - where T: Into> { + where + T: Into>, + { ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new()) } - pub(crate) fn with_inputs_and_alias_counter(in_variables: BTreeSet, - inputs: T, - alias_counter: RcCounter) -> ConjoiningClauses - where T: Into> { + pub(crate) fn with_inputs_and_alias_counter( + in_variables: BTreeSet, + inputs: T, + alias_counter: RcCounter, + ) -> ConjoiningClauses + where + T: Into>, + { match inputs.into() { 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. types.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. - cc.known_types - .extend(types.iter() - .map(|(k, v)| (k.clone(), ValueTypeSet::of_one(*v)))); + cc.known_types.extend( + types + .iter() + .map(|(k, v)| (k.clone(), ValueTypeSet::of_one(*v))), + ); cc - }, + } } } } @@ -340,11 +302,13 @@ impl ConjoiningClauses { pub(crate) fn derive_types_from_find_spec(&mut self, find_spec: &FindSpec) { for spec in find_spec.columns() { match spec { - &Element::Pull(Pull { ref var, patterns: _ }) => { + &Element::Pull(Pull { + ref var, + patterns: _, + }) => { 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 let Some(vec) = self.column_bindings.get(var) { 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? // If so, constrain that! 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. @@ -439,10 +407,19 @@ impl ConjoiningClauses { } 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>(&mut self, schema: &Schema, table: TableAlias, column: C, var: Variable) { + pub(crate) fn bind_column_to_var>( + &mut self, + schema: &Schema, + table: TableAlias, + column: C, + var: Variable, + ) { let column = column.into(); // Do we have an external binding for this? 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 // produces the variable projection will do so. self.constrain_column_to_constant(table, column, bound_val); - }, + } Column::Transactions(_) => { self.constrain_column_to_constant(table, column, bound_val); - }, + } - Column::Fulltext(FulltextColumn::Rowid) | - Column::Fulltext(FulltextColumn::Text) => { + Column::Fulltext(FulltextColumn::Rowid) + | Column::Fulltext(FulltextColumn::Text) => { // We never expose `rowid` via queries. We do expose `text`, but only // indirectly, by joining against `datoms`. Therefore, these are meaningless. unimplemented!() - }, + } Column::Fixed(DatomsColumn::ValueTypeTag) => { // 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)]] // ``` unimplemented!(); - }, + } // TODO: recognize when the valueType might be a ref and also translate entids there. Column::Fixed(DatomsColumn::Value) => { self.constrain_column_to_constant(table, column, bound_val); - }, + } // 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. - Column::Fixed(DatomsColumn::Attribute) | - Column::Fixed(DatomsColumn::Entity) | - Column::Fixed(DatomsColumn::Tx) => { + Column::Fixed(DatomsColumn::Attribute) + | Column::Fixed(DatomsColumn::Entity) + | Column::Fixed(DatomsColumn::Tx) => { match bound_val { TypedValue::Keyword(ref 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. self.mark_known_empty(EmptyBecause::UnresolvedIdent(kw.cloned())); } - }, + } TypedValue::Ref(entid) => { self.constrain_column_to_entity(table, column, entid); - }, + } _ => { // One can't bind an e, a, or tx to something other than an entity. 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 // to get its type, record that we can get it from this table. - let needs_type_extraction = - !late_binding && // Never need to extract for bound vars. + let needs_type_extraction = !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.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); @@ -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>(&mut self, table: TableAlias, column: C, constant: TypedValue) { + pub(crate) fn constrain_column_to_constant>( + &mut self, + table: TableAlias, + column: C, + constant: TypedValue, + ) { match constant { // Be a little more explicit. TypedValue::Ref(entid) => self.constrain_column_to_entity(table, column, entid), _ => { 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>(&mut self, table: TableAlias, column: C, entity: Entid) { + pub(crate) fn constrain_column_to_entity>( + &mut self, + table: TableAlias, + column: C, + entity: Entid, + ) { 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) { @@ -562,7 +557,8 @@ impl ConjoiningClauses { pub(crate) fn constrain_value_to_numeric(&mut self, table: TableAlias, value: i64) { self.wheres.add_intersection(ColumnConstraint::Equals( QualifiedAlias(table, Column::Fixed(DatomsColumn::Value)), - QueryValue::PrimitiveLong(value))) + QueryValue::PrimitiveLong(value), + )) } /// Mark the given value as a long. @@ -575,11 +571,19 @@ impl ConjoiningClauses { 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 { + pub(crate) fn can_constrain_var_to_type( + &self, + var: &Variable, + this_type: ValueType, + ) -> Option { 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 { + fn can_constrain_var_to_types( + &self, + var: &Variable, + these_types: ValueTypeSet, + ) -> Option { if let Some(existing) = self.known_types.get(var) { if existing.intersection(&these_types).is_empty() { return Some(EmptyBecause::TypeMismatch { @@ -603,7 +607,11 @@ impl ConjoiningClauses { if let Some(existing) = self.known_types.insert(var.clone(), this_type_set) { // There was an existing mapping. Does this type match? 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.insert(types); return; - }, + } Entry::Occupied(mut entry) => { // We have an existing requirement. The new requirement will be // the intersection, but we'll `mark_known_empty` if that's empty. @@ -644,7 +652,7 @@ impl ConjoiningClauses { existing: existing, desired: types, } - }, + } }; self.mark_known_empty(empty_because); } @@ -664,21 +672,22 @@ impl ConjoiningClauses { self.extracted_types.remove(e.key()); } e.insert(new_types); - }, + } Entry::Occupied(mut e) => { let new; // Scoped borrow of `e`. { let existing_types = e.get(); 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.", e.key()); } new = existing_types.union(&new_types); } e.insert(new); - }, + } } } } @@ -699,18 +708,20 @@ impl ConjoiningClauses { match self.known_types.entry(var) { Entry::Vacant(e) => { e.insert(types); - }, + } Entry::Occupied(mut e) => { let intersected: ValueTypeSet = types.intersection(e.get()); if intersected.is_empty() { - let reason = EmptyBecause::TypeMismatch { var: e.key().clone(), - existing: e.get().clone(), - desired: types }; + let reason = EmptyBecause::TypeMismatch { + var: e.key().clone(), + existing: e.get().clone(), + desired: types, + }; empty_because = Some(reason); } // Always insert, even if it's empty! e.insert(intersected); - }, + } } if let Some(e) = empty_because { @@ -754,39 +765,47 @@ impl ConjoiningClauses { if self.empty_because.is_some() { return; } - println!("CC known empty: {:?}.", &why); // TODO: proper logging. + println!("CC known empty: {:?}.", &why); // TODO: proper logging. self.empty_because = Some(why); } - fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a Keyword) -> Option { + fn entid_for_ident<'s, 'a>( + &self, + schema: &'s Schema, + ident: &'a Keyword, + ) -> Option { schema.get_entid(&ident) } - fn table_for_attribute_and_value<'s, 'a>(&self, attribute: &'s Attribute, value: &'a EvolvedValuePlace) -> ::std::result::Result { + fn table_for_attribute_and_value<'s, 'a>( + &self, + attribute: &'s Attribute, + value: &'a EvolvedValuePlace, + ) -> ::std::result::Result { if attribute.fulltext { match value { - &EvolvedValuePlace::Placeholder => - Ok(DatomsTable::Datoms), // We don't need the value. + &EvolvedValuePlace::Placeholder => Ok(DatomsTable::Datoms), // We don't need the value. // TODO: an existing non-string binding can cause this pattern to fail. - &EvolvedValuePlace::Variable(_) => - Ok(DatomsTable::FulltextDatoms), + &EvolvedValuePlace::Variable(_) => Ok(DatomsTable::FulltextDatoms), - &EvolvedValuePlace::Value(TypedValue::String(_)) => - Ok(DatomsTable::FulltextDatoms), + &EvolvedValuePlace::Value(TypedValue::String(_)) => Ok(DatomsTable::FulltextDatoms), _ => { // We can't succeed if there's a non-string constant value for a fulltext // field. Err(EmptyBecause::NonStringFulltextValue) - }, + } } } else { Ok(DatomsTable::Datoms) } } - fn table_for_unknown_attribute<'s, 'a>(&self, value: &'a EvolvedValuePlace) -> ::std::result::Result { + fn table_for_unknown_attribute<'s, 'a>( + &self, + value: &'a EvolvedValuePlace, + ) -> ::std::result::Result { // If the value is known to be non-textual, we can simply use the regular datoms // 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 // ourselves, because we'll need to either extract or compare on the string. - Ok( - match value { - // 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. - &EvolvedValuePlace::Variable(ref v) => { - // If `required_types` and `known_types` don't exclude strings, - // we need to query `all_datoms`. - if self.required_types.get(v).map_or(true, |s| s.contains(ValueType::String)) && - self.known_types.get(v).map_or(true, |s| s.contains(ValueType::String)) { - DatomsTable::AllDatoms - } else { - DatomsTable::Datoms - } + Ok(match value { + // 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. + &EvolvedValuePlace::Variable(ref v) => { + // If `required_types` and `known_types` don't exclude strings, + // we need to query `all_datoms`. + if self + .required_types + .get(v) + .map_or(true, |s| s.contains(ValueType::String)) + && self + .known_types + .get(v) + .map_or(true, |s| s.contains(ValueType::String)) + { + DatomsTable::AllDatoms + } else { + DatomsTable::Datoms } - &EvolvedValuePlace::Value(TypedValue::String(_)) => - DatomsTable::AllDatoms, - _ => - DatomsTable::Datoms, - }) + } + &EvolvedValuePlace::Value(TypedValue::String(_)) => DatomsTable::AllDatoms, + _ => DatomsTable::Datoms, + }) } /// 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 /// 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. - fn table_for_places<'s, 'a>(&self, schema: &'s Schema, attribute: &'a EvolvedNonValuePlace, value: &'a EvolvedValuePlace) -> ::std::result::Result { + fn table_for_places<'s, 'a>( + &self, + schema: &'s Schema, + attribute: &'a EvolvedNonValuePlace, + value: &'a EvolvedValuePlace, + ) -> ::std::result::Result { match attribute { - &EvolvedNonValuePlace::Entid(id) => - schema.attribute_for_entid(id) - .ok_or_else(|| EmptyBecause::InvalidAttributeEntid(id)) - .and_then(|attribute| self.table_for_attribute_and_value(attribute, value)), + &EvolvedNonValuePlace::Entid(id) => schema + .attribute_for_entid(id) + .ok_or_else(|| EmptyBecause::InvalidAttributeEntid(id)) + .and_then(|attribute| self.table_for_attribute_and_value(attribute, value)), // TODO: In a prepared context, defer this decision until a second algebrizing phase. // #278. - &EvolvedNonValuePlace::Placeholder => - self.table_for_unknown_attribute(value), + &EvolvedNonValuePlace::Placeholder => self.table_for_unknown_attribute(value), &EvolvedNonValuePlace::Variable(ref v) => { // See if we have a binding for the variable. match self.bound_value(v) { // TODO: In a prepared context, defer this decision until a second algebrizing phase. // #278. - None => - self.table_for_unknown_attribute(value), + None => self.table_for_unknown_attribute(value), Some(TypedValue::Ref(id)) => - // Recurse: it's easy. - self.table_for_places(schema, &EvolvedNonValuePlace::Entid(id), value), + // Recurse: it's easy. + { + self.table_for_places(schema, &EvolvedNonValuePlace::Entid(id), value) + } Some(TypedValue::Keyword(ref kw)) => - // Don't recurse: avoid needing to clone the keyword. - schema.attribute_for_ident(kw) - .ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned())) - .and_then(|(attribute, _entid)| self.table_for_attribute_and_value(attribute, value)), + // Don't recurse: avoid needing to clone the keyword. + { + schema + .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) => { // This pattern cannot match: the caller has bound a non-entity value to an // 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 { match table { - DatomsTable::Computed(u) => - format!("{}{:02}", table.name(), u), - _ => - format!("{}{:02}", table.name(), self.alias_counter.next()), + DatomsTable::Computed(u) => format!("{}{:02}", table.name(), u), + _ => 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! /// Note that if this function decides that a pattern cannot match, it will flip /// `empty_because`. - fn alias_table<'s, 'a>(&mut self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option { + fn alias_table<'s, 'a>( + &mut self, + schema: &'s Schema, + pattern: &'a EvolvedPattern, + ) -> Option { self.table_for_places(schema, &pattern.attribute, &pattern.value) .map_err(|reason| { self.mark_known_empty(reason); @@ -877,7 +915,11 @@ impl ConjoiningClauses { .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 { // We know this one is known if the attribute lookup succeeds… &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 { EvolvedNonValuePlace::Entid(id) => - // We know this one is known if the attribute lookup succeeds… - schema.attribute_for_entid(id), + // We know this one is known if the attribute lookup succeeds… + { + schema.attribute_for_entid(id) + } EvolvedNonValuePlace::Variable(ref var) => - // 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 - // an attribute so we can find out the type. - self.value_bindings.get(var) - .and_then(|val| self.get_attribute_for_value(schema, val)), + // 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 + // an attribute so we can find out the type. + { + self.value_bindings + .get(var) + .and_then(|val| self.get_attribute_for_value(schema, val)) + } EvolvedNonValuePlace::Placeholder => None, } } - fn get_value_type<'s, 'a>(&self, schema: &'s Schema, pattern: &'a EvolvedPattern) -> Option { + fn get_value_type<'s, 'a>( + &self, + schema: &'s Schema, + pattern: &'a EvolvedPattern, + ) -> Option { self.get_attribute(schema, pattern).map(|a| a.value_type) } } /// Expansions. impl ConjoiningClauses { - /// Take the contents of `column_bindings` and generate inter-constraints for the appropriate /// columns into `wheres`. /// @@ -933,7 +987,10 @@ impl ConjoiningClauses { // TODO: if both primary and secondary are .v, should we make sure // the type tag columns also match? // 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. pub(crate) fn process_required_types(&mut self) -> Result<()> { 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 @@ -1011,9 +1068,10 @@ impl ConjoiningClauses { // Update known types. self.narrow_types_for_var(var.clone(), types); - let qa = self.extracted_types - .get(&var) - .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))?; + let qa = self + .extracted_types + .get(&var) + .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()))?; self.wheres.add_intersection(ColumnConstraint::HasTypes { value: qa.0.clone(), value_types: types, @@ -1053,14 +1111,18 @@ impl ConjoiningClauses { } impl ConjoiningClauses { - fn apply_evolved_patterns(&mut self, known: Known, mut patterns: VecDeque) -> Result<()> { + fn apply_evolved_patterns( + &mut self, + known: Known, + mut patterns: VecDeque, + ) -> Result<()> { while let Some(pattern) = patterns.pop_front() { match self.evolve_pattern(known, pattern) { PlaceOrEmpty::Place(re_evolved) => self.apply_pattern(known, re_evolved), PlaceOrEmpty::Empty(because) => { self.mark_known_empty(because); patterns.clear(); - }, + } } } Ok(()) @@ -1072,13 +1134,17 @@ impl ConjoiningClauses { } } - pub(crate) fn apply_clauses(&mut self, known: Known, where_clauses: Vec) -> Result<()> { + pub(crate) fn apply_clauses( + &mut self, + known: Known, + where_clauses: Vec, + ) -> Result<()> { // We apply (top level) type predicates first as an optimization. for clause in where_clauses.iter() { match clause { &WhereClause::TypeAnnotation(ref anno) => { self.apply_type_anno(anno)?; - }, + } // Patterns are common, so let's grab as much type information from // them as we can. @@ -1086,11 +1152,11 @@ impl ConjoiningClauses { self.mark_as_ref(&p.entity); self.mark_as_ref(&p.attribute); self.mark_as_ref(&p.tx); - }, + } // TODO: if we wish we can include other kinds of clauses in this type // extraction phase. - _ => {}, + _ => {} } } @@ -1105,13 +1171,11 @@ impl ConjoiningClauses { continue; } match clause { - WhereClause::Pattern(p) => { - match self.make_evolved_pattern(known, p) { - PlaceOrEmpty::Place(evolved) => patterns.push_back(evolved), - PlaceOrEmpty::Empty(because) => { - self.mark_known_empty(because); - return Ok(()); - } + WhereClause::Pattern(p) => match self.make_evolved_pattern(known, p) { + PlaceOrEmpty::Place(evolved) => patterns.push_back(evolved), + PlaceOrEmpty::Empty(because) => { + self.mark_known_empty(because); + return Ok(()); } }, _ => { @@ -1120,7 +1184,7 @@ impl ConjoiningClauses { patterns = VecDeque::with_capacity(remaining); } self.apply_clause(known, clause)?; - }, + } } } self.apply_evolved_patterns(known, patterns) @@ -1136,24 +1200,18 @@ impl ConjoiningClauses { PlaceOrEmpty::Empty(because) => self.mark_known_empty(because), } Ok(()) - }, - WhereClause::Pred(p) => { - self.apply_predicate(known, p) - }, - WhereClause::WhereFn(f) => { - self.apply_where_fn(known, f) - }, + } + WhereClause::Pred(p) => self.apply_predicate(known, p), + WhereClause::WhereFn(f) => self.apply_where_fn(known, f), WhereClause::OrJoin(o) => { validate_or_join(&o)?; self.apply_or_join(known, o) - }, + } WhereClause::NotJoin(n) => { validate_not_join(&n)?; self.apply_not_join(known, n) - }, - WhereClause::TypeAnnotation(anno) => { - self.apply_type_anno(&anno) - }, + } + WhereClause::TypeAnnotation(anno) => self.apply_type_anno(&anno), _ => unimplemented!(), } } diff --git a/query-algebrizer/src/clauses/not.rs b/query-algebrizer/src/clauses/not.rs index 9f97ccdd..2ba8117b 100644 --- a/query-algebrizer/src/clauses/not.rs +++ b/query-algebrizer/src/clauses/not.rs @@ -8,23 +8,13 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use edn::query::{ - ContainsVariables, - NotJoin, - UnifyVars, -}; +use edn::query::{ContainsVariables, NotJoin, UnifyVars}; use clauses::ConjoiningClauses; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; -use types::{ - ColumnConstraint, - ComputedTable, -}; +use types::{ColumnConstraint, ComputedTable}; use Known; @@ -78,7 +68,8 @@ impl ConjoiningClauses { let subquery = ComputedTable::Subquery(template); - self.wheres.add_intersection(ColumnConstraint::NotExists(subquery)); + self.wheres + .add_intersection(ColumnConstraint::NotExists(subquery)); Ok(()) } @@ -90,51 +81,22 @@ mod testing { use super::*; - use core_traits::{ - Attribute, - TypedValue, - ValueType, - ValueTypeSet, - }; + use core_traits::{Attribute, TypedValue, ValueType, ValueTypeSet}; - use mentat_core::{ - Schema, - }; + use mentat_core::Schema; - use edn::query::{ - Keyword, - PlainSymbol, - Variable - }; + use edn::query::{Keyword, PlainSymbol, Variable}; - use clauses::{ - QueryInputs, - add_attribute, - associate_ident, - }; + use clauses::{add_attribute, associate_ident, QueryInputs}; - use query_algebrizer_traits::errors::{ - AlgebrizerError, - }; + use query_algebrizer_traits::errors::AlgebrizerError; use types::{ - ColumnAlternation, - ColumnConstraint, - ColumnConstraintOrAlternation, - ColumnIntersection, - DatomsColumn, - DatomsTable, - Inequality, - QualifiedAlias, - QueryValue, - SourceAlias, + ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, ColumnIntersection, + DatomsColumn, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias, }; - use { - algebrize, - algebrize_with_inputs, - parse_find_string, - }; + use {algebrize, algebrize_with_inputs, parse_find_string}; fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { let known = Known::for_schema(schema); @@ -145,7 +107,9 @@ mod testing { fn alg_with_inputs(schema: &Schema, input: &str, inputs: QueryInputs) -> ConjoiningClauses { let known = Known::for_schema(schema); 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 { @@ -155,41 +119,51 @@ mod testing { 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", "height"), 69); - add_attribute(&mut schema, - 65, - Attribute { - value_type: ValueType::String, - multival: false, - ..Default::default() - }); - add_attribute(&mut schema, - 66, - Attribute { - value_type: ValueType::String, - multival: true, - ..Default::default() - }); - add_attribute(&mut schema, - 67, - Attribute { - value_type: ValueType::String, - 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() - }); + add_attribute( + &mut schema, + 65, + Attribute { + value_type: ValueType::String, + multival: false, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 66, + Attribute { + value_type: ValueType::String, + multival: true, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 67, + Attribute { + value_type: ValueType::String, + 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 } @@ -234,24 +208,58 @@ mod testing { let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let mut subquery = ConjoiningClauses::default(); - subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1), - SourceAlias(DatomsTable::Datoms, d2)]; - subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); - subquery.wheres = ColumnIntersection(vec![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.from = vec![ + SourceAlias(DatomsTable::Datoms, d1), + SourceAlias(DatomsTable::Datoms, d2), + ]; + subquery + .column_bindings + .insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); + subquery.wheres = ColumnIntersection(vec![ + 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_eq!(cc.wheres, ColumnIntersection(vec![ - 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.wheres, + ColumnIntersection(vec![ + 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.from, vec![SourceAlias(DatomsTable::Datoms, d0)]); } @@ -302,31 +310,72 @@ mod testing { let mut subquery = ConjoiningClauses::default(); subquery.from = vec![SourceAlias(DatomsTable::Datoms, d3)]; - subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d3e.clone()]); - subquery.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 + .column_bindings + .insert(vx.clone(), vec![d0e.clone(), d3e.clone()]); + subquery + .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.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String)); + subquery + .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()); let expected_wheres = ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), age.clone())), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.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()))), - ]); + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + age.clone(), + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1v.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.column_bindings.get(&vx), Some(&vec![d0e, d1e, d2e])); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), - SourceAlias(DatomsTable::Datoms, d1), - SourceAlias(DatomsTable::Datoms, d2)]); + assert_eq!( + cc.from, + vec![ + SourceAlias(DatomsTable::Datoms, d0), + SourceAlias(DatomsTable::Datoms, d1), + SourceAlias(DatomsTable::Datoms, d2) + ] + ); } // Not with a pattern and a predicate. @@ -366,28 +415,62 @@ mod testing { let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); let mut subquery = ConjoiningClauses::default(); - subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1), - SourceAlias(DatomsTable::Datoms, d2)]; - subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); - subquery.wheres = ColumnIntersection(vec![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.from = vec![ + SourceAlias(DatomsTable::Datoms, d1), + SourceAlias(DatomsTable::Datoms, d2), + ]; + subquery + .column_bindings + .insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); + subquery.wheres = ColumnIntersection(vec![ + 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_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())), + assert_eq!( + cc.wheres, + ColumnIntersection(vec![ + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0a.clone(), + age.clone() + )), ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality { operator: Inequality::LessThan, left: QueryValue::Column(d0v.clone()), 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.from, vec![SourceAlias(DatomsTable::Datoms, d0)]); } @@ -429,32 +512,76 @@ mod testing { let ambar = QueryValue::TypedValue(TypedValue::typed_string("Ámbar")); let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); - let mut subquery = ConjoiningClauses::default(); - subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1), - SourceAlias(DatomsTable::Datoms, d2)]; - subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); - subquery.wheres = ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![ - ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.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.from = vec![ + SourceAlias(DatomsTable::Datoms, d1), + SourceAlias(DatomsTable::Datoms, d2), + ]; + subquery + .column_bindings + .insert(vx.clone(), vec![d0e.clone(), d1e.clone(), d2e.clone()]); + subquery.wheres = ColumnIntersection(vec![ + ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![ + ColumnIntersection(vec![ + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + knows.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_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), - ])); + assert_eq!( + cc.wheres, + ColumnIntersection(vec![ + 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 @@ -467,9 +594,10 @@ mod testing { :where [?x :foo/knows "Bill"] (not [?x :foo/knows ?y])]"#; - let inputs = QueryInputs::with_value_sequence(vec![ - (Variable::from_valid_name("?y"), "John".into()) - ]); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?y"), + "John".into(), + )]); let cc = alg_with_inputs(&schema, query, inputs); let vx = Variable::from_valid_name("?x"); @@ -492,25 +620,52 @@ mod testing { let mut subquery = ConjoiningClauses::default(); subquery.from = vec![SourceAlias(DatomsTable::Datoms, d1)]; - subquery.column_bindings.insert(vx.clone(), vec![d0e.clone(), 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 + .column_bindings + .insert(vx.clone(), vec![d0e.clone(), 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.known_types.insert(vy.clone(), ValueTypeSet::of_one(ValueType::String)); + subquery + .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 = BTreeSet::default(); input_vars.insert(vy.clone()); 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_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), bill)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::NotExists(ComputedTable::Subquery(subquery))), - ])); + assert_eq!( + cc.wheres, + ColumnIntersection(vec![ + 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 @@ -525,9 +680,10 @@ mod testing { [?x :foo/nope "Daphne"])]"#; let cc = alg(&schema, query); assert!(!cc.is_known_empty()); - compare_ccs(cc, - alg(&schema, - r#"[:find ?x :where [?x :foo/knows "Bill"]]"#)); + compare_ccs( + cc, + 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. @@ -541,9 +697,10 @@ mod testing { [?x :foo/nope "Daphne"])]"#; let cc = alg(&schema, query); assert!(!cc.is_known_empty()); - compare_ccs(cc, - alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#)); - + compare_ccs( + cc, + alg(&schema, r#"[:find ?x :where [?x :foo/knows "John"]]"#), + ); } #[test] @@ -557,7 +714,9 @@ mod testing { let parsed = parse_find_string(query).expect("parse failed"); let err = algebrize(known, parsed).expect_err("algebrization should have failed"); 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), } } diff --git a/query-algebrizer/src/clauses/or.rs b/query-algebrizer/src/clauses/or.rs index 2b378e7b..e2658c9b 100644 --- a/query-algebrizer/src/clauses/or.rs +++ b/query-algebrizer/src/clauses/or.rs @@ -9,46 +9,22 @@ // specific language governing permissions and limitations under the License. use std::collections::btree_map::Entry; -use std::collections::{ - BTreeMap, - BTreeSet, -}; +use std::collections::{BTreeMap, BTreeSet}; -use core_traits::{ - ValueTypeSet, -}; +use core_traits::ValueTypeSet; use edn::query::{ - OrJoin, - OrWhereClause, - Pattern, - PatternValuePlace, - PatternNonValuePlace, - UnifyVars, - Variable, + OrJoin, OrWhereClause, Pattern, PatternNonValuePlace, PatternValuePlace, UnifyVars, Variable, WhereClause, }; -use clauses::{ - ConjoiningClauses, - PushComputed, -}; +use clauses::{ConjoiningClauses, PushComputed}; -use query_algebrizer_traits::errors::{ - Result, -}; +use query_algebrizer_traits::errors::Result; use types::{ - ColumnConstraintOrAlternation, - ColumnAlternation, - ColumnIntersection, - ComputedTable, - DatomsTable, - EmptyBecause, - EvolvedPattern, - PlaceOrEmpty, - QualifiedAlias, - SourceAlias, + ColumnAlternation, ColumnConstraintOrAlternation, ColumnIntersection, ComputedTable, + DatomsTable, EmptyBecause, EvolvedPattern, PlaceOrEmpty, QualifiedAlias, SourceAlias, VariableColumn, }; @@ -59,10 +35,10 @@ fn _simply_matches_place(left: &PatternNonValuePlace, right: &PatternNonValuePla match (left, right) { (&PatternNonValuePlace::Variable(ref a), &PatternNonValuePlace::Variable(ref b)) => a == b, (&PatternNonValuePlace::Placeholder, &PatternNonValuePlace::Placeholder) => true, - (&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Entid(_)) => true, - (&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Ident(_)) => true, - (&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Ident(_)) => true, - (&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Entid(_)) => true, + (&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Entid(_)) => true, + (&PatternNonValuePlace::Entid(_), &PatternNonValuePlace::Ident(_)) => true, + (&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Ident(_)) => true, + (&PatternNonValuePlace::Ident(_), &PatternNonValuePlace::Entid(_)) => true, _ => false, } } @@ -101,7 +77,7 @@ impl ConjoiningClauses { OrWhereClause::And(clauses) => { self.apply_clauses(known, clauses)?; Ok(()) - }, + } } } @@ -117,7 +93,7 @@ impl ConjoiningClauses { 1 if or_join.is_fully_unified() => { let clause = or_join.clauses.pop().expect("there's a clause"); self.apply_or_where_clause(known, clause) - }, + } // Either there's only one clause pattern, and it's not fully unified, or we // have multiple clauses. // 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()) { Place((aaa, value_type)) => { match self.make_evolved_value(&known, value_type, p.value.clone()) { - Place(v) => { - self.table_for_places(known.schema, &aaa, &v) - }, + Place(v) => self.table_for_places(known.schema, &aaa, &v), Empty(e) => Err(e), } - }, + } Empty(e) => Err(e), }; @@ -237,20 +211,19 @@ impl ConjoiningClauses { // Do not accumulate this pattern at all. Add lightness! continue; - }, + } Ok(table) => { // Check the shape of the pattern against a previous pattern. - let same_shape = - if let Some(template) = patterns.get(0) { - template.source == p.source && // or-arms all use the same source anyway. + let same_shape = if let Some(template) = patterns.get(0) { + template.source == p.source && // or-arms all use the same source anyway. _simply_matches_place(&template.entity, &p.entity) && _simply_matches_place(&template.attribute, &p.attribute) && _simply_matches_value_place(&template.value, &p.value) && _simply_matches_place(&template.tx, &p.tx) - } else { - // No previous pattern. - true - }; + } else { + // No previous pattern. + true + }; // 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`! @@ -286,10 +259,7 @@ impl ConjoiningClauses { .chain(clauses) .collect(); - return DeconstructedOrJoin::Complex(OrJoin::new( - UnifyVars::Implicit, - reconstructed, - )); + return DeconstructedOrJoin::Complex(OrJoin::new(UnifyVars::Implicit, reconstructed)); } // If we got here without returning, then `patterns` is what we're working with. @@ -298,7 +268,7 @@ impl ConjoiningClauses { 0 => { assert!(empty_because.is_some()); DeconstructedOrJoin::KnownEmpty(empty_because.unwrap()) - }, + } 1 => DeconstructedOrJoin::UnitPattern(patterns.pop().unwrap()), _ => DeconstructedOrJoin::Simple(patterns, mentioned_vars), } @@ -309,43 +279,42 @@ impl ConjoiningClauses { DeconstructedOrJoin::KnownSuccess => { // The pattern came to us empty -- `(or)`. Do nothing. Ok(()) - }, + } DeconstructedOrJoin::KnownEmpty(reason) => { // There were no arms of the join that could be mapped to a table. // The entire `or`, and thus the CC, cannot yield results. self.mark_known_empty(reason); Ok(()) - }, + } DeconstructedOrJoin::Unit(clause) => { // There was only one clause. We're unifying all variables, so we can just apply here. self.apply_or_where_clause(known, clause) - }, + } DeconstructedOrJoin::UnitPattern(pattern) => { // Same, but simpler. match self.make_evolved_pattern(known, pattern) { PlaceOrEmpty::Empty(e) => { self.mark_known_empty(e); - }, + } PlaceOrEmpty::Place(pattern) => { self.apply_pattern(known, pattern); - }, + } }; Ok(()) - }, + } DeconstructedOrJoin::Simple(patterns, mentioned_vars) => { // 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, // using a single table alias. self.apply_simple_or_join(known, patterns, mentioned_vars) - }, + } DeconstructedOrJoin::Complex(or_join) => { // Do this the hard way. self.apply_complex_or_join(known, or_join) - }, + } } } - /// 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 /// @@ -373,27 +342,30 @@ impl ConjoiningClauses { /// OR (datoms00.a = 98 AND datoms00.v = 'Peter') /// ``` /// - fn apply_simple_or_join(&mut self, - known: Known, - patterns: Vec, - mentioned_vars: BTreeSet) - -> Result<()> { + fn apply_simple_or_join( + &mut self, + known: Known, + patterns: Vec, + mentioned_vars: BTreeSet, + ) -> Result<()> { if self.is_known_empty() { - return Ok(()) + return Ok(()); } assert!(patterns.len() >= 2); - let patterns: Vec = patterns.into_iter().filter_map(|pattern| { - match self.make_evolved_pattern(known, pattern) { - PlaceOrEmpty::Empty(_e) => { - // Never mind. - None - }, - PlaceOrEmpty::Place(p) => Some(p), - } - }).collect(); - + let patterns: Vec = patterns + .into_iter() + .filter_map(|pattern| { + match self.make_evolved_pattern(known, pattern) { + PlaceOrEmpty::Empty(_e) => { + // Never mind. + None + } + PlaceOrEmpty::Place(p) => Some(p), + } + }) + .collect(); // 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 @@ -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 // 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. let mut folded = ConjoiningClauses::default(); @@ -432,24 +406,26 @@ impl ConjoiningClauses { // :where [?a :some/int ?x] // [_ :some/otherint ?x]] // ``` - let mut receptacles = - patterns.into_iter() - .map(|pattern| { - let mut receptacle = template.make_receptacle(); - receptacle.apply_pattern_clause_for_alias(known, &pattern, &source_alias); - receptacle - }) - .peekable(); + let mut receptacles = patterns + .into_iter() + .map(|pattern| { + let mut receptacle = template.make_receptacle(); + receptacle.apply_pattern_clause_for_alias(known, &pattern, &source_alias); + receptacle + }) + .peekable(); // Let's see if we can grab a reason if every pattern failed. // If every pattern failed, we can just take the first! - let reason = receptacles.peek() - .map(|r| r.empty_because.clone()) - .unwrap_or(None); + let reason = receptacles + .peek() + .map(|r| r.empty_because.clone()) + .unwrap_or(None); // Filter out empties. - let mut receptacles = receptacles.filter(|receptacle| !receptacle.is_known_empty()) - .peekable(); + let mut receptacles = receptacles + .filter(|receptacle| !receptacle.is_known_empty()) + .peekable(); // 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. @@ -460,10 +436,10 @@ impl ConjoiningClauses { match self.column_bindings.entry(v.clone()) { Entry::Vacant(e) => { e.insert(cols.clone()); - }, + } Entry::Occupied(mut e) => { e.get_mut().append(&mut cols.clone()); - }, + } } } } 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 // this point we'll simply state that `?x` and `?y` can both be integers or strings. - fn vec_for_iterator(iter: &I) -> Vec where I: Iterator { + fn vec_for_iterator(iter: &I) -> Vec + where + I: Iterator, + { match iter.size_hint().1 { None => Vec::new(), Some(expected) => Vec::with_capacity(expected), @@ -593,10 +572,10 @@ impl ConjoiningClauses { match clause { OrWhereClause::And(clauses) => { receptacle.apply_clauses(known, clauses)?; - }, + } OrWhereClause::Clause(clause) => { receptacle.apply_clause(known, clause)?; - }, + } } if receptacle.is_known_empty() { empty_because = receptacle.empty_because; @@ -681,10 +660,11 @@ impl ConjoiningClauses { // Note that we start with the first clause's type information. { let mut clauses = acc.iter(); - let mut additional_types = clauses.next() - .expect("there to be at least one clause") - .known_types - .clone(); + let mut additional_types = clauses + .next() + .expect("there to be at least one clause") + .known_types + .clone(); for cc in clauses { 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. let schema = known.schema; 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() { - 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)); Ok(()) @@ -713,8 +701,10 @@ impl ConjoiningClauses { } /// Helper to fold together a set of type maps. -fn union_types(into: &mut BTreeMap, - additional_types: &BTreeMap) { +fn union_types( + into: &mut BTreeMap, + additional_types: &BTreeMap, +) { // We want the exclusive disjunction -- any variable not mentioned in both sets -- to default // to ValueTypeSet::Any. // 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, { let i: BTreeSet<&Variable> = into.keys().collect(); let a: BTreeSet<&Variable> = additional_types.keys().collect(); - any = i.symmetric_difference(&a) - .map(|v| ((*v).clone(), ValueTypeSet::any())) - .collect(); + any = i + .symmetric_difference(&a) + .map(|v| ((*v).clone(), ValueTypeSet::any())) + .collect(); } // Collect the additional types. @@ -737,11 +728,11 @@ fn union_types(into: &mut BTreeMap, match into.entry(var.clone()) { Entry::Vacant(e) => { e.insert(new_types.clone()); - }, + } Entry::Occupied(mut e) => { let new = e.get().union(&new_types); e.insert(new); - }, + } } } @@ -753,41 +744,20 @@ fn union_types(into: &mut BTreeMap, mod testing { use super::*; - use core_traits::{ - Attribute, - ValueType, - TypedValue, - }; + use core_traits::{Attribute, TypedValue, ValueType}; - use mentat_core::{ - Schema, - }; + use mentat_core::Schema; - use edn::query::{ - Keyword, - Variable, - }; + use edn::query::{Keyword, Variable}; - use clauses::{ - add_attribute, - associate_ident, - }; + use clauses::{add_attribute, associate_ident}; use types::{ - ColumnConstraint, - DatomsColumn, - DatomsTable, - Inequality, - QualifiedAlias, - QueryValue, + ColumnConstraint, DatomsColumn, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias, }; - use { - algebrize, - algebrize_with_counter, - parse_find_string, - }; + use {algebrize, algebrize_with_counter, parse_find_string}; fn alg(known: Known, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); @@ -798,7 +768,9 @@ mod testing { /// simpler version. fn alg_c(known: Known, counter: usize, input: &str) -> ConjoiningClauses { 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) { @@ -813,31 +785,51 @@ mod testing { 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", "height"), 69); - add_attribute(&mut schema, 65, Attribute { - value_type: ValueType::String, - multival: false, - ..Default::default() - }); - add_attribute(&mut schema, 66, Attribute { - value_type: ValueType::String, - multival: true, - ..Default::default() - }); - add_attribute(&mut schema, 67, Attribute { - value_type: ValueType::String, - 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() - }); + add_attribute( + &mut schema, + 65, + Attribute { + value_type: ValueType::String, + multival: false, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 66, + Attribute { + value_type: ValueType::String, + multival: true, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 67, + Attribute { + value_type: ValueType::String, + 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 } @@ -853,7 +845,12 @@ mod testing { [?x :foo/nope3 "Daphne"])]"#; let cc = alg(known, query); 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. @@ -868,7 +865,10 @@ mod testing { [?x :foo/nope "Daphne"])]"#; let cc = alg(known, query); 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. @@ -894,19 +894,43 @@ mod testing { let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); assert!(!cc.is_known_empty()); - assert_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Alternation( + assert_eq!( + cc.wheres, + ColumnIntersection(vec![ColumnConstraintOrAlternation::Alternation( ColumnAlternation(vec![ ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows.clone())), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), john))]), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0a.clone(), + knows.clone() + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0v.clone(), + john + )) + ]), ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), parent)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), ambar))]), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0a.clone(), + parent + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0v.clone(), + ambar + )) + ]), ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), knows)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0v.clone(), daphne))]), - ]))])); + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0a.clone(), + knows + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0v.clone(), + daphne + )) + ]), + ]) + )]) + ); assert_eq!(cc.column_bindings.get(&vx), Some(&vec![d0e])); assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0)]); } @@ -940,26 +964,60 @@ mod testing { let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); assert!(!cc.is_known_empty()); - assert_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), name.clone())), - ColumnConstraintOrAlternation::Alternation( - ColumnAlternation(vec![ + assert_eq!( + cc.wheres, + ColumnIntersection(vec![ + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0a.clone(), + name.clone() + )), + ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![ ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + knows.clone() + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1v.clone(), + john + )) + ]), ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), parent)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), ambar))]), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + parent + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1v.clone(), + ambar + )) + ]), ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), daphne))]), - ])), - // The outer pattern joins against the `or`. - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), - ])); + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + knows + )), + 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.from, vec![SourceAlias(DatomsTable::Datoms, d0), - SourceAlias(DatomsTable::Datoms, d1)]); + assert_eq!( + cc.from, + vec![ + SourceAlias(DatomsTable::Datoms, d0), + SourceAlias(DatomsTable::Datoms, d1) + ] + ); } // Alternation with a pattern and a predicate. @@ -990,28 +1048,55 @@ mod testing { let daphne = QueryValue::TypedValue(TypedValue::typed_string("Daphne")); assert!(!cc.is_known_empty()); - assert_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0a.clone(), age.clone())), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality { - operator: Inequality::LessThan, - left: QueryValue::Column(d0v.clone()), - right: QueryValue::TypedValue(TypedValue::Long(30)), - }), - ColumnConstraintOrAlternation::Alternation( - ColumnAlternation(vec![ + assert_eq!( + cc.wheres, + ColumnIntersection(vec![ + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d0a.clone(), + age.clone() + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Inequality { + operator: Inequality::LessThan, + left: QueryValue::Column(d0v.clone()), + right: QueryValue::TypedValue(TypedValue::Long(30)), + }), + ColumnConstraintOrAlternation::Alternation(ColumnAlternation(vec![ ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows.clone())), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), john))]), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + knows.clone() + )), + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1v.clone(), + john + )) + ]), ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1a.clone(), knows)), - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d1v.clone(), daphne))]), - ])), - // The outer pattern joins against the `or`. - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(d0e.clone(), QueryValue::Column(d1e.clone()))), - ])); + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + d1a.clone(), + knows + )), + 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.from, vec![SourceAlias(DatomsTable::Datoms, d0), - SourceAlias(DatomsTable::Datoms, d1)]); + assert_eq!( + cc.from, + vec![ + SourceAlias(DatomsTable::Datoms, d0), + SourceAlias(DatomsTable::Datoms, d1) + ] + ); } // These two are not equivalent: @@ -1036,18 +1121,32 @@ mod testing { let knows = QueryValue::Entid(66); assert!(!cc.is_known_empty()); - assert_eq!(cc.wheres, ColumnIntersection(vec![ - ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals(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.wheres, + ColumnIntersection(vec![ + ColumnConstraintOrAlternation::Constraint(ColumnConstraint::Equals( + 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])); // ?y does not have a binding in the `or-join` pattern. assert_eq!(cc.column_bindings.get(&vy), Some(&vec![d0v])); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, d0), - SourceAlias(DatomsTable::Computed(0), c0)]); + assert_eq!( + cc.from, + vec![ + SourceAlias(DatomsTable::Datoms, d0), + SourceAlias(DatomsTable::Computed(0), c0) + ] + ); } // These two are equivalent: @@ -1057,14 +1156,13 @@ mod testing { fn test_unit_or_does_flatten() { let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - let or_query = r#"[:find ?x + let or_query = r#"[:find ?x :where [?x :foo/knows ?y] (or [?x :foo/parent ?y])]"#; let flat_query = r#"[:find ?x :where [?x :foo/knows ?y] [?x :foo/parent ?y]]"#; - compare_ccs(alg(known, or_query), - alg(known, flat_query)); + compare_ccs(alg(known, or_query), alg(known, flat_query)); } // Elision of `and`. @@ -1072,14 +1170,13 @@ mod testing { fn test_unit_or_and_does_flatten() { let schema = prepopulated_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] [?x :foo/age 7]))]"#; - let flat_query = r#"[:find ?x + let flat_query = r#"[:find ?x :where [?x :foo/parent ?y] [?x :foo/age 7]]"#; - compare_ccs(alg(known, or_query), - alg(known, flat_query)); + compare_ccs(alg(known, or_query), alg(known, flat_query)); } // Alternation with `and`. @@ -1101,31 +1198,45 @@ mod testing { let cc = alg(known, query); let mut tables = cc.computed_tables.into_iter(); 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()); let mut arms = arms.into_iter(); match (arms.next(), arms.next(), arms.next()) { (Some(and), Some(pattern), None) => { - let expected_and = alg_c(known, - 0, // The first pattern to be processed. - r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#); + let expected_and = alg_c( + known, + 0, // The first pattern to be processed. + r#"[:find ?x :where [?x :foo/knows "John"] [?x :foo/parent "Ámbar"]]"#, + ); compare_ccs(and, expected_and); - let expected_pattern = alg_c(known, - 2, // Two aliases taken by the other arm. - r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#); + let expected_pattern = alg_c( + known, + 2, // Two aliases taken by the other arm. + r#"[:find ?x :where [?x :foo/knows "Daphne"]]"#, + ); compare_ccs(pattern, expected_pattern); - }, + } _ => { panic!("Expected two arms"); } } - }, + } _ => { panic!("Didn't get two inner tables."); - }, + } } } diff --git a/query-algebrizer/src/clauses/pattern.rs b/query-algebrizer/src/clauses/pattern.rs index a26e47a0..635dae9c 100644 --- a/query-algebrizer/src/clauses/pattern.rs +++ b/query-algebrizer/src/clauses/pattern.rs @@ -8,47 +8,26 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - Entid, - ValueType, - TypedValue, - ValueTypeSet, -}; +use core_traits::{Entid, TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - Cloned, - HasSchema, -}; +use mentat_core::{Cloned, HasSchema}; use edn::query::{ - NonIntegerConstant, - Pattern, - PatternValuePlace, - PatternNonValuePlace, - SrcVar, - Variable, + NonIntegerConstant, Pattern, PatternNonValuePlace, PatternValuePlace, SrcVar, Variable, }; -use clauses::{ - ConjoiningClauses, -}; +use clauses::ConjoiningClauses; use types::{ - ColumnConstraint, - DatomsColumn, - EmptyBecause, - EvolvedNonValuePlace, - EvolvedPattern, - EvolvedValuePlace, - PlaceOrEmpty, - SourceAlias, + ColumnConstraint, DatomsColumn, EmptyBecause, EvolvedNonValuePlace, EvolvedPattern, + EvolvedValuePlace, PlaceOrEmpty, SourceAlias, }; use Known; pub fn into_typed_value(nic: NonIntegerConstant) -> TypedValue { match nic { - NonIntegerConstant::BigInteger(_) => unimplemented!(), // TODO: #280. + NonIntegerConstant::BigInteger(_) => unimplemented!(), // TODO: #280. NonIntegerConstant::Boolean(v) => TypedValue::Boolean(v), NonIntegerConstant::Float(v) => TypedValue::Double(v), NonIntegerConstant::Text(v) => v.into(), @@ -59,7 +38,6 @@ pub fn into_typed_value(nic: NonIntegerConstant) -> TypedValue { /// Application of patterns. impl ConjoiningClauses { - /// Apply the constraints in the provided pattern to this CC. /// /// This is a single-pass process, which means it is naturally incomplete, failing to take into @@ -95,7 +73,12 @@ impl ConjoiningClauses { /// existence subquery instead of a join. /// /// This method is only public for use from `or.rs`. - pub(crate) fn apply_pattern_clause_for_alias(&mut self, known: Known, pattern: &EvolvedPattern, alias: &SourceAlias) { + pub(crate) fn apply_pattern_clause_for_alias( + &mut self, + known: Known, + pattern: &EvolvedPattern, + alias: &SourceAlias, + ) { if self.is_known_empty() { return; } @@ -115,21 +98,25 @@ impl ConjoiningClauses { let schema = known.schema; match pattern.entity { EvolvedNonValuePlace::Placeholder => - // Placeholders don't contribute any column bindings, nor do - // they constrain the query -- there's no need to produce - // IS NOT NULL, because we don't store nulls in our schema. - (), - EvolvedNonValuePlace::Variable(ref v) => - self.bind_column_to_var(schema, col.clone(), DatomsColumn::Entity, v.clone()), - EvolvedNonValuePlace::Entid(entid) => - self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid), + // Placeholders don't contribute any column bindings, nor do + // they constrain the query -- there's no need to produce + // IS NOT NULL, because we don't store nulls in our schema. + { + () + } + EvolvedNonValuePlace::Variable(ref v) => { + self.bind_column_to_var(schema, col.clone(), DatomsColumn::Entity, v.clone()) + } + EvolvedNonValuePlace::Entid(entid) => { + self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid) + } } match pattern.attribute { - EvolvedNonValuePlace::Placeholder => - (), - EvolvedNonValuePlace::Variable(ref v) => - self.bind_column_to_var(schema, col.clone(), DatomsColumn::Attribute, v.clone()), + EvolvedNonValuePlace::Placeholder => (), + EvolvedNonValuePlace::Variable(ref v) => { + self.bind_column_to_var(schema, col.clone(), DatomsColumn::Attribute, v.clone()) + } EvolvedNonValuePlace::Entid(entid) => { if !schema.is_attribute(entid) { // Furthermore, that entid must resolve to an attribute. If it doesn't, this @@ -138,7 +125,7 @@ impl ConjoiningClauses { return; } self.constrain_attribute(col.clone(), entid) - }, + } } // Determine if the pattern's value type is known. @@ -149,8 +136,7 @@ impl ConjoiningClauses { let value_type = self.get_value_type(schema, pattern); match pattern.value { - EvolvedValuePlace::Placeholder => - (), + EvolvedValuePlace::Placeholder => (), EvolvedValuePlace::Variable(ref v) => { if let Some(this_type) = value_type { @@ -163,22 +149,24 @@ impl ConjoiningClauses { } self.bind_column_to_var(schema, col.clone(), DatomsColumn::Value, v.clone()); - }, - EvolvedValuePlace::Entid(i) => { - match value_type { - Some(ValueType::Ref) | None => { - self.constrain_column_to_entity(col.clone(), DatomsColumn::Value, i); - }, - Some(value_type) => { - self.mark_known_empty(EmptyBecause::ValueTypeMismatch(value_type, TypedValue::Ref(i))); - }, + } + EvolvedValuePlace::Entid(i) => match value_type { + Some(ValueType::Ref) | None => { + self.constrain_column_to_entity(col.clone(), DatomsColumn::Value, i); + } + Some(value_type) => { + self.mark_known_empty(EmptyBecause::ValueTypeMismatch( + value_type, + TypedValue::Ref(i), + )); } }, EvolvedValuePlace::EntidOrInteger(i) => - // If we know the valueType, then we can determine whether this is an entid or an - // integer. If we don't, then we must generate a more general query with a - // value_type_tag. + // If we know the valueType, then we can determine whether this is an entid or an + // integer. If we don't, then we must generate a more general query with a + // value_type_tag. + { if let Some(ValueType::Ref) = value_type { self.constrain_column_to_entity(col.clone(), DatomsColumn::Value, i); } else { @@ -196,7 +184,8 @@ impl ConjoiningClauses { // TODO: isn't there a bug here? We'll happily take a numeric value // for a non-numeric attribute! self.constrain_value_to_numeric(col.clone(), i); - }, + } + } EvolvedValuePlace::IdentOrKeyword(ref kw) => { // If we know the valueType, then we can determine whether this is an ident or a // keyword. If we don't, then we must generate a more general query with a @@ -206,7 +195,11 @@ impl ConjoiningClauses { // such. if let Some(ValueType::Ref) = value_type { if let Some(entid) = self.entid_for_ident(schema, kw) { - self.constrain_column_to_entity(col.clone(), DatomsColumn::Value, entid.into()) + self.constrain_column_to_entity( + col.clone(), + DatomsColumn::Value, + entid.into(), + ) } else { // A resolution failure means we're done here: this attribute must have an // entity value. @@ -215,10 +208,18 @@ impl ConjoiningClauses { } } else { // It must be a keyword. - self.constrain_column_to_constant(col.clone(), DatomsColumn::Value, TypedValue::Keyword(kw.clone())); - self.wheres.add_intersection(ColumnConstraint::has_unit_type(col.clone(), ValueType::Keyword)); + self.constrain_column_to_constant( + col.clone(), + DatomsColumn::Value, + TypedValue::Keyword(kw.clone()), + ); + self.wheres + .add_intersection(ColumnConstraint::has_unit_type( + col.clone(), + ValueType::Keyword, + )); }; - }, + } EvolvedValuePlace::Value(ref c) => { // TODO: don't allocate. let typed_value = c.clone(); @@ -252,24 +253,33 @@ impl ConjoiningClauses { // Because everything we handle here is unambiguous, we generate a single type // restriction from the value type of the typed value. if value_type.is_none() { - self.wheres.add_intersection( - ColumnConstraint::has_unit_type(col.clone(), typed_value_type)); + self.wheres + .add_intersection(ColumnConstraint::has_unit_type( + col.clone(), + typed_value_type, + )); } - }, + } } match pattern.tx { EvolvedNonValuePlace::Placeholder => (), EvolvedNonValuePlace::Variable(ref v) => { self.bind_column_to_var(schema, col.clone(), DatomsColumn::Tx, v.clone()); - }, + } EvolvedNonValuePlace::Entid(entid) => { self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid); - }, + } } } - fn reverse_lookup(&mut self, known: Known, var: &Variable, attr: Entid, val: &TypedValue) -> bool { + fn reverse_lookup( + &mut self, + known: Known, + var: &Variable, + attr: Entid, + val: &TypedValue, + ) -> bool { if let Some(attribute) = known.schema.attribute_for_entid(attr) { let unique = attribute.unique.is_some(); if unique { @@ -280,11 +290,11 @@ impl ConjoiningClauses { attr: attr, }); true - }, + } Some(item) => { self.bind_value(var, TypedValue::Ref(item)); true - }, + } } } else { match known.get_entids_for_value(attr, val) { @@ -294,7 +304,7 @@ impl ConjoiningClauses { attr: attr, }); true - }, + } Some(items) => { if items.len() == 1 { let item = items.iter().next().cloned().unwrap(); @@ -305,7 +315,7 @@ impl ConjoiningClauses { // TODO: handle multiple values. false } - }, + } } } } else { @@ -340,9 +350,9 @@ impl ConjoiningClauses { let cached_forward = known.is_attribute_cached_forward(attr); let cached_reverse = known.is_attribute_cached_reverse(attr); - if (cached_forward || cached_reverse) && - pattern.tx == EvolvedNonValuePlace::Placeholder { - + if (cached_forward || cached_reverse) + && pattern.tx == EvolvedNonValuePlace::Placeholder + { let attribute = schema.attribute_for_entid(attr).unwrap(); // There are two patterns we can handle: @@ -359,41 +369,46 @@ impl ConjoiningClauses { // It's an ident. // TODO return false; - }, + } ValueType::Keyword => { let tv: TypedValue = TypedValue::Keyword(kw.clone()); return self.reverse_lookup(known, var, attr, &tv); - }, + } t => { let tv: TypedValue = TypedValue::Keyword(kw.clone()); // Anything else can't match an IdentOrKeyword. - self.mark_known_empty(EmptyBecause::ValueTypeMismatch(t, tv)); + self.mark_known_empty(EmptyBecause::ValueTypeMismatch( + t, tv, + )); return true; - }, + } } - }, + } EvolvedValuePlace::Value(ref val) => { if cached_reverse { return self.reverse_lookup(known, var, attr, val); } } - _ => {}, // TODO: check constant values against cache. + _ => {} // TODO: check constant values against cache. } - }, + } // Forward lookup. EvolvedNonValuePlace::Entid(entity) => { match pattern.value { EvolvedValuePlace::Variable(ref var) => { if cached_forward { - match known.get_value_for_entid(known.schema, attr, entity) { + match known.get_value_for_entid(known.schema, attr, entity) + { None => { - self.mark_known_empty(EmptyBecause::CachedAttributeHasNoValues { - entity: entity, - attr: attr, - }); + self.mark_known_empty( + EmptyBecause::CachedAttributeHasNoValues { + entity: entity, + attr: attr, + }, + ); return true; - }, + } Some(item) => { self.bind_value(var, item.clone()); return true; @@ -401,21 +416,26 @@ impl ConjoiningClauses { } } } - _ => {}, // TODO: check constant values against cache. + _ => {} // TODO: check constant values against cache. } - }, - _ => {}, + } + _ => {} } } - }, - _ => {}, + } + _ => {} } false } /// Transform a pattern place into a narrower type. /// If that's impossible, returns Empty. - fn make_evolved_non_value(&self, known: &Known, col: DatomsColumn, non_value: PatternNonValuePlace) -> PlaceOrEmpty { + fn make_evolved_non_value( + &self, + known: &Known, + col: DatomsColumn, + non_value: PatternNonValuePlace, + ) -> PlaceOrEmpty { use self::PlaceOrEmpty::*; match non_value { PatternNonValuePlace::Placeholder => Place(EvolvedNonValuePlace::Placeholder), @@ -427,7 +447,7 @@ impl ConjoiningClauses { } else { Empty(EmptyBecause::UnresolvedIdent((&*kw).clone())) } - }, + } PatternNonValuePlace::Variable(var) => { // See if we have it! match self.bound_value(&var) { @@ -440,25 +460,35 @@ impl ConjoiningClauses { } else { Empty(EmptyBecause::UnresolvedIdent((&*kw).clone())) } - }, - Some(v) => { - Empty(EmptyBecause::InvalidBinding(col.into(), v)) - }, + } + Some(v) => Empty(EmptyBecause::InvalidBinding(col.into(), v)), } - }, + } } } - fn make_evolved_entity(&self, known: &Known, entity: PatternNonValuePlace) -> PlaceOrEmpty { + fn make_evolved_entity( + &self, + known: &Known, + entity: PatternNonValuePlace, + ) -> PlaceOrEmpty { self.make_evolved_non_value(known, DatomsColumn::Entity, entity) } - fn make_evolved_tx(&self, known: &Known, tx: PatternNonValuePlace) -> PlaceOrEmpty { + fn make_evolved_tx( + &self, + known: &Known, + tx: PatternNonValuePlace, + ) -> PlaceOrEmpty { // TODO: make sure that, if it's an entid, it names a tx. self.make_evolved_non_value(known, DatomsColumn::Tx, tx) } - pub(crate) fn make_evolved_attribute(&self, known: &Known, attribute: PatternNonValuePlace) -> PlaceOrEmpty<(EvolvedNonValuePlace, Option)> { + pub(crate) fn make_evolved_attribute( + &self, + known: &Known, + attribute: PatternNonValuePlace, + ) -> PlaceOrEmpty<(EvolvedNonValuePlace, Option)> { use self::PlaceOrEmpty::*; self.make_evolved_non_value(known, DatomsColumn::Attribute, attribute) .and_then(|a| { @@ -475,21 +505,21 @@ impl ConjoiningClauses { }) } - pub(crate) fn make_evolved_value(&self, - known: &Known, - value_type: Option, - value: PatternValuePlace) -> PlaceOrEmpty { + pub(crate) fn make_evolved_value( + &self, + known: &Known, + value_type: Option, + value: PatternValuePlace, + ) -> PlaceOrEmpty { use self::PlaceOrEmpty::*; match value { PatternValuePlace::Placeholder => Place(EvolvedValuePlace::Placeholder), - PatternValuePlace::EntidOrInteger(e) => { - match value_type { - Some(ValueType::Ref) => Place(EvolvedValuePlace::Entid(e)), - Some(ValueType::Long) => Place(EvolvedValuePlace::Value(TypedValue::Long(e))), - Some(ValueType::Double) => Place(EvolvedValuePlace::Value((e as f64).into())), - Some(t) => Empty(EmptyBecause::ValueTypeMismatch(t, TypedValue::Long(e))), - None => Place(EvolvedValuePlace::EntidOrInteger(e)), - } + PatternValuePlace::EntidOrInteger(e) => match value_type { + Some(ValueType::Ref) => Place(EvolvedValuePlace::Entid(e)), + Some(ValueType::Long) => Place(EvolvedValuePlace::Value(TypedValue::Long(e))), + Some(ValueType::Double) => Place(EvolvedValuePlace::Value((e as f64).into())), + Some(t) => Empty(EmptyBecause::ValueTypeMismatch(t, TypedValue::Long(e))), + None => Place(EvolvedValuePlace::EntidOrInteger(e)), }, PatternValuePlace::IdentOrKeyword(kw) => { match value_type { @@ -500,18 +530,14 @@ impl ConjoiningClauses { } else { Empty(EmptyBecause::UnresolvedIdent((&*kw).clone())) } - }, + } Some(ValueType::Keyword) => { Place(EvolvedValuePlace::Value(TypedValue::Keyword(kw))) - }, - Some(t) => { - Empty(EmptyBecause::ValueTypeMismatch(t, TypedValue::Keyword(kw))) - }, - None => { - Place(EvolvedValuePlace::IdentOrKeyword(kw)) - }, + } + Some(t) => Empty(EmptyBecause::ValueTypeMismatch(t, TypedValue::Keyword(kw))), + None => Place(EvolvedValuePlace::IdentOrKeyword(kw)), } - }, + } PatternValuePlace::Variable(var) => { // See if we have it! match self.bound_value(&var) { @@ -522,57 +548,64 @@ impl ConjoiningClauses { } else { Place(EvolvedValuePlace::Entid(entid)) } - }, + } Some(val) => { - if let Some(empty) = self.can_constrain_var_to_type(&var, val.value_type()) { + if let Some(empty) = self.can_constrain_var_to_type(&var, val.value_type()) + { Empty(empty) } else { Place(EvolvedValuePlace::Value(val)) } - }, + } } - }, + } PatternValuePlace::Constant(nic) => { Place(EvolvedValuePlace::Value(into_typed_value(nic))) - }, + } } } - pub(crate) fn make_evolved_pattern(&self, known: Known, pattern: Pattern) -> PlaceOrEmpty { - let (e, a, v, tx, source) = (pattern.entity, pattern.attribute, pattern.value, pattern.tx, pattern.source); + pub(crate) fn make_evolved_pattern( + &self, + known: Known, + pattern: Pattern, + ) -> PlaceOrEmpty { + let (e, a, v, tx, source) = ( + pattern.entity, + pattern.attribute, + pattern.value, + pattern.tx, + pattern.source, + ); use self::PlaceOrEmpty::*; match self.make_evolved_entity(&known, e) { Empty(because) => Empty(because), - Place(e) => { - match self.make_evolved_attribute(&known, a) { + Place(e) => match self.make_evolved_attribute(&known, a) { + Empty(because) => Empty(because), + Place((a, value_type)) => match self.make_evolved_value(&known, value_type, v) { Empty(because) => Empty(because), - Place((a, value_type)) => { - match self.make_evolved_value(&known, value_type, v) { - Empty(because) => Empty(because), - Place(v) => { - match self.make_evolved_tx(&known, tx) { - Empty(because) => Empty(because), - Place(tx) => { - PlaceOrEmpty::Place(EvolvedPattern { - source: source.unwrap_or(SrcVar::DefaultSrc), - entity: e, - attribute: a, - value: v, - tx: tx, - }) - }, - } - }, - } + Place(v) => match self.make_evolved_tx(&known, tx) { + Empty(because) => Empty(because), + Place(tx) => PlaceOrEmpty::Place(EvolvedPattern { + source: source.unwrap_or(SrcVar::DefaultSrc), + entity: e, + attribute: a, + value: v, + tx: tx, + }), }, - } + }, }, } } /// Re-examine the pattern to see if it can be specialized or is now known to fail. #[allow(unused_variables)] - pub(crate) fn evolve_pattern(&mut self, known: Known, mut pattern: EvolvedPattern) -> PlaceOrEmpty { + pub(crate) fn evolve_pattern( + &mut self, + known: Known, + mut pattern: EvolvedPattern, + ) -> PlaceOrEmpty { use self::PlaceOrEmpty::*; let mut new_entity: Option = None; @@ -585,16 +618,16 @@ impl ConjoiningClauses { None => (), Some(TypedValue::Ref(entid)) => { new_entity = Some(EvolvedNonValuePlace::Entid(entid)); - }, + } Some(v) => { return Empty(EmptyBecause::TypeMismatch { var: var.clone(), existing: self.known_type_set(&var), desired: ValueTypeSet::of_one(ValueType::Ref), }); - }, + } }; - }, + } _ => (), } match &pattern.value { @@ -604,13 +637,12 @@ impl ConjoiningClauses { None => (), Some(tv) => { new_value = Some(EvolvedValuePlace::Value(tv.clone())); - }, + } }; - }, + } _ => (), } - if let Some(e) = new_entity { pattern.entity = e; } @@ -659,42 +691,17 @@ mod testing { use std::collections::BTreeMap; use std::collections::BTreeSet; - use core_traits::attribute::{ - Unique, - }; - use core_traits::{ - Attribute, - ValueTypeSet, - }; - use mentat_core::{ - Schema, - }; + use core_traits::attribute::Unique; + use core_traits::{Attribute, ValueTypeSet}; + use mentat_core::Schema; - use edn::query::{ - Keyword, - Variable, - }; + use edn::query::{Keyword, Variable}; - use clauses::{ - QueryInputs, - add_attribute, - associate_ident, - ident, - }; + use clauses::{add_attribute, associate_ident, ident, QueryInputs}; - use types::{ - Column, - ColumnConstraint, - DatomsTable, - QualifiedAlias, - QueryValue, - SourceAlias, - }; + use types::{Column, ColumnConstraint, DatomsTable, QualifiedAlias, QueryValue, SourceAlias}; - use { - algebrize, - parse_find_string, - }; + use {algebrize, parse_find_string}; fn alg(schema: &Schema, input: &str) -> ConjoiningClauses { let parsed = parse_find_string(input).expect("parse failed"); @@ -708,13 +715,16 @@ mod testing { let schema = Schema::default(); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), + tx: PatternNonValuePlace::Placeholder, + }, + ); assert!(cc.is_known_empty()); } @@ -727,13 +737,16 @@ mod testing { associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?x")), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), + tx: PatternNonValuePlace::Placeholder, + }, + ); assert!(cc.is_known_empty()); } @@ -744,20 +757,27 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), + tx: PatternNonValuePlace::Placeholder, + }, + ); // println!("{:#?}", cc); @@ -767,7 +787,10 @@ mod testing { // After this, we know a lot of things: assert!(!cc.is_known_empty()); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]); + assert_eq!( + cc.from, + vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())] + ); // ?x must be a ref. assert_eq!(cc.known_type(&x).unwrap(), ValueType::Ref); @@ -779,10 +802,14 @@ mod testing { // - datoms0.a = 99 // - datoms0.v = true // No need for a type tag constraint, because the attribute is known. - assert_eq!(cc.wheres, vec![ - ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)), - ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))), - ].into()); + assert_eq!( + cc.wheres, + vec![ + ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)), + ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))), + ] + .into() + ); } #[test] @@ -792,13 +819,16 @@ mod testing { let x = Variable::from_valid_name("?x"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), + tx: PatternNonValuePlace::Placeholder, + }, + ); // println!("{:#?}", cc); @@ -806,7 +836,10 @@ mod testing { let d0_v = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Value); assert!(!cc.is_known_empty()); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]); + assert_eq!( + cc.from, + vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())] + ); // ?x must be a ref. assert_eq!(cc.known_type(&x).unwrap(), ValueType::Ref); @@ -818,10 +851,14 @@ mod testing { // - datoms0.v = true // - datoms0.value_type_tag = boolean // TODO: implement expand_type_tags. - assert_eq!(cc.wheres, vec![ - ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))), - ColumnConstraint::has_unit_type("datoms00".to_string(), ValueType::Boolean), - ].into()); + assert_eq!( + cc.wheres, + vec![ + ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))), + ColumnConstraint::has_unit_type("datoms00".to_string(), ValueType::Boolean), + ] + .into() + ); } /// This test ensures that we do less work if we know the attribute thanks to a var lookup. @@ -830,25 +867,33 @@ mod testing { let mut cc = ConjoiningClauses::default(); let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let a = Variable::from_valid_name("?a"); let v = Variable::from_valid_name("?v"); cc.input_variables.insert(a.clone()); - cc.value_bindings.insert(a.clone(), TypedValue::typed_ns_keyword("foo", "bar")); + cc.value_bindings + .insert(a.clone(), TypedValue::typed_ns_keyword("foo", "bar")); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Variable(a.clone()), - value: PatternValuePlace::Variable(v.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Variable(a.clone()), + value: PatternValuePlace::Variable(v.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // println!("{:#?}", cc); @@ -856,7 +901,10 @@ mod testing { let d0_a = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Attribute); assert!(!cc.is_known_empty()); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())]); + assert_eq!( + cc.from, + vec![SourceAlias(DatomsTable::Datoms, "datoms00".to_string())] + ); // ?x must be a ref, and ?v a boolean. assert_eq!(cc.known_type(&x), Some(ValueType::Ref)); @@ -867,9 +915,10 @@ mod testing { // ?x is bound to datoms0.e. assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]); - assert_eq!(cc.wheres, vec![ - ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)), - ].into()); + assert_eq!( + cc.wheres, + vec![ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)),].into() + ); } /// Queries that bind non-entity values to entity places can't return results. @@ -886,19 +935,24 @@ mod testing { cc.input_variables.insert(a.clone()); cc.value_bindings.insert(a.clone(), hello.clone()); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Variable(a.clone()), - value: PatternValuePlace::Variable(v.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Variable(a.clone()), + value: PatternValuePlace::Variable(v.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); assert!(cc.is_known_empty()); - assert_eq!(cc.empty_because.unwrap(), EmptyBecause::InvalidBinding(Column::Fixed(DatomsColumn::Attribute), hello)); + assert_eq!( + cc.empty_because.unwrap(), + EmptyBecause::InvalidBinding(Column::Fixed(DatomsColumn::Attribute), hello) + ); } - /// This test ensures that we query all_datoms if we're possibly retrieving a string. #[test] fn test_apply_unattributed_pattern_with_returned() { @@ -909,20 +963,29 @@ mod testing { let a = Variable::from_valid_name("?a"); let v = Variable::from_valid_name("?v"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Variable(a.clone()), - value: PatternValuePlace::Variable(v.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Variable(a.clone()), + value: PatternValuePlace::Variable(v.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // println!("{:#?}", cc); let d0_e = QualifiedAlias::new("all_datoms00".to_string(), DatomsColumn::Entity); assert!(!cc.is_known_empty()); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::AllDatoms, "all_datoms00".to_string())]); + assert_eq!( + cc.from, + vec![SourceAlias( + DatomsTable::AllDatoms, + "all_datoms00".to_string() + )] + ); // ?x must be a ref. assert_eq!(cc.known_type(&x).unwrap(), ValueType::Ref); @@ -940,13 +1003,16 @@ mod testing { let x = Variable::from_valid_name("?x"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::Constant("hello".into()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::Constant("hello".into()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // println!("{:#?}", cc); @@ -954,7 +1020,13 @@ mod testing { let d0_v = QualifiedAlias::new("all_datoms00".to_string(), DatomsColumn::Value); assert!(!cc.is_known_empty()); - assert_eq!(cc.from, vec![SourceAlias(DatomsTable::AllDatoms, "all_datoms00".to_string())]); + assert_eq!( + cc.from, + vec![SourceAlias( + DatomsTable::AllDatoms, + "all_datoms00".to_string() + )] + ); // ?x must be a ref. assert_eq!(cc.known_type(&x).unwrap(), ValueType::Ref); @@ -966,10 +1038,17 @@ mod testing { // - datoms0.v = 'hello' // - datoms0.value_type_tag = string // TODO: implement expand_type_tags. - assert_eq!(cc.wheres, vec![ - ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::typed_string("hello"))), - ColumnConstraint::has_unit_type("all_datoms00".to_string(), ValueType::String), - ].into()); + assert_eq!( + cc.wheres, + vec![ + ColumnConstraint::Equals( + d0_v, + QueryValue::TypedValue(TypedValue::typed_string("hello")) + ), + ColumnConstraint::has_unit_type("all_datoms00".to_string(), ValueType::String), + ] + .into() + ); } #[test] @@ -979,32 +1058,46 @@ mod testing { associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); - add_attribute(&mut schema, 98, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 98, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "roz"), - value: PatternValuePlace::Constant("idgoeshere".into()), - tx: PatternNonValuePlace::Placeholder, - }); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "roz"), + value: PatternValuePlace::Constant("idgoeshere".into()), + tx: PatternNonValuePlace::Placeholder, + }, + ); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // Finally, expand column bindings to get the overlaps for ?x. cc.expand_column_bindings(); @@ -1018,32 +1111,41 @@ mod testing { let d1_a = QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Attribute); assert!(!cc.is_known_empty()); - assert_eq!(cc.from, vec![ - SourceAlias(DatomsTable::Datoms, "datoms00".to_string()), - SourceAlias(DatomsTable::Datoms, "datoms01".to_string()), - ]); + assert_eq!( + cc.from, + vec![ + SourceAlias(DatomsTable::Datoms, "datoms00".to_string()), + SourceAlias(DatomsTable::Datoms, "datoms01".to_string()), + ] + ); // ?x must be a ref. assert_eq!(cc.known_type(&x).unwrap(), ValueType::Ref); // ?x is bound to datoms0.e and datoms1.e. - assert_eq!(cc.column_bindings.get(&x).unwrap(), - &vec![ - d0_e.clone(), - d1_e.clone(), - ]); + assert_eq!( + cc.column_bindings.get(&x).unwrap(), + &vec![d0_e.clone(), d1_e.clone(),] + ); // Our 'where' clauses are four: // - datoms0.a = 98 (:foo/roz) // - datoms0.v = "idgoeshere" // - datoms1.a = 99 (:foo/bar) // - datoms1.e = datoms0.e - assert_eq!(cc.wheres, vec![ - ColumnConstraint::Equals(d0_a, QueryValue::Entid(98)), - ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::typed_string("idgoeshere"))), - ColumnConstraint::Equals(d1_a, QueryValue::Entid(99)), - ColumnConstraint::Equals(d0_e, QueryValue::Column(d1_e)), - ].into()); + assert_eq!( + cc.wheres, + vec![ + ColumnConstraint::Equals(d0_a, QueryValue::Entid(98)), + ColumnConstraint::Equals( + d0_v, + QueryValue::TypedValue(TypedValue::typed_string("idgoeshere")) + ), + ColumnConstraint::Equals(d1_a, QueryValue::Entid(99)), + ColumnConstraint::Equals(d0_e, QueryValue::Column(d1_e)), + ] + .into() + ); } #[test] @@ -1051,45 +1153,57 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - let b: BTreeMap = - vec![(y.clone(), TypedValue::Boolean(true))].into_iter().collect(); + let b: BTreeMap = vec![(y.clone(), TypedValue::Boolean(true))] + .into_iter() + .collect(); let inputs = QueryInputs::with_values(b); - let variables: BTreeSet = vec![Variable::from_valid_name("?y")].into_iter().collect(); + let variables: BTreeSet = + vec![Variable::from_valid_name("?y")].into_iter().collect(); let mut cc = ConjoiningClauses::with_inputs(variables, inputs); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); let d0_e = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Entity); let d0_a = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Attribute); let d0_v = QualifiedAlias::new("datoms00".to_string(), DatomsColumn::Value); // ?y has been expanded into `true`. - assert_eq!(cc.wheres, vec![ - ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)), - ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))), - ].into()); + assert_eq!( + cc.wheres, + vec![ + ColumnConstraint::Equals(d0_a, QueryValue::Entid(99)), + ColumnConstraint::Equals(d0_v, QueryValue::TypedValue(TypedValue::Boolean(true))), + ] + .into() + ); // There is no binding for ?y. assert!(!cc.column_bindings.contains_key(&y)); // ?x is bound to the entity. - assert_eq!(cc.column_bindings.get(&x).unwrap(), - &vec![d0_e.clone()]); + assert_eq!(cc.column_bindings.get(&x).unwrap(), &vec![d0_e.clone()]); } #[test] @@ -1099,28 +1213,37 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - let b: BTreeMap = - vec![(y.clone(), TypedValue::Long(42))].into_iter().collect(); + let b: BTreeMap = vec![(y.clone(), TypedValue::Long(42))] + .into_iter() + .collect(); let inputs = QueryInputs::with_values(b); - let variables: BTreeSet = vec![Variable::from_valid_name("?y")].into_iter().collect(); + let variables: BTreeSet = + vec![Variable::from_valid_name("?y")].into_iter().collect(); let mut cc = ConjoiningClauses::with_inputs(variables, inputs); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // The type of the provided binding doesn't match the type of the attribute. assert!(cc.is_known_empty()); @@ -1133,30 +1256,39 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::String, - index: true, - fulltext: true, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::String, + index: true, + fulltext: true, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); - let b: BTreeMap = - vec![(y.clone(), TypedValue::Long(42))].into_iter().collect(); + let b: BTreeMap = vec![(y.clone(), TypedValue::Long(42))] + .into_iter() + .collect(); let inputs = QueryInputs::with_values(b); - let variables: BTreeSet = vec![Variable::from_valid_name("?y")].into_iter().collect(); + let variables: BTreeSet = + vec![Variable::from_valid_name("?y")].into_iter().collect(); let mut cc = ConjoiningClauses::with_inputs(variables, inputs); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // The type of the provided binding doesn't match the type of the attribute. assert!(cc.is_known_empty()); @@ -1172,44 +1304,60 @@ mod testing { associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); - add_attribute(&mut schema, 98, Attribute { - value_type: ValueType::String, - unique: Some(Unique::Identity), - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..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 y = Variable::from_valid_name("?y"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "roz"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "bar"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "roz"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "bar"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // Finally, expand column bindings to get the overlaps for ?x. cc.expand_column_bindings(); assert!(cc.is_known_empty()); - assert_eq!(cc.empty_because.unwrap(), - EmptyBecause::TypeMismatch { - var: y.clone(), - existing: ValueTypeSet::of_one(ValueType::String), - desired: ValueTypeSet::of_one(ValueType::Boolean), - }); + assert_eq!( + cc.empty_because.unwrap(), + EmptyBecause::TypeMismatch { + var: y.clone(), + existing: ValueTypeSet::of_one(ValueType::String), + desired: ValueTypeSet::of_one(ValueType::Boolean), + } + ); } #[test] @@ -1228,31 +1376,39 @@ mod testing { let y = Variable::from_valid_name("?y"); let z = Variable::from_valid_name("?z"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Variable(y.clone()), - value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), - tx: PatternNonValuePlace::Placeholder, - }); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(z.clone()), - attribute: PatternNonValuePlace::Variable(y.clone()), - value: PatternValuePlace::Variable(x.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Variable(y.clone()), + value: PatternValuePlace::Constant(NonIntegerConstant::Boolean(true)), + tx: PatternNonValuePlace::Placeholder, + }, + ); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(z.clone()), + attribute: PatternNonValuePlace::Variable(y.clone()), + value: PatternValuePlace::Variable(x.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // Finally, expand column bindings to get the overlaps for ?x. cc.expand_column_bindings(); assert!(cc.is_known_empty()); - assert_eq!(cc.empty_because.unwrap(), - EmptyBecause::TypeMismatch { - var: x.clone(), - existing: ValueTypeSet::of_one(ValueType::Ref), - desired: ValueTypeSet::of_one(ValueType::Boolean), - }); + assert_eq!( + cc.empty_because.unwrap(), + EmptyBecause::TypeMismatch { + var: x.clone(), + existing: ValueTypeSet::of_one(ValueType::Ref), + desired: ValueTypeSet::of_one(ValueType::Boolean), + } + ); } #[test] @@ -1260,15 +1416,25 @@ mod testing { let query = r#"[:find ?e ?v :where [_ _ ?v] [?e :foo/bar ?v]]"#; let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); let e = Variable::from_valid_name("?e"); let v = Variable::from_valid_name("?v"); let cc = alg(&schema, query); - assert_eq!(cc.known_types.get(&e), Some(&ValueTypeSet::of_one(ValueType::Ref))); - assert_eq!(cc.known_types.get(&v), Some(&ValueTypeSet::of_one(ValueType::Boolean))); + assert_eq!( + cc.known_types.get(&e), + Some(&ValueTypeSet::of_one(ValueType::Ref)) + ); + assert_eq!( + cc.known_types.get(&v), + Some(&ValueTypeSet::of_one(ValueType::Boolean)) + ); assert!(!cc.extracted_types.contains_key(&e)); assert!(!cc.extracted_types.contains_key(&v)); } diff --git a/query-algebrizer/src/clauses/predicate.rs b/query-algebrizer/src/clauses/predicate.rs index 76d668ab..7a780dde 100644 --- a/query-algebrizer/src/clauses/predicate.rs +++ b/query-algebrizer/src/clauses/predicate.rs @@ -8,37 +8,19 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, - ValueTypeSet, -}; +use core_traits::{ValueType, ValueTypeSet}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::query::{ - FnArg, - PlainSymbol, - Predicate, - TypeAnnotation, -}; +use edn::query::{FnArg, PlainSymbol, Predicate, TypeAnnotation}; use clauses::ConjoiningClauses; use clauses::convert::ValueTypes; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; -use types::{ - ColumnConstraint, - EmptyBecause, - Inequality, - QueryValue, -}; +use types::{ColumnConstraint, EmptyBecause, Inequality, QueryValue}; use Known; @@ -71,8 +53,14 @@ impl ConjoiningClauses { /// to be a specific ValueType. pub(crate) fn apply_type_anno(&mut self, anno: &TypeAnnotation) -> Result<()> { match ValueType::from_keyword(&anno.value_type) { - Some(value_type) => self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type)), - None => bail!(AlgebrizerError::InvalidArgumentType(PlainSymbol::plain("type"), ValueTypeSet::any(), 2)), + Some(value_type) => { + self.add_type_requirement(anno.variable.clone(), ValueTypeSet::of_one(value_type)) + } + None => bail!(AlgebrizerError::InvalidArgumentType( + PlainSymbol::plain("type"), + ValueTypeSet::any(), + 2 + )), } Ok(()) } @@ -81,9 +69,18 @@ impl ConjoiningClauses { /// - Resolves variables and converts types to those more amenable to SQL. /// - Ensures that the predicate functions name a known operator. /// - 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 { - 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. @@ -93,20 +90,29 @@ impl ConjoiningClauses { let left = 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 known types of any variables, and the types supported by our inequality operators. let supported_types = comparison.supported_types(); - let mut left_types = self.potential_types(known.schema, &left)? - .intersection(&supported_types); + let mut left_types = self + .potential_types(known.schema, &left)? + .intersection(&supported_types); 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)? - .intersection(&supported_types); + let mut right_types = self + .potential_types(known.schema, &right)? + .intersection(&supported_types); 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. @@ -135,7 +141,8 @@ impl ConjoiningClauses { left: left_types, right: right_types, } - }); + }, + ); return Ok(()); } @@ -153,7 +160,11 @@ impl ConjoiningClauses { left_v = self.resolve_ref_argument(known.schema, &predicate.operator, 0, left)?; right_v = self.resolve_ref_argument(known.schema, &predicate.operator, 1, right)?; } 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. @@ -167,15 +178,13 @@ impl ConjoiningClauses { impl Inequality { fn to_constraint(&self, left: QueryValue, right: QueryValue) -> ColumnConstraint { match *self { - Inequality::TxAfter | - Inequality::TxBefore => { + Inequality::TxAfter | Inequality::TxBefore => { // 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 // not passed to this function -- then we can generate two constraints, // or clamp a fixed value. - }, - _ => { - }, + } + _ => {} } ColumnConstraint::Inequality { @@ -190,36 +199,16 @@ impl Inequality { mod testing { use super::*; - use core_traits::attribute::{ - Unique, - }; - use core_traits::{ - Attribute, - TypedValue, - ValueType, - }; + use core_traits::attribute::Unique; + use core_traits::{Attribute, TypedValue, ValueType}; use edn::query::{ - FnArg, - Keyword, - Pattern, - PatternNonValuePlace, - PatternValuePlace, - PlainSymbol, - Variable, + FnArg, Keyword, Pattern, PatternNonValuePlace, PatternValuePlace, PlainSymbol, Variable, }; - use clauses::{ - add_attribute, - associate_ident, - ident, - }; + use clauses::{add_attribute, associate_ident, ident}; - use types::{ - ColumnConstraint, - EmptyBecause, - QueryValue, - }; + use types::{ColumnConstraint, EmptyBecause, QueryValue}; #[test] /// Apply two patterns: a pattern and a numeric predicate. @@ -230,30 +219,45 @@ mod testing { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Long, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Long, + ..Default::default() + }, + ); let x = Variable::from_valid_name("?x"); let y = Variable::from_valid_name("?y"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); assert!(!cc.is_known_empty()); let op = PlainSymbol::plain("<"); let comp = Inequality::from_datalog_operator(op.name()).unwrap(); - assert!(cc.apply_inequality(known, comp, Predicate { - operator: op, - args: vec![ - FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10), - ]}).is_ok()); + assert!(cc + .apply_inequality( + known, + comp, + Predicate { + operator: op, + args: vec![ + FnArg::Variable(Variable::from_valid_name("?y")), + FnArg::EntidOrInteger(10), + ] + } + ) + .is_ok()); 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 // 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(); assert_eq!(Some(&expected), cc.known_types.get(&y)); let clauses = cc.wheres; assert_eq!(clauses.len(), 1); - assert_eq!(clauses.0[0], ColumnConstraint::Inequality { - operator: Inequality::LessThan, - left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()), - right: QueryValue::TypedValue(TypedValue::Long(10)), - }.into()); + assert_eq!( + clauses.0[0], + ColumnConstraint::Inequality { + operator: Inequality::LessThan, + left: QueryValue::Column(cc.column_bindings.get(&y).unwrap()[0].clone()), + right: QueryValue::TypedValue(TypedValue::Long(10)), + } + .into() + ); } #[test] @@ -286,54 +294,78 @@ mod testing { associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); associate_ident(&mut schema, Keyword::namespaced("foo", "roz"), 98); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Long, - ..Default::default() - }); - add_attribute(&mut schema, 98, Attribute { - value_type: ValueType::String, - unique: Some(Unique::Identity), - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Long, + ..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 y = Variable::from_valid_name("?y"); let known = Known::for_schema(&schema); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: PatternNonValuePlace::Placeholder, - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: PatternNonValuePlace::Placeholder, + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); assert!(!cc.is_known_empty()); let op = PlainSymbol::plain(">="); let comp = Inequality::from_datalog_operator(op.name()).unwrap(); - assert!(cc.apply_inequality(known, comp, Predicate { - operator: op, - args: vec![ - FnArg::Variable(Variable::from_valid_name("?y")), FnArg::EntidOrInteger(10), - ]}).is_ok()); + assert!(cc + .apply_inequality( + known, + comp, + Predicate { + operator: op, + args: vec![ + FnArg::Variable(Variable::from_valid_name("?y")), + FnArg::EntidOrInteger(10), + ] + } + ) + .is_ok()); assert!(!cc.is_known_empty()); - cc.apply_parsed_pattern(known, Pattern { - source: None, - entity: PatternNonValuePlace::Variable(x.clone()), - attribute: ident("foo", "roz"), - value: PatternValuePlace::Variable(y.clone()), - tx: PatternNonValuePlace::Placeholder, - }); + cc.apply_parsed_pattern( + known, + Pattern { + source: None, + entity: PatternNonValuePlace::Variable(x.clone()), + attribute: ident("foo", "roz"), + value: PatternValuePlace::Variable(y.clone()), + tx: PatternNonValuePlace::Placeholder, + }, + ); // Finally, expand column bindings to get the overlaps for ?x. cc.expand_column_bindings(); assert!(cc.is_known_empty()); - assert_eq!(cc.empty_because.unwrap(), - EmptyBecause::TypeMismatch { - var: y.clone(), - existing: ValueTypeSet::of_numeric_types(), - desired: ValueTypeSet::of_one(ValueType::String), - }); + assert_eq!( + cc.empty_because.unwrap(), + EmptyBecause::TypeMismatch { + var: y.clone(), + existing: ValueTypeSet::of_numeric_types(), + desired: ValueTypeSet::of_one(ValueType::String), + } + ); } } diff --git a/query-algebrizer/src/clauses/resolve.rs b/query-algebrizer/src/clauses/resolve.rs index ee96d5d5..aa86ae8f 100644 --- a/query-algebrizer/src/clauses/resolve.rs +++ b/query-algebrizer/src/clauses/resolve.rs @@ -8,33 +8,17 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, - TypedValue, -}; +use core_traits::{TypedValue, ValueType}; -use mentat_core::{ - HasSchema, - Schema, -}; +use mentat_core::{HasSchema, Schema}; -use edn::query::{ - FnArg, - NonIntegerConstant, - PlainSymbol, -}; +use edn::query::{FnArg, NonIntegerConstant, PlainSymbol}; use clauses::ConjoiningClauses; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; -use types::{ - EmptyBecause, - QueryValue, -}; +use types::{EmptyBecause, QueryValue}; /// Argument resolution. impl ConjoiningClauses { @@ -43,7 +27,12 @@ impl ConjoiningClauses { /// Additionally, do two things: /// - Mark the pattern as known-empty if any argument is known non-numeric. /// - Mark any variables encountered as numeric. - pub(crate) fn resolve_numeric_argument(&mut self, function: &PlainSymbol, position: usize, arg: FnArg) -> Result { + pub(crate) fn resolve_numeric_argument( + &mut self, + function: &PlainSymbol, + position: usize, + arg: FnArg, + ) -> Result { use self::FnArg::*; match arg { FnArg::Variable(var) => { @@ -80,45 +69,62 @@ impl ConjoiningClauses { } /// Just like `resolve_numeric_argument`, but for `ValueType::Instant`. - pub(crate) fn resolve_instant_argument(&mut self, function: &PlainSymbol, position: usize, arg: FnArg) -> Result { + pub(crate) fn resolve_instant_argument( + &mut self, + function: &PlainSymbol, + position: usize, + arg: FnArg, + ) -> Result { use self::FnArg::*; match arg { - FnArg::Variable(var) => { - match self.bound_value(&var) { - Some(TypedValue::Instant(v)) => Ok(QueryValue::TypedValue(TypedValue::Instant(v))), - Some(v) => bail!(AlgebrizerError::InputTypeDisagreement(var.name().clone(), ValueType::Instant, v.value_type())), - None => { - self.constrain_var_to_type(var.clone(), ValueType::Instant); - self.column_bindings - .get(&var) - .and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) - .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()) - }, + FnArg::Variable(var) => match self.bound_value(&var) { + Some(TypedValue::Instant(v)) => Ok(QueryValue::TypedValue(TypedValue::Instant(v))), + Some(v) => bail!(AlgebrizerError::InputTypeDisagreement( + var.name().clone(), + ValueType::Instant, + v.value_type() + )), + None => { + self.constrain_var_to_type(var.clone(), ValueType::Instant); + 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)) => { Ok(QueryValue::TypedValue(TypedValue::Instant(v))) - }, + } // TODO: should we allow integers if they seem to be timestamps? It's ambiguous… - EntidOrInteger(_) | - IdentOrKeyword(_) | - SrcVar(_) | - Constant(NonIntegerConstant::Boolean(_)) | - Constant(NonIntegerConstant::Float(_)) | - Constant(NonIntegerConstant::Text(_)) | - Constant(NonIntegerConstant::Uuid(_)) | - Constant(NonIntegerConstant::BigInteger(_)) | - Vector(_) => { + EntidOrInteger(_) + | IdentOrKeyword(_) + | SrcVar(_) + | Constant(NonIntegerConstant::Boolean(_)) + | Constant(NonIntegerConstant::Float(_)) + | Constant(NonIntegerConstant::Text(_)) + | Constant(NonIntegerConstant::Uuid(_)) + | Constant(NonIntegerConstant::BigInteger(_)) + | Vector(_) => { 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 /// constraint. - pub(crate) fn resolve_ref_argument(&mut self, schema: &Schema, function: &PlainSymbol, position: usize, arg: FnArg) -> Result { + pub(crate) fn resolve_ref_argument( + &mut self, + schema: &Schema, + function: &PlainSymbol, + position: usize, + arg: FnArg, + ) -> Result { use self::FnArg::*; match arg { FnArg::Variable(var) => { @@ -132,31 +138,39 @@ impl ConjoiningClauses { .and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()) } - }, + } EntidOrInteger(i) => Ok(QueryValue::TypedValue(TypedValue::Ref(i))), - IdentOrKeyword(i) => { - schema.get_entid(&i) - .map(|known_entid| QueryValue::Entid(known_entid.into())) - .ok_or_else(|| AlgebrizerError::UnrecognizedIdent(i.to_string()).into()) - }, - Constant(NonIntegerConstant::Boolean(_)) | - Constant(NonIntegerConstant::Float(_)) | - Constant(NonIntegerConstant::Text(_)) | - Constant(NonIntegerConstant::Uuid(_)) | - Constant(NonIntegerConstant::Instant(_)) | - Constant(NonIntegerConstant::BigInteger(_)) | - SrcVar(_) | - Vector(_) => { + IdentOrKeyword(i) => schema + .get_entid(&i) + .map(|known_entid| QueryValue::Entid(known_entid.into())) + .ok_or_else(|| AlgebrizerError::UnrecognizedIdent(i.to_string()).into()), + Constant(NonIntegerConstant::Boolean(_)) + | Constant(NonIntegerConstant::Float(_)) + | Constant(NonIntegerConstant::Text(_)) + | Constant(NonIntegerConstant::Uuid(_)) + | Constant(NonIntegerConstant::Instant(_)) + | Constant(NonIntegerConstant::BigInteger(_)) + | SrcVar(_) + | Vector(_) => { 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 /// a concrete constraint. - pub(crate) fn resolve_tx_argument(&mut self, schema: &Schema, function: &PlainSymbol, position: usize, arg: FnArg) -> Result { + pub(crate) fn resolve_tx_argument( + &mut self, + schema: &Schema, + function: &PlainSymbol, + position: usize, + arg: FnArg, + ) -> Result { // Under the hood there's nothing special about a transaction ID -- it's just another ref. // In the future, we might handle instants specially. self.resolve_ref_argument(schema, function, position, arg) @@ -168,27 +182,34 @@ impl ConjoiningClauses { fn resolve_argument(&self, arg: FnArg) -> Result { use self::FnArg::*; match arg { - FnArg::Variable(var) => { - match self.bound_value(&var) { - Some(v) => Ok(QueryValue::TypedValue(v)), - None => { - self.column_bindings - .get(&var) - .and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) - .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()) - }, - } + FnArg::Variable(var) => match self.bound_value(&var) { + Some(v) => Ok(QueryValue::TypedValue(v)), + None => self + .column_bindings + .get(&var) + .and_then(|cols| cols.first().map(|col| QueryValue::Column(col.clone()))) + .ok_or_else(|| AlgebrizerError::UnboundVariable(var.name()).into()), }, EntidOrInteger(i) => Ok(QueryValue::PrimitiveLong(i)), - IdentOrKeyword(_) => unimplemented!(), // TODO - Constant(NonIntegerConstant::Boolean(val)) => Ok(QueryValue::TypedValue(TypedValue::Boolean(val))), - Constant(NonIntegerConstant::Float(f)) => 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))), + IdentOrKeyword(_) => unimplemented!(), // TODO + Constant(NonIntegerConstant::Boolean(val)) => { + Ok(QueryValue::TypedValue(TypedValue::Boolean(val))) + } + Constant(NonIntegerConstant::Float(f)) => { + 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!(), SrcVar(_) => unimplemented!(), - Vector(_) => unimplemented!(), // TODO + Vector(_) => unimplemented!(), // TODO } } } diff --git a/query-algebrizer/src/clauses/tx_log_api.rs b/query-algebrizer/src/clauses/tx_log_api.rs index e5015a39..f91f3bac 100644 --- a/query-algebrizer/src/clauses/tx_log_api.rs +++ b/query-algebrizer/src/clauses/tx_log_api.rs @@ -8,36 +8,16 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, -}; +use core_traits::ValueType; -use edn::query::{ - Binding, - FnArg, - SrcVar, - VariableOrPlaceholder, - WhereFn, -}; +use edn::query::{Binding, FnArg, SrcVar, VariableOrPlaceholder, WhereFn}; -use clauses::{ - ConjoiningClauses, -}; +use clauses::ConjoiningClauses; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - BindingError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, BindingError, Result}; use types::{ - Column, - ColumnConstraint, - DatomsTable, - Inequality, - QualifiedAlias, - QueryValue, - SourceAlias, + Column, ColumnConstraint, DatomsTable, Inequality, QualifiedAlias, QueryValue, SourceAlias, TransactionsColumn, }; @@ -60,17 +40,27 @@ impl ConjoiningClauses { // transactions that impact one of the given attributes. pub(crate) fn apply_tx_ids(&mut self, known: Known, where_fn: WhereFn) -> Result<()> { 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() { // 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() { // 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. @@ -78,38 +68,49 @@ impl ConjoiningClauses { Binding::BindRel(bindings) => { let bindings_count = bindings.len(); if bindings_count != 1 { - bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), - BindingError::InvalidNumberOfBindings { - number: bindings_count, - expected: 1, - })); + bail!(AlgebrizerError::InvalidBinding( + where_fn.operator.clone(), + BindingError::InvalidNumberOfBindings { + number: bindings_count, + expected: 1, + } + )); } match bindings.into_iter().next().unwrap() { VariableOrPlaceholder::Placeholder => unreachable!("binding.is_empty()!"), VariableOrPlaceholder::Variable(v) => v, } - }, + } Binding::BindColl(v) => v, - Binding::BindScalar(_) | - Binding::BindTuple(_) => { - bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRelOrBindColl)) - }, + Binding::BindScalar(_) | Binding::BindTuple(_) => { + bail!(AlgebrizerError::InvalidBinding( + where_fn.operator.clone(), + BindingError::ExpectedBindRelOrBindColl + )) + } }; let mut args = where_fn.args.into_iter(); // TODO: process source variables. match args.next().unwrap() { - FnArg::SrcVar(SrcVar::DefaultSrc) => {}, - _ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)), + FnArg::SrcVar(SrcVar::DefaultSrc) => {} + _ => 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 tx2 = self.resolve_tx_argument(&known.schema, &where_fn.operator, 2, args.next().unwrap())?; + let tx1 = + 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); - self.from.push(SourceAlias(DatomsTable::Transactions, transactions.clone())); + self.from + .push(SourceAlias(DatomsTable::Transactions, transactions.clone())); // Bound variable must be a ref. self.constrain_var_to_type(tx_var.clone(), ValueType::Ref); @@ -117,18 +118,29 @@ impl ConjoiningClauses { 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 { operator: Inequality::LessThanOrEquals, 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); let before_constraint = ColumnConstraint::Inequality { 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, }; 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<()> { 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() { // 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() { // 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. @@ -156,42 +178,67 @@ impl ConjoiningClauses { Binding::BindRel(bindings) => { let bindings_count = bindings.len(); if bindings_count < 1 || bindings_count > 5 { - bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), - BindingError::InvalidNumberOfBindings { - number: bindings.len(), - expected: 5, - })); + bail!(AlgebrizerError::InvalidBinding( + where_fn.operator.clone(), + BindingError::InvalidNumberOfBindings { + number: bindings.len(), + expected: 5, + } + )); } bindings - }, - Binding::BindScalar(_) | - Binding::BindTuple(_) | - Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)), + } + Binding::BindScalar(_) | Binding::BindTuple(_) | Binding::BindColl(_) => { + bail!(AlgebrizerError::InvalidBinding( + where_fn.operator.clone(), + BindingError::ExpectedBindRel + )) + } }; let mut bindings = bindings.into_iter(); - let b_e = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); - let b_a = bindings.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 b_e = bindings + .next() + .unwrap_or(VariableOrPlaceholder::Placeholder); + let b_a = bindings + .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(); // TODO: process source variables. match args.next().unwrap() { - FnArg::SrcVar(SrcVar::DefaultSrc) => {}, - _ => bail!(AlgebrizerError::InvalidArgument(where_fn.operator.clone(), "source variable", 0)), + FnArg::SrcVar(SrcVar::DefaultSrc) => {} + _ => 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); - self.from.push(SourceAlias(DatomsTable::Transactions, transactions.clone())); + self.from + .push(SourceAlias(DatomsTable::Transactions, transactions.clone())); let tx_constraint = ColumnConstraint::Equals( - QualifiedAlias(transactions.clone(), Column::Transactions(TransactionsColumn::Tx)), - tx); + QualifiedAlias( + transactions.clone(), + Column::Transactions(TransactionsColumn::Tx), + ), + tx, + ); self.wheres.add_intersection(tx_constraint); if let VariableOrPlaceholder::Variable(ref var) = b_e { @@ -201,7 +248,12 @@ impl ConjoiningClauses { 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 { @@ -211,11 +263,21 @@ impl ConjoiningClauses { 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 { - 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 { @@ -227,7 +289,12 @@ impl ConjoiningClauses { // TODO: this might be a programming error if var is our tx argument. Perhaps we can be // 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 { @@ -237,7 +304,12 @@ impl ConjoiningClauses { 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(()) @@ -248,21 +320,11 @@ impl ConjoiningClauses { mod testing { use super::*; - use core_traits::{ - TypedValue, - ValueType, - }; + use core_traits::{TypedValue, ValueType}; - use mentat_core::{ - Schema, - }; + use mentat_core::Schema; - use edn::query::{ - Binding, - FnArg, - PlainSymbol, - Variable, - }; + use edn::query::{Binding, FnArg, PlainSymbol, Variable}; #[test] fn test_apply_tx_ids() { @@ -272,16 +334,21 @@ mod testing { let known = Known::for_schema(&schema); let op = PlainSymbol::plain("tx-ids"); - cc.apply_tx_ids(known, WhereFn { - operator: op, - args: vec![ - FnArg::SrcVar(SrcVar::DefaultSrc), - FnArg::EntidOrInteger(1000), - FnArg::EntidOrInteger(2000), - ], - binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), - ]), - }).expect("to be able to apply_tx_ids"); + cc.apply_tx_ids( + known, + WhereFn { + operator: op, + args: vec![ + FnArg::SrcVar(SrcVar::DefaultSrc), + FnArg::EntidOrInteger(1000), + FnArg::EntidOrInteger(2000), + ], + binding: Binding::BindRel(vec![VariableOrPlaceholder::Variable( + Variable::from_valid_name("?tx"), + )]), + }, + ) + .expect("to be able to apply_tx_ids"); assert!(!cc.is_known_empty()); @@ -292,31 +359,56 @@ mod testing { let clauses = cc.wheres; assert_eq!(clauses.len(), 2); - assert_eq!(clauses.0[0], - ColumnConstraint::Inequality { - operator: Inequality::LessThanOrEquals, - left: QueryValue::TypedValue(TypedValue::Ref(1000)), - right: QueryValue::Column(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))), - }.into()); + assert_eq!( + clauses.0[0], + ColumnConstraint::Inequality { + operator: Inequality::LessThanOrEquals, + left: QueryValue::TypedValue(TypedValue::Ref(1000)), + right: QueryValue::Column(QualifiedAlias( + "transactions00".to_string(), + Column::Transactions(TransactionsColumn::Tx) + )), + } + .into() + ); - assert_eq!(clauses.0[1], - ColumnConstraint::Inequality { - operator: Inequality::LessThan, - left: QueryValue::Column(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))), - right: QueryValue::TypedValue(TypedValue::Ref(2000)), - }.into()); + assert_eq!( + clauses.0[1], + ColumnConstraint::Inequality { + operator: Inequality::LessThan, + left: QueryValue::Column(QualifiedAlias( + "transactions00".to_string(), + Column::Transactions(TransactionsColumn::Tx) + )), + right: QueryValue::TypedValue(TypedValue::Ref(2000)), + } + .into() + ); let bindings = cc.column_bindings; assert_eq!(bindings.len(), 1); - assert_eq!(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("?tx")) + .expect("column binding for ?tx") + .clone(), + vec![QualifiedAlias( + "transactions00".to_string(), + Column::Transactions(TransactionsColumn::Tx) + )] + ); let known_types = cc.known_types; assert_eq!(known_types.len(), 1); - 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("?tx")) + .expect("known types for ?tx") + .clone(), + vec![ValueType::Ref].into_iter().collect() + ); } #[test] @@ -327,20 +419,24 @@ mod testing { let known = Known::for_schema(&schema); let op = PlainSymbol::plain("tx-data"); - cc.apply_tx_data(known, WhereFn { - operator: op, - args: vec![ - FnArg::SrcVar(SrcVar::DefaultSrc), - FnArg::EntidOrInteger(1000), - ], - binding: Binding::BindRel(vec![ - VariableOrPlaceholder::Variable(Variable::from_valid_name("?e")), - VariableOrPlaceholder::Variable(Variable::from_valid_name("?a")), - VariableOrPlaceholder::Variable(Variable::from_valid_name("?v")), - VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), - VariableOrPlaceholder::Variable(Variable::from_valid_name("?added")), - ]), - }).expect("to be able to apply_tx_data"); + cc.apply_tx_data( + known, + WhereFn { + operator: op, + args: vec![ + FnArg::SrcVar(SrcVar::DefaultSrc), + FnArg::EntidOrInteger(1000), + ], + binding: Binding::BindRel(vec![ + VariableOrPlaceholder::Variable(Variable::from_valid_name("?e")), + VariableOrPlaceholder::Variable(Variable::from_valid_name("?a")), + VariableOrPlaceholder::Variable(Variable::from_valid_name("?v")), + VariableOrPlaceholder::Variable(Variable::from_valid_name("?tx")), + VariableOrPlaceholder::Variable(Variable::from_valid_name("?added")), + ]), + }, + ) + .expect("to be able to apply_tx_data"); assert!(!cc.is_known_empty()); @@ -351,47 +447,123 @@ mod testing { let clauses = cc.wheres; assert_eq!(clauses.len(), 1); - assert_eq!(clauses.0[0], - ColumnConstraint::Equals(QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx)), - QueryValue::TypedValue(TypedValue::Ref(1000))).into()); + assert_eq!( + clauses.0[0], + ColumnConstraint::Equals( + QualifiedAlias( + "transactions00".to_string(), + Column::Transactions(TransactionsColumn::Tx) + ), + QueryValue::TypedValue(TypedValue::Ref(1000)) + ) + .into() + ); let bindings = cc.column_bindings; assert_eq!(bindings.len(), 5); - assert_eq!(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("?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(), - vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Attribute))]); + assert_eq!( + 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(), - vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Value))]); + assert_eq!( + 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(), - vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Tx))]); + assert_eq!( + 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(), - vec![QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::Added))]); + assert_eq!( + 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; assert_eq!(known_types.len(), 4); - assert_eq!(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("?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(), - vec![ValueType::Ref].into_iter().collect()); + assert_eq!( + 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(), - vec![ValueType::Ref].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("?added")).expect("known types for ?added").clone(), - vec![ValueType::Boolean].into_iter().collect()); + assert_eq!( + 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; assert_eq!(extracted_types.len(), 1); - assert_eq!(extracted_types.get(&Variable::from_valid_name("?v")).expect("extracted types for ?v").clone(), - QualifiedAlias("transactions00".to_string(), Column::Transactions(TransactionsColumn::ValueTypeTag))); + assert_eq!( + extracted_types + .get(&Variable::from_valid_name("?v")) + .expect("extracted types for ?v") + .clone(), + QualifiedAlias( + "transactions00".to_string(), + Column::Transactions(TransactionsColumn::ValueTypeTag) + ) + ); } } diff --git a/query-algebrizer/src/clauses/where_fn.rs b/query-algebrizer/src/clauses/where_fn.rs index 6150aff6..41b0771b 100644 --- a/query-algebrizer/src/clauses/where_fn.rs +++ b/query-algebrizer/src/clauses/where_fn.rs @@ -8,18 +8,11 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use edn::query::{ - WhereFn, -}; +use edn::query::WhereFn; -use clauses::{ - ConjoiningClauses, -}; +use clauses::ConjoiningClauses; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; use Known; diff --git a/query-algebrizer/src/lib.rs b/query-algebrizer/src/lib.rs index de2e9b97..9d4c2aad 100644 --- a/query-algebrizer/src/lib.rs +++ b/query-algebrizer/src/lib.rs @@ -20,49 +20,23 @@ use std::collections::BTreeSet; use std::ops::Sub; use std::rc::Rc; +mod clauses; mod types; mod validate; -mod clauses; -use core_traits::{ - Entid, - TypedValue, - ValueType, -}; +use core_traits::{Entid, TypedValue, ValueType}; -use mentat_core::{ - CachedAttributes, - Schema, - parse_query, -}; +use mentat_core::{parse_query, CachedAttributes, Schema}; use mentat_core::counter::RcCounter; -use edn::query::{ - Element, - FindSpec, - Limit, - Order, - ParsedQuery, - SrcVar, - Variable, - WhereClause, -}; +use edn::query::{Element, FindSpec, Limit, Order, ParsedQuery, SrcVar, Variable, WhereClause}; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; -pub use clauses::{ - QueryInputs, - VariableBindings, -}; +pub use clauses::{QueryInputs, VariableBindings}; -pub use types::{ - EmptyBecause, - FindQuery, -}; +pub use types::{EmptyBecause, FindQuery}; /// 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 @@ -71,7 +45,7 @@ pub use types::{ #[derive(Clone, Copy)] pub struct Known<'s, 'c> { pub schema: &'s Schema, - pub cache: Option<&'c CachedAttributes>, + pub cache: Option<&'c dyn CachedAttributes>, } 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 { schema: s, cache: c, @@ -93,36 +67,70 @@ impl<'s, 'c> Known<'s, 'c> { /// 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`. impl<'s, 'c> Known<'s, 'c> { - pub fn is_attribute_cached_reverse(&self, entid: U) -> bool where U: Into { + pub fn is_attribute_cached_reverse(&self, entid: U) -> bool + where + U: Into, + { self.cache .map(|cache| cache.is_attribute_cached_reverse(entid.into())) .unwrap_or(false) } - pub fn is_attribute_cached_forward(&self, entid: U) -> bool where U: Into { + pub fn is_attribute_cached_forward(&self, entid: U) -> bool + where + U: Into, + { self.cache .map(|cache| cache.is_attribute_cached_forward(entid.into())) .unwrap_or(false) } - pub fn get_values_for_entid(&self, schema: &Schema, attribute: U, entid: V) -> Option<&Vec> - where U: Into, V: Into { - self.cache.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into())) + pub fn get_values_for_entid( + &self, + schema: &Schema, + attribute: U, + entid: V, + ) -> Option<&Vec> + where + U: Into, + V: Into, + { + self.cache + .and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into())) } - pub fn get_value_for_entid(&self, schema: &Schema, attribute: U, entid: V) -> Option<&TypedValue> - where U: Into, V: Into { - self.cache.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into())) + pub fn get_value_for_entid( + &self, + schema: &Schema, + attribute: U, + entid: V, + ) -> Option<&TypedValue> + where + U: Into, + V: Into, + { + self.cache + .and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into())) } pub fn get_entid_for_value(&self, attribute: U, value: &TypedValue) -> Option - where U: Into { - self.cache.and_then(|cache| cache.get_entid_for_value(attribute.into(), value)) + where + U: Into, + { + self.cache + .and_then(|cache| cache.get_entid_for_value(attribute.into(), value)) } - pub fn get_entids_for_value(&self, attribute: U, value: &TypedValue) -> Option<&BTreeSet> - where U: Into { - self.cache.and_then(|cache| cache.get_entids_for_value(attribute.into(), value)) + pub fn get_entids_for_value( + &self, + attribute: U, + value: &TypedValue, + ) -> Option<&BTreeSet> + where + U: Into, + { + 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. pub fn is_fully_bound(&self) -> bool { - self.find_spec - .columns() - .all(|e| match e { - // Pull expressions are never fully bound. - // TODO: but the 'inside' of a pull expression certainly can be. - &Element::Pull(_) => false, + self.find_spec.columns().all(|e| match e { + // Pull expressions are never fully bound. + // TODO: but the 'inside' of a pull expression certainly can be. + &Element::Pull(_) => false, - &Element::Variable(ref var) | - &Element::Corresponding(ref var) => self.cc.is_value_bound(var), + &Element::Variable(ref var) | &Element::Corresponding(ref var) => { + self.cc.is_value_bound(var) + } - // 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. - &Element::Aggregate(ref _fn) => false, - }) + // 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. + &Element::Aggregate(ref _fn) => false, + }) } /// 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. pub fn is_fully_unit_bound(&self) -> bool { - self.cc.wheres.is_empty() && - self.is_fully_bound() + self.cc.wheres.is_empty() && self.is_fully_bound() } - /// 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. pub fn unbound_variables(&self) -> BTreeSet { - 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 { +pub fn algebrize_with_counter( + known: Known, + parsed: FindQuery, + counter: usize, +) -> Result { algebrize_with_inputs(known, parsed, counter, QueryInputs::default()) } @@ -200,12 +211,14 @@ pub fn algebrize(known: Known, parsed: FindQuery) -> Result { /// 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 /// clauses possible. -fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option>) - -> Result<(Option>, BTreeSet)> { +fn validate_and_simplify_order( + cc: &ConjoiningClauses, + order: Option>, +) -> Result<(Option>, BTreeSet)> { match order { None => Ok((None, BTreeSet::default())), Some(order) => { - let mut order_bys: Vec = Vec::with_capacity(order.len() * 2); // Space for tags. + let mut order_bys: Vec = Vec::with_capacity(order.len() * 2); // Space for tags. let mut vars: BTreeSet = BTreeSet::default(); for Order(direction, var) in order.into_iter() { @@ -221,49 +234,63 @@ fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option> // Otherwise, determine if we also need to order by type… 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()))); 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 { // Unpack any limit variables in place. - let refined_limit = - match query.limit { - Limit::Variable(ref v) => { - match query.cc.bound_value(v) { - Some(TypedValue::Long(n)) => { - if n <= 0 { - // User-specified limits should always be natural numbers (> 0). - bail!(AlgebrizerError::InvalidLimit(n.to_string(), ValueType::Long)) - } 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 - }, + let refined_limit = match query.limit { + Limit::Variable(ref v) => { + match query.cc.bound_value(v) { + Some(TypedValue::Long(n)) => { + if n <= 0 { + // User-specified limits should always be natural numbers (> 0). + bail!(AlgebrizerError::InvalidLimit( + n.to_string(), + ValueType::Long + )) + } else { + Some(Limit::Fixed(n as u64)) + } } - }, - Limit::None => None, - Limit::Fixed(_) => None, - }; + 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 + } + } + } + Limit::None => None, + Limit::Fixed(_) => None, + }; if let Some(lim) = refined_limit { query.limit = lim; @@ -271,12 +298,15 @@ fn simplify_limit(mut query: AlgebraicQuery) -> Result { Ok(query) } -pub fn algebrize_with_inputs(known: Known, - parsed: FindQuery, - counter: usize, - inputs: QueryInputs) -> Result { +pub fn algebrize_with_inputs( + known: Known, + parsed: FindQuery, + counter: usize, + inputs: QueryInputs, +) -> Result { 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`. 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)?; // 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 { default_source: parsed.default_source, 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, named_projection: extra_vars, order: order, @@ -313,30 +347,14 @@ pub fn algebrize_with_inputs(known: Known, simplify_limit(q) } -pub use clauses::{ - ConjoiningClauses, -}; +pub use clauses::ConjoiningClauses; pub use types::{ - Column, - ColumnAlternation, - ColumnConstraint, - ColumnConstraintOrAlternation, - ColumnIntersection, - ColumnName, - ComputedTable, - DatomsColumn, - DatomsTable, - FulltextColumn, - OrderBy, - QualifiedAlias, - QueryValue, - SourceAlias, - TableAlias, - VariableColumn, + Column, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, ColumnIntersection, + ColumnName, ComputedTable, DatomsColumn, DatomsTable, FulltextColumn, OrderBy, QualifiedAlias, + QueryValue, SourceAlias, TableAlias, VariableColumn, }; - impl FindQuery { pub fn simple(spec: FindSpec, where_clauses: Vec) -> FindQuery { FindQuery { diff --git a/query-algebrizer/src/types.rs b/query-algebrizer/src/types.rs index 80292da6..555291df 100644 --- a/query-algebrizer/src/types.rs +++ b/query-algebrizer/src/types.rs @@ -9,43 +9,24 @@ // specific language governing permissions and limitations under the License. use std::collections::BTreeSet; -use std::fmt::{ - Debug, - Formatter, -}; +use std::fmt::{Debug, Formatter}; -use core_traits::{ - Entid, - TypedValue, - ValueType, - ValueTypeSet, -}; +use core_traits::{Entid, TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - ValueRc, -}; +use mentat_core::ValueRc; -use edn::query::{ - Direction, - FindSpec, - Keyword, - Limit, - Order, - SrcVar, - Variable, - WhereClause, -}; +use edn::query::{Direction, FindSpec, Keyword, Limit, Order, SrcVar, Variable, WhereClause}; /// This enum models the fixed set of default tables we have -- two /// tables and two views -- and computed tables defined in the enclosing CC. #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum DatomsTable { - Datoms, // The non-fulltext datoms table. - FulltextValues, // The virtual table mapping IDs to strings. - FulltextDatoms, // The fulltext-datoms view. - AllDatoms, // Fulltext and non-fulltext datoms. - Computed(usize), // A computed table, tracked elsewhere in the query. - Transactions, // The transactions table, which makes the tx-data log API efficient. + Datoms, // The non-fulltext datoms table. + FulltextValues, // The virtual table mapping IDs to strings. + FulltextDatoms, // The fulltext-datoms view. + AllDatoms, // Fulltext and non-fulltext datoms. + Computed(usize), // A computed table, tracked elsewhere in the query. + 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. @@ -295,7 +276,8 @@ impl QualifiedAlias { Column::Fulltext(_) => None, Column::Variable(_) => None, 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 { use self::QueryValue::*; match self { - &Column(ref qa) => { - write!(f, "{:?}", qa) - }, - &Entid(ref entid) => { - write!(f, "entity({:?})", entid) - }, - &TypedValue(ref typed_value) => { - write!(f, "value({:?})", typed_value) - }, - &PrimitiveLong(value) => { - write!(f, "primitive({:?})", value) - }, - + &Column(ref qa) => write!(f, "{:?}", qa), + &Entid(ref entid) => 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 { use self::Inequality::*; match self { - LessThan => "<", - LessThanOrEquals => "<=", - GreaterThan => ">", + LessThan => "<", + LessThanOrEquals => "<=", + GreaterThan => ">", GreaterThanOrEquals => ">=", - NotEquals => "<>", + NotEquals => "<>", - Unpermute => "<", - Differ => "<>", + Unpermute => "<", + Differ => "<>", - TxAfter => ">", - TxBefore => "<", + TxAfter => ">", + TxBefore => "<", } } pub fn from_datalog_operator(s: &str) -> Option { match s { - "<" => Some(Inequality::LessThan), + "<" => Some(Inequality::LessThan), "<=" => Some(Inequality::LessThanOrEquals), - ">" => Some(Inequality::GreaterThan), + ">" => Some(Inequality::GreaterThan), ">=" => Some(Inequality::GreaterThanOrEquals), "!=" => Some(Inequality::NotEquals), @@ -405,21 +378,12 @@ impl Inequality { pub fn supported_types(&self) -> ValueTypeSet { use self::Inequality::*; match self { - &LessThan | - &LessThanOrEquals | - &GreaterThan | - &GreaterThanOrEquals | - &NotEquals => { + &LessThan | &LessThanOrEquals | &GreaterThan | &GreaterThanOrEquals | &NotEquals => { let mut ts = ValueTypeSet::of_numeric_types(); ts.insert(ValueType::Instant); ts - }, - &Unpermute | - &Differ | - &TxAfter | - &TxBefore => { - ValueTypeSet::of_one(ValueType::Ref) - }, + } + &Unpermute | &Differ | &TxAfter | &TxBefore => ValueTypeSet::of_one(ValueType::Ref), } } } @@ -432,7 +396,7 @@ impl Debug for Inequality { &LessThanOrEquals => "<=", &GreaterThan => ">", &GreaterThanOrEquals => ">=", - &NotEquals => "!=", // Datalog uses !=. SQL uses <>. + &NotEquals => "!=", // Datalog uses !=. SQL uses <>. &Unpermute => "<", &Differ => "<>", @@ -570,51 +534,78 @@ impl Debug for ColumnConstraint { fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { use self::ColumnConstraint::*; match self { - &Equals(ref qa1, ref thing) => { - write!(f, "{:?} = {:?}", qa1, thing) - }, + &Equals(ref qa1, ref thing) => write!(f, "{:?} = {:?}", qa1, thing), - &Inequality { operator, ref left, ref right } => { - write!(f, "{:?} {:?} {:?}", left, operator, right) - }, + &Inequality { + operator, + ref left, + ref right, + } => write!(f, "{:?} {:?} {:?}", left, operator, right), - &Matches(ref qa, ref thing) => { - write!(f, "{:?} MATCHES {:?}", qa, thing) - }, + &Matches(ref qa, ref 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. write!(f, "(")?; for value_type in value_types.iter() { write!(f, "({:?}.value_type_tag = {:?}", value, value_type)?; - if check_value && value_type == ValueType::Double || value_type == ValueType::Long { - write!(f, " AND typeof({:?}) = '{:?}')", value, - if value_type == ValueType::Double { "real" } else { "integer" })?; + if check_value && value_type == ValueType::Double + || value_type == ValueType::Long + { + write!( + f, + " AND typeof({:?}) = '{:?}')", + value, + if value_type == ValueType::Double { + "real" + } else { + "integer" + } + )?; } else { write!(f, ")")?; } write!(f, " OR ")?; } write!(f, "1)") - }, - &NotExists(ref ct) => { - write!(f, "NOT EXISTS {:?}", ct) - }, + } + &NotExists(ref ct) => write!(f, "NOT EXISTS {:?}", ct), } } } #[derive(PartialEq, Clone)] pub enum EmptyBecause { - CachedAttributeHasNoValues { entity: Entid, attr: Entid }, - CachedAttributeHasNoEntity { value: TypedValue, attr: Entid }, - ConflictingBindings { var: Variable, existing: TypedValue, desired: TypedValue }, + CachedAttributeHasNoValues { + entity: Entid, + 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. - TypeMismatch { var: Variable, existing: ValueTypeSet, desired: ValueTypeSet }, + TypeMismatch { + var: Variable, + existing: ValueTypeSet, + desired: ValueTypeSet, + }, // The same, but for non-variables. - KnownTypeMismatch { left: ValueTypeSet, right: ValueTypeSet }, + KnownTypeMismatch { + left: ValueTypeSet, + right: ValueTypeSet, + }, NoValidTypes(Variable), NonAttributeArgument, NonInstantArgument, @@ -627,76 +618,70 @@ pub enum EmptyBecause { InvalidAttributeEntid(Entid), InvalidBinding(Column, 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 { fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result { use self::EmptyBecause::*; match self { - &CachedAttributeHasNoEntity { ref value, ref attr } => { - write!(f, "(?e, {}, {:?}, _) not present in store", attr, value) - }, - &CachedAttributeHasNoValues { ref entity, ref attr } => { - write!(f, "({}, {}, ?v, _) not present in store", entity, attr) - }, - &ConflictingBindings { ref var, ref existing, ref desired } => { - write!(f, "Var {:?} can't be {:?} because it's already bound to {:?}", - var, desired, existing) - }, - &TypeMismatch { ref var, ref existing, ref desired } => { - write!(f, "Type mismatch: {:?} can't be {:?}, because it's already {:?}", - var, desired, existing) - }, - &KnownTypeMismatch { ref left, ref right } => { - write!(f, "Type mismatch: {:?} can't be compared to {:?}", - left, right) - }, - &NoValidTypes(ref var) => { - write!(f, "Type mismatch: {:?} has no valid types", var) - }, - &NonAttributeArgument => { - write!(f, "Non-attribute argument in attribute place") - }, - &NonInstantArgument => { - write!(f, "Non-instant argument in instant place") - }, - &NonEntityArgument => { - write!(f, "Non-entity argument in entity place") - }, - &NonNumericArgument => { - write!(f, "Non-numeric argument in numeric place") - }, - &NonStringFulltextValue => { - write!(f, "Non-string argument for fulltext attribute") - }, - &UnresolvedIdent(ref kw) => { - write!(f, "Couldn't resolve keyword {}", kw) - }, - &InvalidAttributeIdent(ref kw) => { - write!(f, "{} does not name an attribute", kw) - }, - &InvalidAttributeEntid(entid) => { - write!(f, "{} is not an attribute", entid) - }, - &NonFulltextAttribute(entid) => { - write!(f, "{} is not a fulltext attribute", entid) - }, + &CachedAttributeHasNoEntity { + ref value, + ref attr, + } => write!(f, "(?e, {}, {:?}, _) not present in store", attr, value), + &CachedAttributeHasNoValues { + ref entity, + ref attr, + } => write!(f, "({}, {}, ?v, _) not present in store", entity, attr), + &ConflictingBindings { + ref var, + ref existing, + ref desired, + } => write!( + f, + "Var {:?} can't be {:?} because it's already bound to {:?}", + var, desired, existing + ), + &TypeMismatch { + ref var, + ref existing, + ref desired, + } => write!( + f, + "Type mismatch: {:?} can't be {:?}, because it's already {:?}", + var, desired, existing + ), + &KnownTypeMismatch { + ref left, + ref right, + } => write!( + f, + "Type mismatch: {:?} can't be compared to {:?}", + left, right + ), + &NoValidTypes(ref var) => write!(f, "Type mismatch: {:?} has no valid types", var), + &NonAttributeArgument => write!(f, "Non-attribute argument in attribute place"), + &NonInstantArgument => write!(f, "Non-instant argument in instant place"), + &NonEntityArgument => write!(f, "Non-entity argument in entity place"), + &NonNumericArgument => write!(f, "Non-numeric argument in numeric place"), + &NonStringFulltextValue => write!(f, "Non-string argument for fulltext attribute"), + &UnresolvedIdent(ref kw) => write!(f, "Couldn't resolve keyword {}", kw), + &InvalidAttributeIdent(ref kw) => write!(f, "{} does not name an attribute", kw), + &InvalidAttributeEntid(entid) => write!(f, "{} is not an attribute", entid), + &NonFulltextAttribute(entid) => write!(f, "{} is not a fulltext attribute", entid), &InvalidBinding(ref column, ref tv) => { write!(f, "{:?} cannot name column {:?}", tv, column) - }, - &ValueTypeMismatch(value_type, ref typed_value) => { - write!(f, "Type mismatch: {:?} doesn't match attribute type {:?}", - typed_value, value_type) - }, - &AttributeLookupFailed => { - write!(f, "Attribute lookup failed") - }, + } + &ValueTypeMismatch(value_type, ref typed_value) => write!( + f, + "Type mismatch: {:?} doesn't match attribute type {:?}", + typed_value, value_type + ), + &AttributeLookupFailed => write!(f, "Attribute lookup failed"), } } } - /// A `FindQuery` represents a valid query to the query algebrizer. /// /// We split `FindQuery` from `ParsedQuery` because it's not easy to generalize over containers @@ -720,7 +705,7 @@ pub struct FindQuery { pub enum EvolvedNonValuePlace { Placeholder, 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? diff --git a/query-algebrizer/src/validate.rs b/query-algebrizer/src/validate.rs index 59d2dcb7..68baba79 100644 --- a/query-algebrizer/src/validate.rs +++ b/query-algebrizer/src/validate.rs @@ -10,18 +10,9 @@ use std::collections::BTreeSet; -use edn::query::{ - ContainsVariables, - OrJoin, - NotJoin, - Variable, - UnifyVars, -}; +use edn::query::{ContainsVariables, NotJoin, OrJoin, UnifyVars, Variable}; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - Result, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, Result}; /// In an `or` expression, every mentioned var is considered 'free'. /// 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(()) } - }, + } UnifyVars::Explicit(ref vars) => { // Each leg must use the joined vars. let var_set: BTreeSet = vars.iter().cloned().collect(); @@ -71,16 +62,14 @@ pub(crate) fn validate_or_join(or_join: &OrJoin) -> Result<()> { } } Ok(()) - }, + } } } pub(crate) fn validate_not_join(not_join: &NotJoin) -> Result<()> { // Grab our mentioned variables and ensure that the rules are followed. match not_join.unify_vars { - UnifyVars::Implicit => { - Ok(()) - }, + UnifyVars::Implicit => Ok(()), UnifyVars::Explicit(ref vars) => { // The joined vars must each appear somewhere in the clause's mentioned variables. let var_set: BTreeSet = vars.iter().cloned().collect(); @@ -88,33 +77,25 @@ pub(crate) fn validate_not_join(not_join: &NotJoin) -> Result<()> { bail!(AlgebrizerError::NonMatchingVariablesInNotClause) } Ok(()) - }, + } } } #[cfg(test)] mod tests { - extern crate mentat_core; extern crate edn; + extern crate mentat_core; use edn::query::{ - Keyword, - OrWhereClause, - Pattern, - PatternNonValuePlace, - PatternValuePlace, - UnifyVars, - Variable, - WhereClause, + Keyword, OrWhereClause, Pattern, PatternNonValuePlace, PatternValuePlace, UnifyVars, + Variable, WhereClause, }; use clauses::ident; use super::*; use parse_find_string; - use types::{ - FindQuery, - }; + use types::FindQuery; fn value_ident(ns: &str, name: &str) -> PatternValuePlace { Keyword::namespaced(ns, name).into() @@ -134,7 +115,7 @@ mod tests { assert_eq!((), validate_or_join(&or_join).unwrap()); assert_eq!(expected_unify, or_join.unify_vars); or_join.clauses - }, + } _ => panic!(), } } @@ -157,31 +138,38 @@ mod tests { left, OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?artist" + )), attribute: ident("artist", "type"), value: value_ident("artist.type", "group"), tx: PatternNonValuePlace::Placeholder, - }))); + })) + ); assert_eq!( right, - OrWhereClause::And( - vec![ - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), - attribute: ident("artist", "type"), - value: value_ident("artist.type", "person"), - tx: PatternNonValuePlace::Placeholder, - }), - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), - attribute: ident("artist", "gender"), - value: value_ident("artist.gender", "female"), - tx: PatternNonValuePlace::Placeholder, - }), - ])); - }, + OrWhereClause::And(vec![ + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?artist" + )), + attribute: ident("artist", "type"), + value: value_ident("artist.type", "person"), + tx: PatternNonValuePlace::Placeholder, + }), + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?artist" + )), + attribute: ident("artist", "gender"), + value: value_ident("artist.gender", "female"), + tx: PatternNonValuePlace::Placeholder, + }), + ]) + ); + } _ => panic!(), }; } @@ -193,7 +181,12 @@ mod tests { :where (or [?artist :artist/type :artist.type/group] [?artist :artist/type ?type])]"#; 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()), _ => panic!(), } @@ -209,7 +202,10 @@ mod tests { (and [?artist :artist/type ?type] [?type :artist/role :artist.role/parody]))]"#; 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 mut arms = clauses.into_iter(); @@ -219,36 +215,42 @@ mod tests { left, OrWhereClause::Clause(WhereClause::Pattern(Pattern { source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?artist" + )), attribute: ident("artist", "type"), value: value_ident("artist.type", "group"), tx: PatternNonValuePlace::Placeholder, - }))); + })) + ); assert_eq!( right, - OrWhereClause::And( - vec![ - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?artist")), - attribute: ident("artist", "type"), - value: PatternValuePlace::Variable(Variable::from_valid_name("?type")), - tx: PatternNonValuePlace::Placeholder, - }), - WhereClause::Pattern(Pattern { - source: None, - entity: PatternNonValuePlace::Variable(Variable::from_valid_name("?type")), - attribute: ident("artist", "role"), - value: value_ident("artist.role", "parody"), - tx: PatternNonValuePlace::Placeholder, - }), - ])); - }, + OrWhereClause::And(vec![ + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?artist" + )), + attribute: ident("artist", "type"), + value: PatternValuePlace::Variable(Variable::from_valid_name("?type")), + tx: PatternNonValuePlace::Placeholder, + }), + WhereClause::Pattern(Pattern { + source: None, + entity: PatternNonValuePlace::Variable(Variable::from_valid_name( + "?type" + )), + attribute: ident("artist", "role"), + value: value_ident("artist.role", "parody"), + tx: PatternNonValuePlace::Placeholder, + }), + ]) + ); + } _ => panic!(), }; } - /// Tests that the top-level form is a valid `not`, returning the clauses. fn valid_not_join(parsed: FindQuery, expected_unify: UnifyVars) -> Vec { // Filter out all the clauses that are not `not`s. @@ -267,7 +269,7 @@ mod tests { assert_eq!((), validate_not_join(¬_join).unwrap()); assert_eq!(expected_unify, not_join.unify_vars); not_join.clauses - }, + } _ => panic!(), } } @@ -296,7 +298,8 @@ mod tests { attribute: artist_country.clone(), value: value_ident("country", "CA"), tx: PatternNonValuePlace::Placeholder, - })); + }) + ); assert_eq!( clause2, WhereClause::Pattern(Pattern { @@ -305,8 +308,9 @@ mod tests { attribute: artist_country, value: value_ident("country", "GB"), tx: PatternNonValuePlace::Placeholder, - })); - }, + }) + ); + } _ => panic!(), }; } @@ -319,7 +323,10 @@ mod tests { [?release :release/artists ?artist] [?release :release/year 1970])]"#; 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 artist = PatternValuePlace::Variable(Variable::from_valid_name("?artist")); @@ -335,7 +342,8 @@ mod tests { attribute: ident("release", "artists"), value: artist, tx: PatternNonValuePlace::Placeholder, - })); + }) + ); assert_eq!( clause2, WhereClause::Pattern(Pattern { @@ -344,8 +352,9 @@ mod tests { attribute: ident("release", "year"), value: PatternValuePlace::EntidOrInteger(1970), tx: PatternNonValuePlace::Placeholder, - })); - }, + }) + ); + } _ => panic!(), }; } diff --git a/query-algebrizer/tests/fulltext.rs b/query-algebrizer/tests/fulltext.rs index a7fc4343..277c6507 100644 --- a/query-algebrizer/tests/fulltext.rs +++ b/query-algebrizer/tests/fulltext.rs @@ -8,32 +8,21 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +extern crate core_traits; extern crate edn; extern crate mentat_core; -extern crate core_traits; extern crate mentat_query_algebrizer; extern crate query_algebrizer_traits; mod utils; -use core_traits::{ - Attribute, - ValueType, -}; +use core_traits::{Attribute, ValueType}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::query::{ - Keyword, -}; +use edn::query::Keyword; -use utils::{ - add_attribute, - alg, - associate_ident, -}; +use utils::{add_attribute, alg, associate_ident}; 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", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); - add_attribute(&mut schema, 65, Attribute { - value_type: ValueType::String, - multival: false, - ..Default::default() - }); - add_attribute(&mut schema, 66, Attribute { - value_type: ValueType::String, - index: true, - fulltext: true, - multival: true, - ..Default::default() - }); - add_attribute(&mut schema, 67, Attribute { - value_type: ValueType::String, - 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() - }); + add_attribute( + &mut schema, + 65, + Attribute { + value_type: ValueType::String, + multival: false, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 66, + Attribute { + value_type: ValueType::String, + index: true, + fulltext: true, + multival: true, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 67, + Attribute { + value_type: ValueType::String, + 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 } diff --git a/query-algebrizer/tests/ground.rs b/query-algebrizer/tests/ground.rs index 44e08543..ed7c8d8b 100644 --- a/query-algebrizer/tests/ground.rs +++ b/query-algebrizer/tests/ground.rs @@ -8,9 +8,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +extern crate core_traits; extern crate edn; extern crate mentat_core; -extern crate core_traits; extern crate mentat_query_algebrizer; extern crate query_algebrizer_traits; @@ -18,40 +18,17 @@ mod utils; use std::collections::BTreeMap; -use core_traits::{ - Attribute, - ValueType, - TypedValue, -}; +use core_traits::{Attribute, TypedValue, ValueType}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::query::{ - Keyword, - PlainSymbol, - Variable, -}; +use edn::query::{Keyword, PlainSymbol, Variable}; -use query_algebrizer_traits::errors::{ - AlgebrizerError, - BindingError, -}; +use query_algebrizer_traits::errors::{AlgebrizerError, BindingError}; -use mentat_query_algebrizer::{ - ComputedTable, - Known, - QueryInputs, -}; +use mentat_query_algebrizer::{ComputedTable, Known, QueryInputs}; -use utils::{ - add_attribute, - alg, - associate_ident, - bails, - bails_with_inputs, -}; +use utils::{add_attribute, alg, associate_ident, bails, bails_with_inputs}; fn prepopulated_schema() -> Schema { 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", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); - add_attribute(&mut schema, 65, Attribute { - value_type: ValueType::String, - multival: false, - ..Default::default() - }); - add_attribute(&mut schema, 66, Attribute { - value_type: ValueType::Ref, - multival: true, - ..Default::default() - }); - add_attribute(&mut schema, 67, Attribute { - value_type: ValueType::String, - 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() - }); + add_attribute( + &mut schema, + 65, + Attribute { + value_type: ValueType::String, + multival: false, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 66, + Attribute { + value_type: ValueType::Ref, + multival: true, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 67, + Attribute { + value_type: ValueType::String, + 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 } @@ -126,10 +123,13 @@ fn test_ground_coll_skips_impossible() { let known = Known::for_schema(&schema); let cc = alg(known, &q); assert!(cc.empty_because.is_none()); - assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { - names: vec![Variable::from_valid_name("?x")], - values: vec![TypedValue::Ref(5), TypedValue::Ref(11)], - }); + assert_eq!( + cc.computed_tables[0], + ComputedTable::NamedValues { + names: vec![Variable::from_valid_name("?x")], + values: vec![TypedValue::Ref(5), TypedValue::Ref(11)], + } + ); } #[test] @@ -148,10 +148,21 @@ fn test_ground_rel_skips_impossible() { let known = Known::for_schema(&schema); let cc = alg(known, &q); assert!(cc.empty_because.is_none()); - assert_eq!(cc.computed_tables[0], 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)], - }); + assert_eq!( + cc.computed_tables[0], + 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] @@ -186,8 +197,14 @@ fn test_ground_tuple_placeholders() { let known = Known::for_schema(&schema); let cc = alg(known, &q); assert!(cc.empty_because.is_none()); - assert_eq!(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))); + assert_eq!( + 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] @@ -197,17 +214,23 @@ fn test_ground_rel_placeholders() { let known = Known::for_schema(&schema); let cc = alg(known, &q); assert!(cc.empty_because.is_none()); - assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { - names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?p")], - values: vec![ - TypedValue::Ref(8), - TypedValue::Ref(3), - TypedValue::Ref(5), - TypedValue::Ref(7), - TypedValue::Ref(5), - TypedValue::Ref(9), - ], - }); + assert_eq!( + cc.computed_tables[0], + ComputedTable::NamedValues { + names: vec![ + Variable::from_valid_name("?x"), + Variable::from_valid_name("?p") + ], + values: vec![ + 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… @@ -227,8 +250,14 @@ fn test_ground_tuple_infers_types() { let known = Known::for_schema(&schema); let cc = alg(known, &q); assert!(cc.empty_because.is_none()); - assert_eq!(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))); + assert_eq!( + 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 @@ -251,10 +280,16 @@ fn test_ground_rel_infers_types() { let known = Known::for_schema(&schema); let cc = alg(known, &q); assert!(cc.empty_because.is_none()); - assert_eq!(cc.computed_tables[0], ComputedTable::NamedValues { - names: vec![Variable::from_valid_name("?x"), Variable::from_valid_name("?v")], - values: vec![TypedValue::Ref(8), TypedValue::Long(10)], - }); + assert_eq!( + cc.computed_tables[0], + ComputedTable::NamedValues { + names: vec![ + Variable::from_valid_name("?x"), + Variable::from_valid_name("?v") + ], + values: vec![TypedValue::Ref(8), TypedValue::Long(10)], + } + ); } #[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 schema = prepopulated_schema(); let known = Known::for_schema(&schema); - assert_eq!(bails(known, &q), - AlgebrizerError::InvalidGroundConstant); + assert_eq!(bails(known, &q), AlgebrizerError::InvalidGroundConstant); } #[test] @@ -271,8 +305,7 @@ fn test_ground_rel_heterogeneous_types() { let q = r#"[:find ?x :where [?x _ ?v] [(ground [[false] [5]]) [[?v]]]]"#; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - assert_eq!(bails(known, &q), - AlgebrizerError::InvalidGroundConstant); + assert_eq!(bails(known, &q), AlgebrizerError::InvalidGroundConstant); } #[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 schema = prepopulated_schema(); let known = Known::for_schema(&schema); - assert_eq!(bails(known, &q), - AlgebrizerError::InvalidBinding(PlainSymbol::plain("ground"), BindingError::RepeatedBoundVariable)); + assert_eq!( + bails(known, &q), + AlgebrizerError::InvalidBinding( + PlainSymbol::plain("ground"), + BindingError::RepeatedBoundVariable + ) + ); } #[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 schema = prepopulated_schema(); let known = Known::for_schema(&schema); - assert_eq!(bails(known, &q), - AlgebrizerError::InvalidBinding(PlainSymbol::plain("ground"), BindingError::RepeatedBoundVariable)); + assert_eq!( + bails(known, &q), + AlgebrizerError::InvalidBinding( + PlainSymbol::plain("ground"), + BindingError::RepeatedBoundVariable + ) + ); } #[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 schema = prepopulated_schema(); let known = Known::for_schema(&schema); - assert_eq!(bails(known, &q), - AlgebrizerError::UnboundVariable(PlainSymbol::plain("?v"))); + assert_eq!( + bails(known, &q), + AlgebrizerError::UnboundVariable(PlainSymbol::plain("?v")) + ); } #[test] @@ -315,6 +360,8 @@ fn test_unbound_input_variable_invalid() { let i = QueryInputs::new(types, BTreeMap::default()).expect("valid QueryInputs"); - assert_eq!(bails_with_inputs(known, &q, i), - AlgebrizerError::UnboundVariable(PlainSymbol::plain("?x"))); + assert_eq!( + bails_with_inputs(known, &q, i), + AlgebrizerError::UnboundVariable(PlainSymbol::plain("?x")) + ); } diff --git a/query-algebrizer/tests/predicate.rs b/query-algebrizer/tests/predicate.rs index 9e689f19..22f25901 100644 --- a/query-algebrizer/tests/predicate.rs +++ b/query-algebrizer/tests/predicate.rs @@ -8,71 +8,58 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +extern crate core_traits; extern crate edn; extern crate mentat_core; -extern crate core_traits; extern crate mentat_query_algebrizer; extern crate query_algebrizer_traits; mod utils; -use core_traits::{ - Attribute, - ValueType, - TypedValue, - ValueTypeSet, -}; +use core_traits::{Attribute, TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - DateTime, - Schema, - Utc, -}; +use mentat_core::{DateTime, Schema, Utc}; -use edn::query::{ - Keyword, - PlainSymbol, - Variable, -}; +use edn::query::{Keyword, PlainSymbol, Variable}; -use query_algebrizer_traits::errors::{ - AlgebrizerError, -}; +use query_algebrizer_traits::errors::AlgebrizerError; -use mentat_query_algebrizer::{ - EmptyBecause, - Known, - QueryInputs, -}; +use mentat_query_algebrizer::{EmptyBecause, Known, QueryInputs}; -use utils::{ - add_attribute, - alg, - alg_with_inputs, - associate_ident, - bails, -}; +use utils::{add_attribute, alg, alg_with_inputs, associate_ident, bails}; fn prepopulated_schema() -> Schema { let mut schema = Schema::default(); 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", "long"), 67); - add_attribute(&mut schema, 65, Attribute { - value_type: ValueType::Instant, - multival: false, - ..Default::default() - }); - add_attribute(&mut schema, 66, Attribute { - value_type: ValueType::Double, - multival: false, - ..Default::default() - }); - add_attribute(&mut schema, 67, Attribute { - value_type: ValueType::Long, - multival: false, - ..Default::default() - }); + add_attribute( + &mut schema, + 65, + Attribute { + value_type: ValueType::Instant, + multival: false, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 66, + Attribute { + value_type: ValueType::Double, + multival: false, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 67, + Attribute { + value_type: ValueType::Long, + multival: false, + ..Default::default() + }, + ); schema } @@ -86,21 +73,27 @@ fn test_instant_predicates_require_instants() { :where [?e :foo/date ?t] [(> ?t "2017-06-16T00:56:41.257Z")]]"#; - assert_eq!(bails(known, query), + assert_eq!( + bails(known, query), AlgebrizerError::InvalidArgumentType( PlainSymbol::plain(">"), ValueTypeSet::of_numeric_and_instant_types(), - 1)); + 1 + ) + ); let query = r#"[:find ?e :where [?e :foo/date ?t] [(> "2017-06-16T00:56:41.257Z", ?t)]]"#; - assert_eq!(bails(known, query), + assert_eq!( + bails(known, query), AlgebrizerError::InvalidArgumentType( PlainSymbol::plain(">"), 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. // 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)]]"#; let cc = alg(known, query); assert!(cc.is_known_empty()); - assert_eq!(cc.empty_because.unwrap(), - EmptyBecause::TypeMismatch { - var: Variable::from_valid_name("?t"), - existing: ValueTypeSet::of_one(ValueType::Instant), - desired: ValueTypeSet::of_numeric_types(), - }); + assert_eq!( + cc.empty_because.unwrap(), + EmptyBecause::TypeMismatch { + var: Variable::from_valid_name("?t"), + existing: ValueTypeSet::of_one(ValueType::Instant), + desired: ValueTypeSet::of_numeric_types(), + } + ); // You can compare doubles to longs. let query = r#"[:find ?e @@ -125,8 +120,11 @@ fn test_instant_predicates_require_instants() { [(< ?t 1234512345)]]"#; let cc = alg(known, query); assert!(!cc.is_known_empty()); - assert_eq!(cc.known_type(&Variable::from_valid_name("?t")).expect("?t is known"), - ValueType::Double); + assert_eq!( + cc.known_type(&Variable::from_valid_name("?t")) + .expect("?t is known"), + ValueType::Double + ); } #[test] @@ -135,27 +133,41 @@ fn test_instant_predicates_accepts_var() { let known = Known::for_schema(&schema); let instant_var = Variable::from_valid_name("?time"); - let instant_value = TypedValue::Instant(DateTime::parse_from_rfc3339("2018-04-11T19:17:00.000Z") - .map(|t| t.with_timezone(&Utc)) - .expect("expected valid date")); + let instant_value = TypedValue::Instant( + DateTime::parse_from_rfc3339("2018-04-11T19:17:00.000Z") + .map(|t| t.with_timezone(&Utc)) + .expect("expected valid date"), + ); let query = r#"[:find ?e :in ?time :where [?e :foo/date ?t] [(< ?t ?time)]]"#; - let cc = alg_with_inputs(known, 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 cc = alg_with_inputs( + known, + 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 :in ?time :where [?e :foo/date ?t] [(> ?time, ?t)]]"#; - let cc = alg_with_inputs(known, 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 cc = alg_with_inputs( + known, + 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] @@ -172,16 +184,28 @@ fn test_numeric_predicates_accepts_var() { :where [?e :foo/long ?t] [(> ?t ?long)]]"#; - let cc = alg_with_inputs(known, 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 cc = alg_with_inputs( + known, + 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 :in ?long :where [?e :foo/long ?t] [(> ?long, ?t)]]"#; - let cc = alg_with_inputs(known, 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 cc = alg_with_inputs( + known, + 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 + ); } diff --git a/query-algebrizer/tests/type_reqs.rs b/query-algebrizer/tests/type_reqs.rs index 9cad25a4..930cff2b 100644 --- a/query-algebrizer/tests/type_reqs.rs +++ b/query-algebrizer/tests/type_reqs.rs @@ -8,27 +8,19 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate edn; extern crate core_traits; +extern crate edn; extern crate mentat_core; extern crate mentat_query_algebrizer; extern crate query_algebrizer_traits; mod utils; -use utils::{ - alg, - SchemaBuilder, - bails, -}; +use utils::{alg, bails, SchemaBuilder}; -use core_traits::{ - ValueType, -}; +use core_traits::ValueType; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; use mentat_query_algebrizer::Known; @@ -51,13 +43,21 @@ fn test_empty_known() { let known = Known::for_schema(&schema); for known_type in ValueType::all_enums().iter() { for required in ValueType::all_enums().iter() { - let q = format!("[:find ?e :where [?e :test/{} ?v] [(type ?v {})]]", - known_type.into_keyword().name(), required); + let q = format!( + "[:find ?e :where [?e :test/{} ?v] [(type ?v {})]]", + known_type.into_keyword().name(), + required + ); println!("Query: {}", q); let cc = alg(known, &q); // It should only be empty if the known type and our requirement differ. - assert_eq!(cc.empty_because.is_some(), known_type != required, - "known_type = {}; required = {}", known_type, required); + assert_eq!( + cc.empty_because.is_some(), + known_type != required, + "known_type = {}; required = {}", + known_type, + required + ); } } } diff --git a/query-algebrizer/tests/utils/mod.rs b/query-algebrizer/tests/utils/mod.rs index 6f94f4d8..b8c86ec0 100644 --- a/query-algebrizer/tests/utils/mod.rs +++ b/query-algebrizer/tests/utils/mod.rs @@ -13,31 +13,16 @@ // this module will get warnings otherwise). #![allow(dead_code)] -use core_traits::{ - Attribute, - Entid, - ValueType, -}; +use core_traits::{Attribute, Entid, ValueType}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::query::{ - Keyword, -}; +use edn::query::Keyword; -use query_algebrizer_traits::errors::{ - AlgebrizerError, -}; +use query_algebrizer_traits::errors::AlgebrizerError; use mentat_query_algebrizer::{ - ConjoiningClauses, - Known, - QueryInputs, - algebrize, - algebrize_with_inputs, - parse_find_string, + algebrize, algebrize_with_inputs, parse_find_string, ConjoiningClauses, Known, QueryInputs, }; // Common utility functions used in multiple test files. @@ -61,7 +46,7 @@ impl SchemaBuilder { pub fn new() -> SchemaBuilder { SchemaBuilder { schema: Schema::default(), - counter: 65 + counter: 65, } } @@ -72,18 +57,24 @@ impl SchemaBuilder { self } - pub fn define_simple_attr(self, - keyword_ns: T, - keyword_name: T, - value_type: ValueType, - multival: bool) -> Self - where T: AsRef + pub fn define_simple_attr( + self, + keyword_ns: T, + keyword_name: T, + value_type: ValueType, + multival: bool, + ) -> Self + where + T: AsRef, { - self.define_attr(Keyword::namespaced(keyword_ns, keyword_name), Attribute { - value_type, - multival, - ..Default::default() - }) + self.define_attr( + Keyword::namespaced(keyword_ns, keyword_name), + Attribute { + 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 { 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 { 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 } diff --git a/query-projector-traits/Cargo.toml b/query-projector-traits/Cargo.toml index 6bbbd717..6942ae40 100644 --- a/query-projector-traits/Cargo.toml +++ b/query-projector-traits/Cargo.toml @@ -15,7 +15,7 @@ failure = "0.1.1" failure_derive = "0.1.1" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.edn] diff --git a/query-projector-traits/aggregates.rs b/query-projector-traits/aggregates.rs index a99a003c..fe8bc478 100644 --- a/query-projector-traits/aggregates.rs +++ b/query-projector-traits/aggregates.rs @@ -8,34 +8,15 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - ValueType, - ValueTypeSet, -}; +use core_traits::{ValueType, ValueTypeSet}; -use edn::query::{ - Aggregate, - QueryFunction, - Variable, -}; +use edn::query::{Aggregate, QueryFunction, Variable}; -use mentat_query_algebrizer::{ - ColumnName, - ConjoiningClauses, - VariableColumn, -}; +use mentat_query_algebrizer::{ColumnName, ConjoiningClauses, VariableColumn}; -use mentat_query_sql::{ - ColumnOrExpression, - Expression, - Name, - ProjectedColumn, -}; +use mentat_query_sql::{ColumnOrExpression, Expression, Name, ProjectedColumn}; -use errors::{ - ProjectorError, - Result, -}; +use errors::{ProjectorError, Result}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] 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. Ok(ValueType::Double) } else { - bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) + bail!(ProjectorError::CannotApplyAggregateOperationToTypes( + *self, + possibilities + )) } - }, + } &Sum => { if possibilities.is_only_numeric() { if possibilities.contains(ValueType::Double) { @@ -104,9 +88,12 @@ impl SimpleAggregationOp { Ok(ValueType::Long) } } else { - bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) + bail!(ProjectorError::CannotApplyAggregateOperationToTypes( + *self, + possibilities + )) } - }, + } &Max | &Min => { if possibilities.is_unit() { @@ -124,8 +111,11 @@ impl SimpleAggregationOp { // These types are unordered. Keyword | Ref | Uuid => { - bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) - }, + bail!(ProjectorError::CannotApplyAggregateOperationToTypes( + *self, + possibilities + )) + } } } else { // It cannot be empty -- we checked. @@ -139,10 +129,13 @@ impl SimpleAggregationOp { Ok(ValueType::Long) } } else { - bail!(ProjectorError::CannotApplyAggregateOperationToTypes(*self, possibilities)) + bail!(ProjectorError::CannotApplyAggregateOperationToTypes( + *self, + possibilities + )) } } - }, + } } } } @@ -184,10 +177,10 @@ impl SimpleAggregation for Aggregate { if self.args.len() != 1 { return None; } - self.args[0] - .as_variable() - .and_then(|v| SimpleAggregationOp::for_function(&self.func) - .map(|op| SimpleAggregate { op, var: v.clone(), })) + self.args[0].as_variable().and_then(|v| { + SimpleAggregationOp::for_function(&self.func) + .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 /// variables by name; never to a datoms column. /// - 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 return_type = simple.op.is_applicable_to_types(known_types)?; - let projected_column_or_expression = - if let Some(value) = cc.bound_value(&simple.var) { - // Oh, we already know the value! - if simple.use_static_value() { - // We can statically compute the aggregate result for some operators -- not count or - // sum, but avg/max/min are OK. - 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) - } - } + let projected_column_or_expression = if let Some(value) = cc.bound_value(&simple.var) { + // Oh, we already know the value! + if simple.use_static_value() { + // We can statically compute the aggregate result for some operators -- not count or + // sum, but avg/max/min are OK. + ColumnOrExpression::Value(value) } 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), + arg: ColumnOrExpression::Value(value), }; if simple.is_nullable() { ColumnOrExpression::NullableAggregate(Box::new(expression), return_type) } else { 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, + )) } diff --git a/query-projector-traits/errors.rs b/query-projector-traits/errors.rs index e53f80e3..fb0f2d39 100644 --- a/query-projector-traits/errors.rs +++ b/query-projector-traits/errors.rs @@ -12,20 +12,12 @@ use std; // To refer to std::result::Result. use rusqlite; -use core_traits::{ - ValueTypeSet, -}; +use core_traits::ValueTypeSet; use db_traits::errors::DbError; -use edn::query::{ - PlainSymbol, -}; -use query_pull_traits::errors::{ - PullError, -}; +use edn::query::PlainSymbol; +use query_pull_traits::errors::PullError; -use aggregates::{ - SimpleAggregationOp, -}; +use aggregates::SimpleAggregationOp; pub type Result = std::result::Result; @@ -39,7 +31,10 @@ pub enum ProjectorError { #[fail(display = "no possible types for value provided to {:?}", _0)] 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), #[fail(display = "invalid projection: {}", _0)] @@ -54,7 +49,10 @@ pub enum ProjectorError { #[fail(display = "expected {}, got {}", _0, _1)] 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), #[fail(display = "min/max expressions: {} (max 1), corresponding: {}", _0, _1)] diff --git a/query-projector-traits/lib.rs b/query-projector-traits/lib.rs index 30b14162..39af2be9 100644 --- a/query-projector-traits/lib.rs +++ b/query-projector-traits/lib.rs @@ -23,6 +23,5 @@ extern crate query_pull_traits; extern crate mentat_query_algebrizer; extern crate mentat_query_sql; -pub mod errors; pub mod aggregates; - +pub mod errors; diff --git a/query-projector-traits/tests/aggregates.rs b/query-projector-traits/tests/aggregates.rs index e3b03c1d..c0fa99df 100644 --- a/query-projector-traits/tests/aggregates.rs +++ b/query-projector-traits/tests/aggregates.rs @@ -8,36 +8,22 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +extern crate core_traits; extern crate edn; extern crate mentat_core; -extern crate core_traits; extern crate mentat_query_algebrizer; extern crate mentat_query_projector; extern crate query_projector_traits; -use core_traits::{ - Attribute, - Entid, - ValueType, -}; +use core_traits::{Attribute, Entid, ValueType}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; -use edn::query::{ - Keyword, -}; +use edn::query::Keyword; -use mentat_query_algebrizer::{ - Known, - algebrize, - parse_find_string, -}; +use mentat_query_algebrizer::{algebrize, parse_find_string, Known}; -use mentat_query_projector::{ - query_projection, -}; +use mentat_query_projector::query_projection; // These are helpers that tests use to build Schema instances. 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", "age"), 68); associate_ident(&mut schema, Keyword::namespaced("foo", "height"), 69); - add_attribute(&mut schema, 65, Attribute { - value_type: ValueType::String, - multival: false, - ..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() - }); + add_attribute( + &mut schema, + 65, + Attribute { + value_type: ValueType::String, + multival: false, + ..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 } @@ -103,13 +101,11 @@ fn test_the_without_max_or_min() { // … when we look at the projection list, we cannot reconcile the types. let projection = query_projection(&schema, &algebrized); assert!(projection.is_err()); - use query_projector_traits::errors::{ - ProjectorError, - }; + use query_projector_traits::errors::ProjectorError; match projection.err().expect("expected failure") { 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!(), } } diff --git a/query-projector/Cargo.toml b/query-projector/Cargo.toml index c6335407..37975ec2 100644 --- a/query-projector/Cargo.toml +++ b/query-projector/Cargo.toml @@ -11,7 +11,7 @@ failure = "0.1.1" indexmap = "1" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.core_traits] diff --git a/query-projector/src/binding_tuple.rs b/query-projector/src/binding_tuple.rs index 140e2d51..a268fba2 100644 --- a/query-projector/src/binding_tuple.rs +++ b/query-projector/src/binding_tuple.rs @@ -8,14 +8,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - Binding, -}; +use core_traits::Binding; -use query_projector_traits::errors::{ - ProjectorError, - Result, -}; +use query_projector_traits::errors::{ProjectorError, Result}; /// A `BindingTuple` is any type that can accommodate a Mentat tuple query result of fixed length. /// @@ -32,11 +27,14 @@ impl BindingTuple for Vec { None => Ok(None), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { Ok(Some(vec)) } - }, + } } } } @@ -51,7 +49,10 @@ impl BindingTuple for (Binding,) { None => Ok(None), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { let mut iter = vec.into_iter(); Ok(Some((iter.next().unwrap(),))) @@ -70,7 +71,10 @@ impl BindingTuple for (Binding, Binding) { None => Ok(None), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { let mut iter = vec.into_iter(); Ok(Some((iter.next().unwrap(), iter.next().unwrap()))) @@ -89,10 +93,17 @@ impl BindingTuple for (Binding, Binding, Binding) { None => Ok(None), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { 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), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { 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), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { 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), Some(vec) => { if expected != vec.len() { - Err(ProjectorError::UnexpectedResultsTupleLength(expected, vec.len())) + Err(ProjectorError::UnexpectedResultsTupleLength( + expected, + vec.len(), + )) } else { 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(), + ))) } } } diff --git a/query-projector/src/lib.rs b/query-projector/src/lib.rs index 70b47a9b..95bb21d5 100644 --- a/query-projector/src/lib.rs +++ b/query-projector/src/lib.rs @@ -13,111 +13,63 @@ extern crate failure; extern crate indexmap; extern crate rusqlite; +extern crate db_traits; extern crate edn; extern crate mentat_core; -extern crate db_traits; #[macro_use] 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_pull; -extern crate query_pull_traits; -extern crate query_projector_traits; extern crate mentat_query_sql; +extern crate query_projector_traits; +extern crate query_pull_traits; -use std::collections::{ - BTreeSet, -}; +use std::collections::BTreeSet; use std::iter; use std::rc::Rc; -use rusqlite::{ - Row, - Rows, -}; +use rusqlite::{Row, Rows}; -use core_traits::{ - Binding, - TypedValue, -}; +use core_traits::{Binding, TypedValue}; -use mentat_core::{ - Schema, - ValueTypeTag, -}; +use mentat_core::{Schema, ValueTypeTag}; -use mentat_core::util::{ - Either, -}; +use mentat_core::util::Either; -use mentat_db::{ - TypedSQLValue, -}; +use mentat_db::TypedSQLValue; -use edn::query::{ - Element, - FindSpec, - Limit, - Variable, -}; +use edn::query::{Element, FindSpec, Limit, Variable}; -use mentat_query_algebrizer::{ - AlgebraicQuery, - VariableBindings, -}; +use mentat_query_algebrizer::{AlgebraicQuery, VariableBindings}; -use mentat_query_sql::{ - GroupBy, - Projection, -}; +use mentat_query_sql::{GroupBy, Projection}; pub mod translate; mod binding_tuple; -pub use binding_tuple::{ - BindingTuple, -}; +pub use binding_tuple::BindingTuple; mod project; mod projectors; mod pull; mod relresult; -use project::{ - ProjectedElements, - project_elements, -}; +use project::{project_elements, ProjectedElements}; -pub use project::{ - projected_column_for_var, -}; +pub use project::projected_column_for_var; -pub use projectors::{ - ConstantProjector, - Projector, -}; +pub use projectors::{ConstantProjector, Projector}; use projectors::{ - CollProjector, - CollTwoStagePullProjector, - RelProjector, - RelTwoStagePullProjector, - ScalarProjector, - ScalarTwoStagePullProjector, - TupleProjector, - TupleTwoStagePullProjector, + CollProjector, CollTwoStagePullProjector, RelProjector, RelTwoStagePullProjector, + ScalarProjector, ScalarTwoStagePullProjector, TupleProjector, TupleTwoStagePullProjector, }; -pub use relresult::{ - RelResult, - StructuredRelResult, -}; +pub use relresult::{RelResult, StructuredRelResult}; -use query_projector_traits::errors::{ - ProjectorError, - Result, -}; +use query_projector_traits::errors::{ProjectorError, Result}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct QueryOutput { @@ -140,16 +92,16 @@ impl From for QueryResults { } impl QueryOutput { - pub fn empty_factory(spec: &FindSpec) -> Box QueryResults> { + pub fn empty_factory(spec: &FindSpec) -> Box QueryResults> { use self::FindSpec::*; match spec { - &FindScalar(_) => Box::new(|| QueryResults::Scalar(None)), - &FindTuple(_) => Box::new(|| QueryResults::Tuple(None)), - &FindColl(_) => Box::new(|| QueryResults::Coll(vec![])), + &FindScalar(_) => Box::new(|| QueryResults::Scalar(None)), + &FindTuple(_) => Box::new(|| QueryResults::Tuple(None)), + &FindColl(_) => Box::new(|| QueryResults::Coll(vec![])), &FindRel(ref es) => { let width = es.len(); Box::new(move || QueryResults::Rel(RelResult::empty(width))) - }, + } } } @@ -163,13 +115,12 @@ impl QueryOutput { pub fn empty(spec: &Rc) -> QueryOutput { use self::FindSpec::*; - let results = - match &**spec { - &FindScalar(_) => QueryResults::Scalar(None), - &FindTuple(_) => QueryResults::Tuple(None), - &FindColl(_) => QueryResults::Coll(vec![]), - &FindRel(ref es) => QueryResults::Rel(RelResult::empty(es.len())), - }; + let results = match &**spec { + &FindScalar(_) => QueryResults::Scalar(None), + &FindTuple(_) => QueryResults::Tuple(None), + &FindColl(_) => QueryResults::Coll(vec![]), + &FindRel(ref es) => QueryResults::Rel(RelResult::empty(es.len())), + }; QueryOutput { spec: spec.clone(), results: results, @@ -179,85 +130,83 @@ impl QueryOutput { pub fn from_constants(spec: &Rc, bindings: VariableBindings) -> QueryResults { use self::FindSpec::*; match &**spec { - &FindScalar(Element::Variable(ref var)) | - &FindScalar(Element::Corresponding(ref var)) => { - let val = bindings.get(var) - .cloned() - .map(|v| v.into()); + &FindScalar(Element::Variable(ref var)) + | &FindScalar(Element::Corresponding(ref var)) => { + let val = bindings.get(var).cloned().map(|v| v.into()); QueryResults::Scalar(val) - }, + } &FindScalar(Element::Aggregate(ref _agg)) => { // TODO: static aggregates. unimplemented!(); - }, + } &FindScalar(Element::Pull(ref _pull)) => { // TODO: static pull. unimplemented!(); - }, + } &FindTuple(ref elements) => { - let values = elements.iter() - .map(|e| match e { - &Element::Variable(ref var) | - &Element::Corresponding(ref var) => { - bindings.get(var) - .cloned() - .expect("every var to have a binding") - .into() - }, - &Element::Pull(ref _pull) => { - // TODO: static pull. - unreachable!(); - }, - &Element::Aggregate(ref _agg) => { - // TODO: static computation of aggregates, then - // implement the condition in `is_fully_bound`. - unreachable!(); - }, - }) - .collect(); + let values = elements + .iter() + .map(|e| match e { + &Element::Variable(ref var) | &Element::Corresponding(ref var) => bindings + .get(var) + .cloned() + .expect("every var to have a binding") + .into(), + &Element::Pull(ref _pull) => { + // TODO: static pull. + unreachable!(); + } + &Element::Aggregate(ref _agg) => { + // TODO: static computation of aggregates, then + // implement the condition in `is_fully_bound`. + unreachable!(); + } + }) + .collect(); QueryResults::Tuple(Some(values)) - }, - &FindColl(Element::Variable(ref var)) | - &FindColl(Element::Corresponding(ref var)) => { - let val = bindings.get(var) - .cloned() - .expect("every var to have a binding") - .into(); + } + &FindColl(Element::Variable(ref var)) | &FindColl(Element::Corresponding(ref var)) => { + let val = bindings + .get(var) + .cloned() + .expect("every var to have a binding") + .into(); QueryResults::Coll(vec![val]) - }, + } &FindColl(Element::Pull(ref _pull)) => { // TODO: static pull. unimplemented!(); - }, + } &FindColl(Element::Aggregate(ref _agg)) => { // Does it even make sense to write // [:find [(max ?x) ...] :where [_ :foo/bar ?x]] // ? // TODO unimplemented!(); - }, + } &FindRel(ref elements) => { let width = elements.len(); - let values = elements.iter().map(|e| match e { - &Element::Variable(ref var) | - &Element::Corresponding(ref var) => { - bindings.get(var) - .cloned() - .expect("every var to have a binding") - .into() - }, - &Element::Pull(ref _pull) => { - // TODO: static pull. - unreachable!(); - }, - &Element::Aggregate(ref _agg) => { - // TODO: static computation of aggregates, then - // implement the condition in `is_fully_bound`. - unreachable!(); - }, - }).collect(); + let values = elements + .iter() + .map(|e| match e { + &Element::Variable(ref var) | &Element::Corresponding(ref var) => bindings + .get(var) + .cloned() + .expect("every var to have a binding") + .into(), + &Element::Pull(ref _pull) => { + // TODO: static pull. + unreachable!(); + } + &Element::Aggregate(ref _agg) => { + // TODO: static computation of aggregates, then + // implement the condition in `is_fully_bound`. + unreachable!(); + } + }) + .collect(); QueryResults::Rel(RelResult { width, values }) - }, + } } } @@ -275,9 +224,14 @@ impl QueryOutput { /// /// This is the moral equivalent of `collect` (and `BindingTuple` of `FromIterator`), but /// specialized to tuples of expected length. - pub fn into_tuple(self) -> Result> where B: BindingTuple { + pub fn into_tuple(self) -> Result> + where + B: BindingTuple, + { 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> { @@ -289,10 +243,22 @@ impl QueryResults { pub fn len(&self) -> usize { use QueryResults::*; match self { - &Scalar(ref o) => if o.is_some() { 1 } else { 0 }, - &Tuple(ref o) => if o.is_some() { 1 } else { 0 }, - &Coll(ref v) => v.len(), - &Rel(ref r) => r.row_count(), + &Scalar(ref o) => { + if o.is_some() { + 1 + } 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::*; match self { &Scalar(ref o) => o.is_none(), - &Tuple(ref o) => o.is_none(), - &Coll(ref v) => v.is_empty(), - &Rel(ref r) => r.is_empty(), + &Tuple(ref o) => o.is_none(), + &Coll(ref v) => v.is_empty(), + &Rel(ref r) => r.is_empty(), } } @@ -310,14 +276,18 @@ impl QueryResults { match self { QueryResults::Scalar(o) => Ok(o), 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")), } } pub fn into_coll(self) -> Result> { match self { - QueryResults::Scalar(_) => bail!(ProjectorError::UnexpectedResultsType("scalar", "coll")), + QueryResults::Scalar(_) => { + bail!(ProjectorError::UnexpectedResultsType("scalar", "coll")) + } QueryResults::Coll(c) => Ok(c), QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "coll")), QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "coll")), @@ -326,7 +296,9 @@ impl QueryResults { pub fn into_tuple(self) -> Result>> { match self { - QueryResults::Scalar(_) => bail!(ProjectorError::UnexpectedResultsType("scalar", "tuple")), + QueryResults::Scalar(_) => { + bail!(ProjectorError::UnexpectedResultsType("scalar", "tuple")) + } QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "tuple")), QueryResults::Tuple(t) => Ok(t), QueryResults::Rel(_) => bail!(ProjectorError::UnexpectedResultsType("rel", "tuple")), @@ -335,7 +307,9 @@ impl QueryResults { pub fn into_rel(self) -> Result> { match self { - QueryResults::Scalar(_) => bail!(ProjectorError::UnexpectedResultsType("scalar", "rel")), + QueryResults::Scalar(_) => { + bail!(ProjectorError::UnexpectedResultsType("scalar", "rel")) + } QueryResults::Coll(_) => bail!(ProjectorError::UnexpectedResultsType("coll", "rel")), QueryResults::Tuple(_) => bail!(ProjectorError::UnexpectedResultsType("tuple", "rel")), QueryResults::Rel(r) => Ok(r), @@ -343,7 +317,7 @@ impl QueryResults { } } -type Index = usize; // See rusqlite::RowIndex. +type Index = usize; // See rusqlite::RowIndex. enum TypedIndex { Known(Index, ValueTypeTag), Unknown(Index, Index), @@ -373,19 +347,18 @@ impl TypedIndex { TypedValue::from_sql_value_pair(v, value_type) .map(|v| v.into()) .map_err(|e| e.into()) - }, + } &Unknown(value_index, type_index) => { let v: rusqlite::types::Value = row.get(value_index).unwrap(); let value_type_tag: i32 = row.get(type_index).unwrap(); TypedValue::from_sql_value_pair(v, value_type_tag) .map(|v| v.into()) .map_err(|e| e.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 /// 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 /// the SQL projection) to yield one of the four kinds of Datalog query result. - pub datalog_projector: Box, + pub datalog_projector: Box, /// True if this query requires the SQL query to include DISTINCT. pub distinct: bool, @@ -445,35 +418,38 @@ impl IsPull for Element { /// - The bindings established by the topmost CC. /// - The types known at algebrizing time. /// - The types extracted from the store for unknown attributes. -pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result> { +pub fn query_projection( + schema: &Schema, + query: &AlgebraicQuery, +) -> Result> { use self::FindSpec::*; let spec = query.find_spec.clone(); if query.is_fully_unit_bound() { // Do a few gyrations to produce empty results of the right kind for the query. - let variables: BTreeSet = spec.columns() - .map(|e| match e { - &Element::Variable(ref var) | - &Element::Corresponding(ref var) => var.clone(), + let variables: BTreeSet = spec + .columns() + .map(|e| match e { + &Element::Variable(ref var) | &Element::Corresponding(ref var) => var.clone(), - // Pull expressions can never be fully bound. - // TODO: but the interior can be, in which case we - // can handle this and simply project. - &Element::Pull(_) => { - unreachable!(); - }, - &Element::Aggregate(ref _agg) => { - // TODO: static computation of aggregates, then - // implement the condition in `is_fully_bound`. - unreachable!(); - }, - }) - .collect(); + // Pull expressions can never be fully bound. + // TODO: but the interior can be, in which case we + // can handle this and simply project. + &Element::Pull(_) => { + unreachable!(); + } + &Element::Aggregate(ref _agg) => { + // TODO: static computation of aggregates, then + // implement the condition in `is_fully_bound`. + unreachable!(); + } + }) + .collect(); // TODO: error handling let results = QueryOutput::from_constants(&spec, query.cc.value_bindings(&variables)); - let f = Box::new(move || { results.clone() }); + let f = Box::new(move || results.clone()); Ok(Either::Left(ConstantProjector::new(spec, f))) } else if query.is_known_empty() { @@ -488,8 +464,9 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result { let elements = project_elements(1, iter::once(element), query)?; @@ -498,7 +475,7 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result { let is_pull = elements.iter().any(|e| e.is_pull()); @@ -508,8 +485,9 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result { let is_pull = elements.iter().any(|e| e.is_pull()); @@ -520,41 +498,51 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result { assert_eq!((expected, got), (3, 2)); - }, + } // This forces the result type. Ok(Some((_, _, _))) | _ => panic!("expected error"), } let query_output = QueryOutput { - spec: Rc::new(FindSpec::FindTuple(vec![Element::Variable(Variable::from_valid_name("?x")), - Element::Variable(Variable::from_valid_name("?y"))])), + spec: Rc::new(FindSpec::FindTuple(vec![ + Element::Variable(Variable::from_valid_name("?x")), + Element::Variable(Variable::from_valid_name("?y")), + ])), results: QueryResults::Tuple(None), }; - match query_output.clone().into_tuple() { - Ok(None) => {}, + Ok(None) => {} // This forces the result type. Ok(Some((_, _))) | _ => panic!("expected error"), } @@ -562,7 +550,7 @@ fn test_into_tuple() { match query_output.clone().into_tuple() { Err(ProjectorError::UnexpectedResultsTupleLength(expected, got)) => { assert_eq!((expected, got), (3, 2)); - }, + } // This forces the result type. Ok(Some((_, _, _))) | _ => panic!("expected error"), } diff --git a/query-projector/src/project.rs b/query-projector/src/project.rs index 0074b863..38d4444d 100644 --- a/query-projector/src/project.rs +++ b/query-projector/src/project.rs @@ -8,74 +8,35 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::collections::{ - BTreeSet, -}; +use std::collections::BTreeSet; -use indexmap::{ - IndexSet, -}; +use indexmap::IndexSet; -use core_traits::{ - ValueTypeSet, -}; +use core_traits::ValueTypeSet; -use mentat_core::{ - SQLValueType, - SQLValueTypeSet, -}; +use mentat_core::{SQLValueType, SQLValueTypeSet}; -use mentat_core::util::{ - Either, -}; +use mentat_core::util::Either; -use edn::query::{ - Element, - Pull, - Variable, -}; +use edn::query::{Element, Pull, Variable}; use mentat_query_algebrizer::{ - AlgebraicQuery, - ColumnName, - ConjoiningClauses, - QualifiedAlias, - VariableColumn, + AlgebraicQuery, ColumnName, ConjoiningClauses, QualifiedAlias, VariableColumn, }; - -use mentat_query_sql::{ - ColumnOrExpression, - GroupBy, - Name, - Projection, - ProjectedColumn, -}; +use mentat_query_sql::{ColumnOrExpression, GroupBy, Name, ProjectedColumn, Projection}; use query_projector_traits::aggregates::{ - SimpleAggregation, - projected_column_for_simple_aggregate, + projected_column_for_simple_aggregate, SimpleAggregation, }; -use query_projector_traits::errors::{ - ProjectorError, - Result, -}; +use query_projector_traits::errors::{ProjectorError, Result}; -use projectors::{ - Projector, -}; +use projectors::Projector; -use pull::{ - PullIndices, - PullOperation, - PullTemplate, -}; +use pull::{PullIndices, PullOperation, PullTemplate}; -use super::{ - CombinedProjection, - TypedIndex, -}; +use super::{CombinedProjection, TypedIndex}; /// An internal temporary struct to pass between the projection 'walk' and the /// resultant projector. @@ -97,7 +58,11 @@ pub(crate) struct ProjectedElements { } impl ProjectedElements { - pub(crate) fn combine(self, projector: Box, distinct: bool) -> Result { + pub(crate) fn combine( + self, + projector: Box, + distinct: bool, + ) -> Result { Ok(CombinedProjection { sql_projection: self.sql_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 - .get(var) - .cloned() - .map(|alias| { - let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name(); - (ColumnOrExpression::Column(alias), type_name) - }) - .ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into()) + .get(var) + .cloned() + .map(|alias| { + let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name(); + (ColumnOrExpression::Column(alias), type_name) + }) + .ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into()) } fn cc_column(cc: &ConjoiningClauses, var: &Variable) -> Result { cc.column_bindings - .get(var) - .and_then(|cols| cols.get(0).cloned()) - .ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into()) + .get(var) + .and_then(|cols| cols.get(0).cloned()) + .ok_or_else(|| ProjectorError::UnboundVariable(var.name()).into()) } fn candidate_column(cc: &ConjoiningClauses, var: &Variable) -> Result<(ColumnOrExpression, Name)> { // 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 // bug in our code, so it's appropriate to panic here. - cc_column(cc, var) - .map(|qa| { - let name = VariableColumn::Variable(var.clone()).column_name(); - (ColumnOrExpression::Column(qa), name) - }) + cc_column(cc, var).map(|qa| { + let name = VariableColumn::Variable(var.clone()).column_name(); + (ColumnOrExpression::Column(qa), name) + }) } /// Return the projected column -- that is, a value or SQL column and an associated name -- for a /// given variable. Also return the type. /// 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 we already know the value, then our lives are easy. let tag = value.value_type(); 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 { // If we don't, then the CC *must* have bound the variable. 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>( count: usize, elements: I, - query: &AlgebraicQuery) -> Result { - + query: &AlgebraicQuery, +) -> Result { // Give a little padding for type tags. let mut inner_projection = Vec::with_capacity(count + 2); @@ -214,24 +187,34 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( match e { &Element::Variable(ref 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) { - 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) => { 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) { - bail!(ProjectorError::InvalidProjection(format!("`(the {})` appears twice in query.", var))); + bail!(ProjectorError::InvalidProjection(format!( + "`(the {})` appears twice in query.", + var + ))); } - }, - &Element::Aggregate(_) => { - }, - &Element::Pull(_) => { - }, + } + &Element::Aggregate(_) => {} + &Element::Pull(_) => {} }; // 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>( match e { &Element::Variable(ref var) => { outer_variables.insert(var.clone()); - }, + } &Element::Corresponding(ref var) => { // We will project these later; don't put them in `outer_variables` // so we know not to group them. 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, // and we expand it later. outer_variables.insert(var.clone()); - }, - &Element::Aggregate(_) => { - }, + } + &Element::Aggregate(_) => {} }; // Now do the main processing of each element. @@ -259,8 +244,7 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( // Each time we come across a variable, we push a SQL column // into the SQL projection, aliased to the name of the variable, // and we push an annotated index into the projector. - &Element::Variable(ref var) | - &Element::Corresponding(ref var) => { + &Element::Variable(ref var) | &Element::Corresponding(ref var) => { inner_variables.insert(var.clone()); let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?; @@ -269,18 +253,21 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( if let Some(tag) = type_set.unique_type_tag() { templates.push(TypedIndex::Known(i, tag)); - i += 1; // We used one SQL column. + i += 1; // We used one SQL column. } else { 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. let (type_column, type_name) = candidate_type_column(&query.cc, &var)?; inner_projection.push(ProjectedColumn(type_column, type_name.clone())); 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()); let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?; @@ -303,12 +290,12 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( }, op: PullOperation((*patterns).clone()), }); - i += 1; // We used one SQL column. + i += 1; // We used one SQL column. } else { // This should be impossible: (pull ?x) implies that ?x is a ref. unreachable!(); } - }, + } &Element::Aggregate(ref a) => { if let Some(simple) = a.to_simple() { aggregates = true; @@ -317,7 +304,7 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( match simple.op { Max | Min => { min_max_count += 1; - }, + } Avg | Count | Sum => (), } @@ -330,16 +317,24 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( // - The type set must be appropriate for the operation. E.g., `Sum` is not a // 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)); if !inner_variables.contains(&simple.var) { 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); - 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. - 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())); } } @@ -349,23 +344,27 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( i += 1; } else { // TODO: complex aggregates. - bail!(ProjectorError::NotYetImplemented("complex aggregates".into())); + bail!(ProjectorError::NotYetImplemented( + "complex aggregates".into() + )); } - }, + } } } match (min_max_count, corresponded_variables.len()) { - (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, _) => { // This is the success case! - }, + } (n, c) => { bail!(ProjectorError::AmbiguousAggregates(n, c)); - }, + } } // Anything used in ORDER BY (which we're given in `named_projection`) @@ -412,12 +411,12 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( if !aggregates { // We're done -- we never need to group unless we're aggregating. return Ok(ProjectedElements { - sql_projection: Projection::Columns(inner_projection), - pre_aggregate_projection: None, - templates, - pulls, - group_by: vec![], - }); + sql_projection: Projection::Columns(inner_projection), + pre_aggregate_projection: None, + templates, + pulls, + group_by: vec![], + }); } // OK, on to aggregates. @@ -439,7 +438,9 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( let mut group_by = Vec::with_capacity(outer_variables.len() + 2); 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) { if query.cc.is_value_bound(&var) { @@ -464,12 +465,13 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( if needs_type_projection { let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name(); if !already_inner { - let type_col = query.cc - .extracted_types - .get(&var) - .cloned() - .ok_or_else(|| ProjectorError::NoTypeAvailableForVariable(var.name().clone()))?; - inner_projection.push(ProjectedColumn(ColumnOrExpression::Column(type_col), type_name.clone())); + let type_col = query.cc.extracted_types.get(&var).cloned().ok_or_else(|| { + ProjectorError::NoTypeAvailableForVariable(var.name().clone()) + })?; + inner_projection.push(ProjectedColumn( + ColumnOrExpression::Column(type_col), + type_name.clone(), + )); } if group { group_by.push(GroupBy::ProjectedColumn(type_name)); @@ -505,15 +507,15 @@ pub(crate) fn project_elements<'a, I: IntoIterator>( // `ground` and known values?) // Walk the projection, switching the outer columns to use the inner names. - let outer_projection = outer_projection.into_iter().map(|c| { - match c { + let outer_projection = outer_projection + .into_iter() + .map(|c| match c { Either::Left(name) => { - ProjectedColumn(ColumnOrExpression::ExistingColumn(name.clone()), - name) - }, + ProjectedColumn(ColumnOrExpression::ExistingColumn(name.clone()), name) + } Either::Right(pc) => pc, - } - }).collect(); + }) + .collect(); Ok(ProjectedElements { sql_projection: Projection::Columns(outer_projection), diff --git a/query-projector/src/projectors/constant.rs b/query-projector/src/projectors/constant.rs index 5e35be28..c9939400 100644 --- a/query-projector/src/projectors/constant.rs +++ b/query-projector/src/projectors/constant.rs @@ -10,33 +10,24 @@ use std::rc::Rc; -use ::{ - Element, - FindSpec, - QueryOutput, - QueryResults, - Rows, - Schema, - rusqlite, -}; +use {rusqlite, Element, FindSpec, QueryOutput, QueryResults, Rows, Schema}; -use query_projector_traits::errors::{ - Result, -}; +use query_projector_traits::errors::Result; -use super::{ - Projector, -}; +use super::Projector; /// A projector that produces a `QueryResult` containing fixed data. /// Takes a boxed function that should return an empty result set of the desired type. pub struct ConstantProjector { spec: Rc, - results_factory: Box QueryResults>, + results_factory: Box QueryResults>, } impl ConstantProjector { - pub fn new(spec: Rc, results_factory: Box QueryResults>) -> ConstantProjector { + pub fn new( + spec: Rc, + results_factory: Box QueryResults>, + ) -> ConstantProjector { ConstantProjector { spec: spec, results_factory: results_factory, @@ -56,11 +47,16 @@ impl ConstantProjector { // TODO: a ConstantProjector with non-constant pull expressions. impl Projector for ConstantProjector { - fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, _rows: Rows<'stmt>) -> Result { + fn project<'stmt, 's>( + &self, + _schema: &Schema, + _sqlite: &'s rusqlite::Connection, + _rows: Rows<'stmt>, + ) -> Result { self.project_without_rows() } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } diff --git a/query-projector/src/projectors/mod.rs b/query-projector/src/projectors/mod.rs index d3b47092..84290146 100644 --- a/query-projector/src/projectors/mod.rs +++ b/query-projector/src/projectors/mod.rs @@ -8,39 +8,29 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use super::{ - Element, - Schema, - QueryOutput, - Rows, - rusqlite, -}; +use super::{rusqlite, Element, QueryOutput, Rows, Schema}; -use query_projector_traits::errors::{ - Result, -}; +use query_projector_traits::errors::Result; pub trait Projector { - fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, rows: Rows<'stmt>) -> Result; - fn columns<'s>(&'s self) -> Box + 's>; + fn project<'stmt, 's>( + &self, + schema: &Schema, + sqlite: &'s rusqlite::Connection, + rows: Rows<'stmt>, + ) -> Result; + fn columns<'s>(&'s self) -> Box + 's>; } mod constant; -mod simple; mod pull_two_stage; +mod simple; pub use self::constant::ConstantProjector; -pub(crate) use self::simple::{ - CollProjector, - RelProjector, - ScalarProjector, - TupleProjector, -}; +pub(crate) use self::simple::{CollProjector, RelProjector, ScalarProjector, TupleProjector}; pub(crate) use self::pull_two_stage::{ - CollTwoStagePullProjector, - RelTwoStagePullProjector, - ScalarTwoStagePullProjector, + CollTwoStagePullProjector, RelTwoStagePullProjector, ScalarTwoStagePullProjector, TupleTwoStagePullProjector, }; diff --git a/query-projector/src/projectors/pull_two_stage.rs b/query-projector/src/projectors/pull_two_stage.rs index e03d56d8..a845761e 100644 --- a/query-projector/src/projectors/pull_two_stage.rs +++ b/query-projector/src/projectors/pull_two_stage.rs @@ -10,47 +10,22 @@ use std::rc::Rc; -use std::iter::{ - once, +use std::iter::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::{ - Puller, -}; +use pull::{PullConsumer, PullOperation, PullTemplate}; -use core_traits::{ - Entid, -}; +use query_projector_traits::errors::Result; -use ::{ - 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, -}; +use super::Projector; pub(crate) struct ScalarTwoStagePullProjector { spec: Rc, @@ -61,34 +36,53 @@ pub(crate) struct ScalarTwoStagePullProjector { // The only output is the pull expression, and so we can directly supply the projected entity // to the pull SQL. impl ScalarTwoStagePullProjector { - fn with_template(schema: &Schema, spec: Rc, pull: PullOperation) -> Result { + fn with_template( + schema: &Schema, + spec: Rc, + pull: PullOperation, + ) -> Result { Ok(ScalarTwoStagePullProjector { spec: spec, puller: Puller::prepare(schema, pull.0.clone())?, }) } - pub(crate) fn combine(schema: &Schema, spec: Rc, mut elements: ProjectedElements) -> Result { + pub(crate) fn combine( + schema: &Schema, + spec: Rc, + mut elements: ProjectedElements, + ) -> Result { 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; elements.combine(projector, distinct) } } impl Projector for ScalarTwoStagePullProjector { - fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { + fn project<'stmt, 's>( + &self, + schema: &Schema, + sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { // Scalar is pretty straightforward -- zero or one entity, do the pull directly. - let results = - if let Some(r) = rows.next().unwrap() { - let row = r; - let entity: Entid = row.get(0).unwrap(); // This will always be 0 and a ref. - let bindings = self.puller.pull(schema, sqlite, once(entity))?; - let m = Binding::Map(bindings.get(&entity).cloned().unwrap_or_else(Default::default)); - QueryResults::Scalar(Some(m)) - } else { - QueryResults::Scalar(None) - }; + let results = if let Some(r) = rows.next().unwrap() { + let row = r; + let entity: Entid = row.get(0).unwrap(); // This will always be 0 and a ref. + let bindings = self.puller.pull(schema, sqlite, once(entity))?; + let m = Binding::Map( + bindings + .get(&entity) + .cloned() + .unwrap_or_else(Default::default), + ); + QueryResults::Scalar(Some(m)) + } else { + QueryResults::Scalar(None) + }; Ok(QueryOutput { spec: self.spec.clone(), @@ -96,7 +90,7 @@ impl Projector for ScalarTwoStagePullProjector { }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } @@ -110,7 +104,12 @@ pub(crate) struct TupleTwoStagePullProjector { } impl TupleTwoStagePullProjector { - fn with_templates(spec: Rc, len: usize, templates: Vec, pulls: Vec) -> TupleTwoStagePullProjector { + fn with_templates( + spec: Rc, + len: usize, + templates: Vec, + pulls: Vec, + ) -> TupleTwoStagePullProjector { TupleTwoStagePullProjector { spec: spec, len: len, @@ -131,54 +130,68 @@ impl TupleTwoStagePullProjector { .collect::>>() } - pub(crate) fn combine(spec: Rc, column_count: usize, mut elements: ProjectedElements) -> Result { - let projector = Box::new(TupleTwoStagePullProjector::with_templates(spec, column_count, elements.take_templates(), elements.take_pulls())); + pub(crate) fn combine( + spec: Rc, + column_count: usize, + mut elements: ProjectedElements, + ) -> Result { + let projector = Box::new(TupleTwoStagePullProjector::with_templates( + spec, + column_count, + elements.take_templates(), + elements.take_pulls(), + )); let distinct = false; elements.combine(projector, distinct) } } impl Projector for TupleTwoStagePullProjector { - fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { - let results = - if let Some(r) = rows.next().unwrap() { - let row = r; + fn project<'stmt, 's>( + &self, + schema: &Schema, + sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { + let results = if let Some(r) = rows.next().unwrap() { + let row = r; - // Keeping the compiler happy. - let pull_consumers: Result> = self.pulls - .iter() - .map(|op| PullConsumer::for_template(schema, op)) - .collect(); - let mut pull_consumers = pull_consumers?; + // Keeping the compiler happy. + let pull_consumers: Result> = self + .pulls + .iter() + .map(|op| PullConsumer::for_template(schema, op)) + .collect(); + let mut pull_consumers = pull_consumers?; - // Collect the usual bindings and accumulate entity IDs for pull. - for p in pull_consumers.iter_mut() { - p.collect_entity(&row); - } + // Collect the usual bindings and accumulate entity IDs for pull. + for p in pull_consumers.iter_mut() { + 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. - for p in pull_consumers.iter_mut() { - p.pull(sqlite)?; - } + // Run the pull expressions for the collected IDs. + for p in pull_consumers.iter_mut() { + p.pull(sqlite)?; + } - // Expand the pull expressions back into the results vector. - for p in pull_consumers.into_iter() { - p.expand(&mut bindings); - } + // Expand the pull expressions back into the results vector. + for p in pull_consumers.into_iter() { + p.expand(&mut bindings); + } - QueryResults::Tuple(Some(bindings)) - } else { - QueryResults::Tuple(None) - }; + QueryResults::Tuple(Some(bindings)) + } else { + QueryResults::Tuple(None) + }; Ok(QueryOutput { spec: self.spec.clone(), results: results, }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } @@ -195,7 +208,12 @@ pub(crate) struct RelTwoStagePullProjector { } impl RelTwoStagePullProjector { - fn with_templates(spec: Rc, len: usize, templates: Vec, pulls: Vec) -> RelTwoStagePullProjector { + fn with_templates( + spec: Rc, + len: usize, + templates: Vec, + pulls: Vec, + ) -> RelTwoStagePullProjector { RelTwoStagePullProjector { spec: spec, len: len, @@ -210,9 +228,7 @@ impl RelTwoStagePullProjector { // The templates will take care of ignoring columns. assert!(row.column_count() >= self.len); let mut count = 0; - for binding in self.templates - .iter() - .map(|ti| ti.lookup(&row)) { + for binding in self.templates.iter().map(|ti| ti.lookup(&row)) { out.push(binding?); count += 1; } @@ -220,32 +236,47 @@ impl RelTwoStagePullProjector { Ok(()) } - pub(crate) fn combine(spec: Rc, column_count: usize, mut elements: ProjectedElements) -> Result { - let projector = Box::new(RelTwoStagePullProjector::with_templates(spec, column_count, elements.take_templates(), elements.take_pulls())); + pub(crate) fn combine( + spec: Rc, + column_count: usize, + mut elements: ProjectedElements, + ) -> Result { + 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 // (because by definition every column in an aggregate query is either // aggregated or is a variable _upon which we group_), then don't bother // with DISTINCT. - let already_distinct = elements.pre_aggregate_projection.is_some() || - projector.columns().all(|e| e.is_unit()); + let already_distinct = + elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit()); elements.combine(projector, !already_distinct) } } impl Projector for RelTwoStagePullProjector { - fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { + fn project<'stmt, 's>( + &self, + schema: &Schema, + sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { // Allocate space for five rows to start. // This is better than starting off by doubling the buffer a couple of times, and will // rapidly grow to support larger query results. let width = self.len; let mut values: Vec<_> = Vec::with_capacity(5 * width); - let pull_consumers: Result> = self.pulls - .iter() - .map(|op| PullConsumer::for_template(schema, op)) - .collect(); + let pull_consumers: Result> = self + .pulls + .iter() + .map(|op| PullConsumer::for_template(schema, op)) + .collect(); let mut pull_consumers = pull_consumers?; // Collect the usual bindings and accumulate entity IDs for pull. @@ -267,7 +298,7 @@ impl Projector for RelTwoStagePullProjector { for p in pull_consumers.iter() { p.expand(bindings); } - }; + } Ok(QueryOutput { spec: self.spec.clone(), @@ -275,7 +306,7 @@ impl Projector for RelTwoStagePullProjector { }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } @@ -295,20 +326,28 @@ impl CollTwoStagePullProjector { } } - pub(crate) fn combine(spec: Rc, mut elements: ProjectedElements) -> Result { + pub(crate) fn combine( + spec: Rc, + mut elements: ProjectedElements, + ) -> Result { let pull = elements.pulls.pop().expect("Expected a single pull"); let projector = Box::new(CollTwoStagePullProjector::with_pull(spec, pull.op)); // 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. - let already_distinct = elements.pre_aggregate_projection.is_some() || - projector.columns().all(|e| e.is_unit()); + let already_distinct = + elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit()); elements.combine(projector, !already_distinct) } } impl Projector for CollTwoStagePullProjector { - fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { + fn project<'stmt, 's>( + &self, + schema: &Schema, + sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { let mut pull_consumer = PullConsumer::for_operation(schema, &self.pull)?; while let Some(r) = rows.next().unwrap() { @@ -328,8 +367,7 @@ impl Projector for CollTwoStagePullProjector { }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } - diff --git a/query-projector/src/projectors/simple.rs b/query-projector/src/projectors/simple.rs index deee8904..c3a2dcd9 100644 --- a/query-projector/src/projectors/simple.rs +++ b/query-projector/src/projectors/simple.rs @@ -10,29 +10,14 @@ use std::rc::Rc; -use ::{ - Binding, - CombinedProjection, - Element, - FindSpec, - ProjectedElements, - QueryOutput, - QueryResults, - RelResult, - Row, - Rows, - Schema, - TypedIndex, - rusqlite, +use { + rusqlite, Binding, CombinedProjection, Element, FindSpec, ProjectedElements, QueryOutput, + QueryResults, RelResult, Row, Rows, Schema, TypedIndex, }; -use query_projector_traits::errors::{ - Result, -}; +use query_projector_traits::errors::Result; -use super::{ - Projector, -}; +use super::Projector; pub(crate) struct ScalarProjector { spec: Rc, @@ -47,8 +32,14 @@ impl ScalarProjector { } } - pub(crate) fn combine(spec: Rc, mut elements: ProjectedElements) -> Result { - let template = elements.templates.pop().expect("Expected a single template"); + pub(crate) fn combine( + spec: Rc, + mut elements: ProjectedElements, + ) -> Result { + let template = elements + .templates + .pop() + .expect("Expected a single template"); let projector = Box::new(ScalarProjector::with_template(spec, template)); let distinct = false; elements.combine(projector, distinct) @@ -56,22 +47,26 @@ impl ScalarProjector { } impl Projector for ScalarProjector { - fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { - let results = - if let Some(r) = rows.next().unwrap() { - let row = r; - let binding = self.template.lookup(&row)?; - QueryResults::Scalar(Some(binding)) - } else { - QueryResults::Scalar(None) - }; + fn project<'stmt, 's>( + &self, + _schema: &Schema, + _sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { + let results = if let Some(r) = rows.next().unwrap() { + let row = r; + let binding = self.template.lookup(&row)?; + QueryResults::Scalar(Some(binding)) + } else { + QueryResults::Scalar(None) + }; Ok(QueryOutput { spec: self.spec.clone(), results: results, }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } @@ -84,7 +79,11 @@ pub(crate) struct TupleProjector { } impl TupleProjector { - fn with_templates(spec: Rc, len: usize, templates: Vec) -> TupleProjector { + fn with_templates( + spec: Rc, + len: usize, + templates: Vec, + ) -> TupleProjector { TupleProjector { spec: spec, len: len, @@ -104,30 +103,42 @@ impl TupleProjector { .collect::>>() } - pub(crate) fn combine(spec: Rc, column_count: usize, mut elements: ProjectedElements) -> Result { - let projector = Box::new(TupleProjector::with_templates(spec, column_count, elements.take_templates())); + pub(crate) fn combine( + spec: Rc, + column_count: usize, + mut elements: ProjectedElements, + ) -> Result { + let projector = Box::new(TupleProjector::with_templates( + spec, + column_count, + elements.take_templates(), + )); let distinct = false; elements.combine(projector, distinct) } } impl Projector for TupleProjector { - fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { - let results = - if let Some(r) = rows.next().unwrap() { - let row = r; - let bindings = self.collect_bindings(row)?; - QueryResults::Tuple(Some(bindings)) - } else { - QueryResults::Tuple(None) - }; + fn project<'stmt, 's>( + &self, + _schema: &Schema, + _sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { + let results = if let Some(r) = rows.next().unwrap() { + let row = r; + let bindings = self.collect_bindings(row)?; + QueryResults::Tuple(Some(bindings)) + } else { + QueryResults::Tuple(None) + }; Ok(QueryOutput { spec: self.spec.clone(), results: results, }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } @@ -157,9 +168,7 @@ impl RelProjector { // The templates will take care of ignoring columns. assert!(row.column_count() >= self.len); let mut count = 0; - for binding in self.templates - .iter() - .map(|ti| ti.lookup(&row)) { + for binding in self.templates.iter().map(|ti| ti.lookup(&row)) { out.push(binding?); count += 1; } @@ -167,21 +176,34 @@ impl RelProjector { Ok(()) } - pub(crate) fn combine(spec: Rc, column_count: usize, mut elements: ProjectedElements) -> Result { - let projector = Box::new(RelProjector::with_templates(spec, column_count, elements.take_templates())); + pub(crate) fn combine( + spec: Rc, + column_count: usize, + mut elements: ProjectedElements, + ) -> Result { + 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 // (because by definition every column in an aggregate query is either // aggregated or is a variable _upon which we group_), then don't bother // with DISTINCT. - let already_distinct = elements.pre_aggregate_projection.is_some() || - projector.columns().all(|e| e.is_unit()); + let already_distinct = + elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit()); elements.combine(projector, !already_distinct) } } impl Projector for RelProjector { - fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { + fn project<'stmt, 's>( + &self, + _schema: &Schema, + _sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { // Allocate space for five rows to start. // This is better than starting off by doubling the buffer a couple of times, and will // rapidly grow to support larger query results. @@ -199,7 +221,7 @@ impl Projector for RelProjector { }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } @@ -219,22 +241,33 @@ impl CollProjector { } } - pub(crate) fn combine(spec: Rc, mut elements: ProjectedElements) -> Result { - let template = elements.templates.pop().expect("Expected a single template"); + pub(crate) fn combine( + spec: Rc, + mut elements: ProjectedElements, + ) -> Result { + let template = elements + .templates + .pop() + .expect("Expected a single template"); let projector = Box::new(CollProjector::with_template(spec, template)); // 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 // aggregated or is a variable _upon which we group_), then don't bother // with DISTINCT. - let already_distinct = elements.pre_aggregate_projection.is_some() || - projector.columns().all(|e| e.is_unit()); + let already_distinct = + elements.pre_aggregate_projection.is_some() || projector.columns().all(|e| e.is_unit()); elements.combine(projector, !already_distinct) } } impl Projector for CollProjector { - fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result { + fn project<'stmt, 's>( + &self, + _schema: &Schema, + _sqlite: &'s rusqlite::Connection, + mut rows: Rows<'stmt>, + ) -> Result { let mut out: Vec<_> = vec![]; while let Some(r) = rows.next().unwrap() { let row = r; @@ -247,7 +280,7 @@ impl Projector for CollProjector { }) } - fn columns<'s>(&'s self) -> Box + 's> { + fn columns<'s>(&'s self) -> Box + 's> { self.spec.columns() } } diff --git a/query-projector/src/pull.rs b/query-projector/src/pull.rs index e325ac62..fa8eae76 100644 --- a/query-projector/src/pull.rs +++ b/query-projector/src/pull.rs @@ -8,44 +8,26 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::collections::{ - BTreeMap, - BTreeSet, -}; +use std::collections::{BTreeMap, BTreeSet}; -use core_traits::{ - Binding, - Entid, - StructuredMap, - TypedValue, -}; +use core_traits::{Binding, Entid, StructuredMap, TypedValue}; -use mentat_core::{ - Schema, - ValueRc, -}; +use mentat_core::{Schema, ValueRc}; -use edn::query::{ - PullAttributeSpec, -}; +use edn::query::PullAttributeSpec; -use mentat_query_pull::{ - Puller, -}; +use mentat_query_pull::Puller; use query_projector_traits::errors::Result; -use super::{ - Index, - rusqlite, -}; +use super::{rusqlite, Index}; #[derive(Clone, Debug)] pub(crate) struct PullOperation(pub(crate) Vec); #[derive(Clone, Copy, Debug)] 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, } @@ -73,7 +55,11 @@ pub(crate) struct 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 { indices: indices, schema: schema, @@ -83,14 +69,24 @@ impl<'schema> PullConsumer<'schema> { } } - pub(crate) fn for_template(schema: &'schema Schema, template: &PullTemplate) -> Result> { + pub(crate) fn for_template( + schema: &'schema Schema, + template: &PullTemplate, + ) -> Result> { let puller = Puller::prepare(schema, template.op.0.clone())?; Ok(PullConsumer::for_puller(puller, schema, template.indices)) } - pub(crate) fn for_operation(schema: &'schema Schema, operation: &PullOperation) -> Result> { + pub(crate) fn for_operation( + schema: &'schema Schema, + operation: &PullOperation, + ) -> Result> { 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>(&mut self, row: &rusqlite::Row<'a>) -> Entid { @@ -110,13 +106,18 @@ impl<'schema> PullConsumer<'schema> { if let Some(pulled) = self.results.get(&id).cloned() { bindings[self.indices.output_index] = Binding::Map(pulled); } 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? pub(crate) fn into_coll_results(self) -> Vec { - self.results.values().cloned().map(|vrc| Binding::Map(vrc)).collect() + self.results + .values() + .cloned() + .map(|vrc| Binding::Map(vrc)) + .collect() } } diff --git a/query-projector/src/relresult.rs b/query-projector/src/relresult.rs index 58937221..95cf2de0 100644 --- a/query-projector/src/relresult.rs +++ b/query-projector/src/relresult.rs @@ -8,10 +8,7 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - Binding, - TypedValue, -}; +use core_traits::{Binding, TypedValue}; /// The result you get from a 'rel' query, like: /// @@ -80,8 +77,12 @@ fn test_rel_result() { }; let two_by_two = StructuredRelResult { width: 2, - values: vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into(), - TypedValue::Long(-2).into(), TypedValue::Boolean(false).into()], + values: vec![ + TypedValue::Long(5).into(), + TypedValue::Boolean(true).into(), + TypedValue::Long(-2).into(), + TypedValue::Boolean(false).into(), + ], }; assert!(empty.is_empty()); @@ -96,13 +97,40 @@ fn test_rel_result() { assert_eq!(unit.row(1), None); assert_eq!(two_by_two.row(2), None); - assert_eq!(unit.row(0), Some(vec![TypedValue::Long(5).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())); + assert_eq!( + unit.row(0), + Some(vec![TypedValue::Long(5).into()].as_slice()) + ); + assert_eq!( + two_by_two.row(0), + Some(vec![TypedValue::Long(5).into(), TypedValue::Boolean(true).into()].as_slice()) + ); + assert_eq!( + two_by_two.row(1), + Some( + vec![ + TypedValue::Long(-2).into(), + TypedValue::Boolean(false).into() + ] + .as_slice() + ) + ); let mut rr = two_by_two.rows(); - assert_eq!(rr.next(), Some(vec![TypedValue::Long(5).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(), + 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); } @@ -115,7 +143,10 @@ impl From>> for RelResult { let width = src.get(0).map(|r| r.len()).unwrap_or(0); RelResult { width: width, - values: src.into_iter().flat_map(|r| r.into_iter().map(|v| v.into())).collect(), + values: src + .into_iter() + .flat_map(|r| r.into_iter().map(|v| v.into())) + .collect(), } } } diff --git a/query-projector/src/translate.rs b/query-projector/src/translate.rs index f8ed87da..bc3cc102 100644 --- a/query-projector/src/translate.rs +++ b/query-projector/src/translate.rs @@ -8,67 +8,27 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use core_traits::{ - TypedValue, - ValueType, - ValueTypeSet, -}; +use core_traits::{TypedValue, ValueType, ValueTypeSet}; -use mentat_core::{ - Schema, - SQLTypeAffinity, - SQLValueType, - SQLValueTypeSet, - ValueTypeTag, -}; +use mentat_core::{SQLTypeAffinity, SQLValueType, SQLValueTypeSet, Schema, ValueTypeTag}; -use mentat_core::util::{ - Either, -}; +use mentat_core::util::Either; -use edn::query::{ - Limit, -}; +use edn::query::Limit; use mentat_query_algebrizer::{ - AlgebraicQuery, - ColumnAlternation, - ColumnConstraint, - ColumnConstraintOrAlternation, - ColumnIntersection, - ColumnName, - ComputedTable, - ConjoiningClauses, - DatomsColumn, - DatomsTable, - OrderBy, - QualifiedAlias, - QueryValue, - SourceAlias, - TableAlias, - VariableColumn, + AlgebraicQuery, ColumnAlternation, ColumnConstraint, ColumnConstraintOrAlternation, + ColumnIntersection, ColumnName, ComputedTable, ConjoiningClauses, DatomsColumn, DatomsTable, + OrderBy, QualifiedAlias, QueryValue, SourceAlias, TableAlias, VariableColumn, }; -use ::{ - CombinedProjection, - ConstantProjector, - Projector, - projected_column_for_var, - query_projection, +use { + projected_column_for_var, query_projection, CombinedProjection, ConstantProjector, Projector, }; use mentat_query_sql::{ - ColumnOrExpression, - Constraint, - FromClause, - GroupBy, - Op, - ProjectedColumn, - Projection, - SelectQuery, - TableList, - TableOrSubquery, - Values, + ColumnOrExpression, Constraint, FromClause, GroupBy, Op, ProjectedColumn, Projection, + SelectQuery, TableList, TableOrSubquery, Values, }; use std::collections::HashMap; @@ -92,7 +52,7 @@ impl ToColumn for QualifiedAlias { impl ToConstraint for ColumnIntersection { fn to_constraint(self) -> Constraint { 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 { fn to_constraint(self) -> Constraint { 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 { - ValueTypeSet::any().into_iter() - .filter(|t| t.value_type_tag() == tag) - .count() + ValueTypeSet::any() + .into_iter() + .filter(|t| t.value_type_tag() == tag) + .count() } -fn type_constraint(table: &TableAlias, tag: i32, to_check: Option>) -> Constraint { - let type_column = QualifiedAlias::new(table.clone(), - DatomsColumn::ValueTypeTag).to_column(); +fn type_constraint( + table: &TableAlias, + tag: i32, + to_check: Option>, +) -> Constraint { + let type_column = QualifiedAlias::new(table.clone(), DatomsColumn::ValueTypeTag).to_column(); let check_type_tag = Constraint::equal(type_column, ColumnOrExpression::Integer(tag)); if let Some(affinities) = to_check { let check_affinities = Constraint::Or { - constraints: affinities.into_iter().map(|affinity| { - Constraint::TypeCheck { - value: QualifiedAlias::new(table.clone(), - DatomsColumn::Value).to_column(), + constraints: affinities + .into_iter() + .map(|affinity| Constraint::TypeCheck { + value: QualifiedAlias::new(table.clone(), DatomsColumn::Value).to_column(), affinity, - } - }).collect() + }) + .collect(), }; Constraint::And { - constraints: vec![ - check_type_tag, - check_affinities - ] + constraints: vec![check_type_tag, check_affinities], } } else { check_type_tag @@ -164,17 +125,23 @@ impl ToConstraint for ColumnConstraint { fn to_constraint(self) -> Constraint { use self::ColumnConstraint::*; match self { - Equals(qa, QueryValue::Entid(entid)) => - Constraint::equal(qa.to_column(), ColumnOrExpression::Entid(entid)), + Equals(qa, QueryValue::Entid(entid)) => { + Constraint::equal(qa.to_column(), ColumnOrExpression::Entid(entid)) + } - Equals(qa, QueryValue::TypedValue(tv)) => - Constraint::equal(qa.to_column(), ColumnOrExpression::Value(tv)), + Equals(qa, QueryValue::TypedValue(tv)) => { + Constraint::equal(qa.to_column(), ColumnOrExpression::Value(tv)) + } - Equals(left, QueryValue::Column(right)) => - Constraint::equal(left.to_column(), right.to_column()), + Equals(left, QueryValue::Column(right)) => { + Constraint::equal(left.to_column(), right.to_column()) + } 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(); // 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 { Constraint::And { constraints: vec![ - Constraint::equal(value_column, - ColumnOrExpression::Value(TypedValue::Long(value))), - Constraint::not_equal(tag_column, - ColumnOrExpression::Integer(ValueType::Boolean.value_type_tag())), + Constraint::equal( + value_column, + ColumnOrExpression::Value(TypedValue::Long(value)), + ), + Constraint::not_equal( + tag_column, + ColumnOrExpression::Integer(ValueType::Boolean.value_type_tag()), + ), ], } } 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 } => { - Constraint::Infix { - op: Op(operator.to_sql_operator()), - left: left.into(), - right: right.into(), - } + Matches(left, right) => Constraint::Infix { + op: Op("MATCH"), + left: ColumnOrExpression::Column(left), + right: right.into(), }, - - Matches(left, right) => { - Constraint::Infix { - op: Op("MATCH"), - left: ColumnOrExpression::Column(left), - right: right.into(), - } - }, - HasTypes { value: table, value_types, check_value } => { + HasTypes { + value: table, + value_types, + check_value, + } => { let constraints = if check_value { possible_affinities(value_types) .into_iter() .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 } else { Some(affinities) }; type_constraint(&table, tag, to_check) - }).collect() + }) + .collect() } else { - value_types.into_iter() - .map(|vt| type_constraint(&table, vt.value_type_tag(), None)) - .collect() + value_types + .into_iter() + .map(|vt| type_constraint(&table, vt.value_type_tag(), None)) + .collect() }; Constraint::Or { constraints } - }, + } NotExists(computed_table) => { let subquery = table_for_computed(computed_table, TableAlias::new()); - Constraint::NotExists { - subquery: subquery, - } - }, + Constraint::NotExists { subquery: subquery } + } } } } @@ -254,7 +234,7 @@ pub enum ProjectedSelect { Constant(ConstantProjector), Query { query: SelectQuery, - projector: Box, + projector: Box, }, } @@ -265,7 +245,9 @@ struct ConsumableVec { impl From> for ConsumableVec { fn from(vec: Vec) -> ConsumableVec { - 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 ConsumableVec { fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubquery { match computed { 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 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) }).collect(), alias) - }, + } ComputedTable::Subquery(subquery) => { TableOrSubquery::Subquery(Box::new(cc_to_exists(subquery))) - }, - ComputedTable::NamedValues { - names, values, - } => { + } + ComputedTable::NamedValues { names, values } => { // We assume column homogeneity, so we won't have any type tag columns. 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 /// query that runs SQL. The next level up the call stack can check for known-empty queries if /// needed. -fn cc_to_select_query(projection: Projection, - cc: ConjoiningClauses, - distinct: bool, - group_by: Vec, - order: Option>, - limit: Limit) -> SelectQuery { +fn cc_to_select_query( + projection: Projection, + cc: ConjoiningClauses, + distinct: bool, + group_by: Vec, + order: Option>, + limit: Limit, +) -> SelectQuery { let from = if cc.from.is_empty() { FromClause::Nothing } 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) // 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`. - let tables = - from.into_iter().map(|source_alias| { - match source_alias { - SourceAlias(DatomsTable::Computed(i), alias) => { - let comp = computed.take_dangerously(i); - table_for_computed(comp, alias) - }, - _ => { - TableOrSubquery::Table(source_alias) - } - } - }); + let tables = from.into_iter().map(|source_alias| match source_alias { + SourceAlias(DatomsTable::Computed(i), alias) => { + let comp = computed.take_dangerously(i); + table_for_computed(comp, alias) + } + _ => TableOrSubquery::Table(source_alias), + }); FromClause::TableList(TableList(tables.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 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 + }; SelectQuery { distinct: distinct, projection: projection, from: from, group_by: group_by, - constraints: cc.wheres - .into_iter() - .map(|c| c.to_constraint()) - .collect(), + constraints: cc.wheres.into_iter().map(|c| c.to_constraint()).collect(), order: order, limit: limit, } @@ -433,18 +413,17 @@ fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery { use self::Projection::*; let nullable = match &projection { - &Columns(ref columns) => { - columns.iter().filter_map(|pc| { - match pc { - &ProjectedColumn(ColumnOrExpression::NullableAggregate(_, _), ref name) => { - Some(Constraint::IsNotNull { - value: ColumnOrExpression::ExistingColumn(name.clone()), - }) - }, - _ => None, + &Columns(ref columns) => columns + .iter() + .filter_map(|pc| match pc { + &ProjectedColumn(ColumnOrExpression::NullableAggregate(_, _), ref name) => { + Some(Constraint::IsNotNull { + value: ColumnOrExpression::ExistingColumn(name.clone()), + }) } - }).collect() - }, + _ => None, + }) + .collect(), &Star => vec![], &One => vec![], }; @@ -453,7 +432,9 @@ fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery { return SelectQuery { distinct: outer_distinct, projection: projection, - from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new(inner))])), + from: FromClause::TableList(TableList(vec![TableOrSubquery::Subquery(Box::new( + inner, + ))])), constraints: vec![], group_by: group_by, order: order_by, @@ -482,7 +463,9 @@ fn re_project(mut inner: SelectQuery, projection: Projection) -> SelectQuery { SelectQuery { distinct: false, 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, group_by: vec![], order: order_by, @@ -508,21 +491,28 @@ pub fn query_to_select(schema: &Schema, query: AlgebraicQuery) -> Result { - let inner = cc_to_select_query(pre_aggregate, - query.cc, - distinct, - group_by_cols, - query.order, - query.limit); + let inner = cc_to_select_query( + pre_aggregate, + query.cc, + distinct, + group_by_cols, + query.order, + query.limit, + ); let outer = re_project(inner, sql_projection); outer - }, - None => { - cc_to_select_query(sql_projection, query.cc, distinct, group_by_cols, query.order, query.limit) - }, + } + None => cc_to_select_query( + sql_projection, + query.cc, + distinct, + group_by_cols, + query.order, + query.limit, + ), }, projector: datalog_projector, } - }, + } }) } diff --git a/query-projector/tests/translate.rs b/query-projector/tests/translate.rs index 4b2faac3..02594127 100644 --- a/query-projector/tests/translate.rs +++ b/query-projector/tests/translate.rs @@ -8,9 +8,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +extern crate core_traits; extern crate edn; extern crate mentat_core; -extern crate core_traits; extern crate mentat_query_algebrizer; extern crate mentat_query_projector; extern crate mentat_sql; @@ -19,39 +19,19 @@ use std::collections::BTreeMap; use std::rc::Rc; -use edn::query::{ - FindSpec, - Keyword, - Variable, -}; +use edn::query::{FindSpec, Keyword, Variable}; -use core_traits::{ - Attribute, - Entid, - TypedValue, - ValueType, -}; +use core_traits::{Attribute, Entid, TypedValue, ValueType}; -use mentat_core::{ - Schema, -}; +use mentat_core::Schema; use mentat_query_algebrizer::{ - Known, - QueryInputs, - algebrize, - algebrize_with_inputs, - parse_find_string, + algebrize, algebrize_with_inputs, parse_find_string, Known, QueryInputs, }; -use mentat_query_projector::{ - ConstantProjector, -}; +use mentat_query_projector::ConstantProjector; -use mentat_query_projector::translate::{ - ProjectedSelect, - query_to_select, -}; +use mentat_query_projector::translate::{query_to_select, ProjectedSelect}; use mentat_sql::SQLQuery; @@ -75,31 +55,39 @@ fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) { fn query_to_sql(query: ProjectedSelect) -> SQLQuery { match query { - ProjectedSelect::Query { query, projector: _projector } => { - query.to_sql_query().expect("to_sql_query to succeed") - }, + ProjectedSelect::Query { + query, + projector: _projector, + } => query.to_sql_query().expect("to_sql_query to succeed"), ProjectedSelect::Constant(constant) => { - panic!("ProjectedSelect wasn't ::Query! Got constant {:#?}", constant.project_without_rows()); - }, + panic!( + "ProjectedSelect wasn't ::Query! Got constant {:#?}", + constant.project_without_rows() + ); + } } } fn query_to_constant(query: ProjectedSelect) -> ConstantProjector { match query { - ProjectedSelect::Constant(constant) => { - constant - }, + ProjectedSelect::Constant(constant) => constant, _ => panic!("ProjectedSelect wasn't ::Constant!"), } } fn assert_query_is_empty(query: ProjectedSelect, expected_spec: FindSpec) { - let constant = query_to_constant(query).project_without_rows().expect("constant run"); + let constant = query_to_constant(query) + .project_without_rows() + .expect("constant run"); assert_eq!(*constant.spec, expected_spec); assert!(constant.results.is_empty()); } -fn inner_translate_with_inputs(schema: &Schema, query: &'static str, inputs: QueryInputs) -> ProjectedSelect { +fn inner_translate_with_inputs( + schema: &Schema, + query: &'static str, + inputs: QueryInputs, +) -> ProjectedSelect { let known = Known::for_schema(schema); let parsed = parse_find_string(query).expect("parse to succeed"); let algebrized = algebrize_with_inputs(known, parsed, 0, inputs).expect("algebrize to succeed"); @@ -114,7 +102,11 @@ fn translate(schema: &Schema, query: &'static str) -> SQLQuery { translate_with_inputs(schema, query, QueryInputs::default()) } -fn translate_with_inputs_to_constant(schema: &Schema, query: &'static str, inputs: QueryInputs) -> ConstantProjector { +fn translate_with_inputs_to_constant( + schema: &Schema, + query: &'static str, + inputs: QueryInputs, +) -> ConstantProjector { query_to_constant(inner_translate_with_inputs(schema, query, inputs)) } @@ -122,21 +114,28 @@ fn translate_to_constant(schema: &Schema, query: &'static str) -> ConstantProjec translate_with_inputs_to_constant(schema, query, QueryInputs::default()) } - fn prepopulated_typed_schema(foo_type: ValueType) -> Schema { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 99); - add_attribute(&mut schema, 99, Attribute { - value_type: foo_type, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: foo_type, + ..Default::default() + }, + ); associate_ident(&mut schema, Keyword::namespaced("foo", "fts"), 100); - add_attribute(&mut schema, 100, Attribute { - value_type: ValueType::String, - index: true, - fulltext: true, - ..Default::default() - }); + add_attribute( + &mut schema, + 100, + Attribute { + value_type: ValueType::String, + index: true, + fulltext: true, + ..Default::default() + }, + ); schema } @@ -144,8 +143,11 @@ fn prepopulated_schema() -> Schema { prepopulated_typed_schema(ValueType::String) } -fn make_arg(name: &'static str, value: &'static str) -> (String, Rc) { - (name.to_string(), Rc::new(mentat_sql::Value::Text(value.to_string()))) +fn make_arg(name: &'static str, value: &'static str) -> (String, Rc) { + ( + name.to_string(), + Rc::new(rusqlite::types::Value::Text(value.to_string())), + ) } #[test] @@ -204,12 +206,16 @@ fn test_unbound_variable_limit() { // We don't know the value of the limit var, so we produce an escaped SQL variable to handle // later input. - let query = r#"[:find ?x :in ?limit-is-9-great :where [?x :foo/bar "yyy"] :limit ?limit-is-9-great]"#; + let query = + r#"[:find ?x :in ?limit-is-9-great :where [?x :foo/bar "yyy"] :limit ?limit-is-9-great]"#; let SQLQuery { sql, args } = translate_with_inputs(&schema, query, QueryInputs::default()); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 \ - LIMIT $ilimit_is_9_great"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 \ + LIMIT $ilimit_is_9_great" + ); assert_eq!(args, vec![make_arg("$v0", "yyy")]); } @@ -219,7 +225,10 @@ fn test_bound_variable_limit() { // We know the value of `?limit` at algebrizing time, so we substitute directly. let query = r#"[:find ?x :in ?limit :where [?x :foo/bar "yyy"] :limit ?limit]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?limit"), TypedValue::Long(92))]); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?limit"), + TypedValue::Long(92), + )]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 92"); assert_eq!(args, vec![make_arg("$v0", "yyy")]); @@ -232,7 +241,10 @@ fn test_bound_variable_limit_affects_distinct() { // We know the value of `?limit` at algebrizing time, so we substitute directly. // As it's `1`, we know we don't need `DISTINCT`! let query = r#"[:find ?x :in ?limit :where [?x :foo/bar "yyy"] :limit ?limit]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?limit"), TypedValue::Long(1))]); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?limit"), + TypedValue::Long(1), + )]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); assert_eq!(sql, "SELECT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1"); assert_eq!(args, vec![make_arg("$v0", "yyy")]); @@ -248,8 +260,12 @@ fn test_bound_variable_limit_affects_types() { let algebrized = algebrize(known, parsed).expect("algebrize failed"); // The type is known. - assert_eq!(Some(ValueType::Long), - algebrized.cc.known_type(&Variable::from_valid_name("?limit"))); + assert_eq!( + Some(ValueType::Long), + algebrized + .cc + .known_type(&Variable::from_valid_name("?limit")) + ); let select = query_to_select(&schema, algebrized).expect("query to translate"); let SQLQuery { sql, args } = query_to_sql(select); @@ -310,7 +326,10 @@ fn test_unknown_attribute_integer_value() { // Can't match boolean; no need to filter it out. let SQLQuery { sql, args } = translate(&schema, negative); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = -1"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = -1" + ); assert_eq!(args, vec![]); // Excludes booleans. @@ -325,7 +344,10 @@ fn test_unknown_attribute_integer_value() { // Can't match boolean; no need to filter it out. let SQLQuery { sql, args } = translate(&schema, two); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = 2"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.v = 2" + ); assert_eq!(args, vec![]); } @@ -353,10 +375,13 @@ fn test_type_required_long() { let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/long)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ - FROM `datoms` AS `datoms00` \ - WHERE ((`datoms00`.value_type_tag = 5 AND \ - (typeof(`datoms00`.v) = 'integer')))"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` \ + FROM `datoms` AS `datoms00` \ + WHERE ((`datoms00`.value_type_tag = 5 AND \ + (typeof(`datoms00`.v) = 'integer')))" + ); assert_eq!(args, vec![]); } @@ -368,10 +393,13 @@ fn test_type_required_double() { let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/double)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ - FROM `datoms` AS `datoms00` \ - WHERE ((`datoms00`.value_type_tag = 5 AND \ - (typeof(`datoms00`.v) = 'real')))"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` \ + FROM `datoms` AS `datoms00` \ + WHERE ((`datoms00`.value_type_tag = 5 AND \ + (typeof(`datoms00`.v) = 'real')))" + ); assert_eq!(args, vec![]); } @@ -383,9 +411,12 @@ fn test_type_required_boolean() { let query = r#"[:find ?x :where [?x _ ?e] [(type ?e :db.type/boolean)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` \ - FROM `datoms` AS `datoms00` \ - WHERE (`datoms00`.value_type_tag = 1)"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` \ + FROM `datoms` AS `datoms00` \ + WHERE (`datoms00`.value_type_tag = 1)" + ); assert_eq!(args, vec![]); } @@ -398,9 +429,12 @@ fn test_type_required_string() { let SQLQuery { sql, args } = translate(&schema, query); // Note: strings should use `all_datoms` and not `datoms`. - assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x` \ - FROM `all_datoms` AS `all_datoms00` \ - WHERE (`all_datoms00`.value_type_tag = 10)"); + assert_eq!( + sql, + "SELECT DISTINCT `all_datoms00`.e AS `?x` \ + FROM `all_datoms` AS `all_datoms00` \ + WHERE (`all_datoms00`.value_type_tag = 10)" + ); assert_eq!(args, vec![]); } @@ -444,10 +478,13 @@ fn test_compare_long_to_double_constants() { [?e :foo/bar ?v] [(< 99.0 1234512345)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 \ - AND 9.9e1 < 1234512345 \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 \ + AND 9.9e1 < 1234512345 \ + LIMIT 1" + ); assert_eq!(args, vec![]); } @@ -461,10 +498,13 @@ fn test_compare_long_to_double() { [?e :foo/bar ?t] [(< ?t 1234512345)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 \ - AND `datoms00`.v < 1234512345 \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 \ + AND `datoms00`.v < 1234512345 \ + LIMIT 1" + ); assert_eq!(args, vec![]); } @@ -478,10 +518,13 @@ fn test_compare_double_to_long() { [?e :foo/bar ?t] [(< ?t 1234512345.0)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 \ - AND `datoms00`.v < 1.234512345e9 \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT `datoms00`.e AS `?e` FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 \ + AND `datoms00`.v < 1.234512345e9 \ + LIMIT 1" + ); assert_eq!(args, vec![]); } @@ -492,10 +535,14 @@ fn test_simple_or_join() { associate_ident(&mut schema, Keyword::namespaced("page", "title"), 98); associate_ident(&mut schema, Keyword::namespaced("page", "description"), 99); for x in 97..100 { - add_attribute(&mut schema, x, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + add_attribute( + &mut schema, + x, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); } let query = r#"[:find [?url ?description] @@ -507,27 +554,38 @@ fn test_simple_or_join() { [?page :page/description ?description]]"#; let SQLQuery { sql, args } = translate(&schema, query); assert_eq!(sql, "SELECT `datoms01`.v AS `?url`, `datoms02`.v AS `?description` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01`, `datoms` AS `datoms02` WHERE ((`datoms00`.a = 97 AND `datoms00`.v = $v0) OR (`datoms00`.a = 98 AND `datoms00`.v = $v1)) AND `datoms01`.a = 97 AND `datoms02`.a = 99 AND `datoms00`.e = `datoms01`.e AND `datoms00`.e = `datoms02`.e LIMIT 1"); - assert_eq!(args, vec![make_arg("$v0", "http://foo.com/"), make_arg("$v1", "Foo")]); + assert_eq!( + args, + vec![make_arg("$v0", "http://foo.com/"), make_arg("$v1", "Foo")] + ); } #[test] fn test_complex_or_join() { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("page", "save"), 95); - add_attribute(&mut schema, 95, Attribute { - value_type: ValueType::Ref, - ..Default::default() - }); + add_attribute( + &mut schema, + 95, + Attribute { + value_type: ValueType::Ref, + ..Default::default() + }, + ); associate_ident(&mut schema, Keyword::namespaced("save", "title"), 96); associate_ident(&mut schema, Keyword::namespaced("page", "url"), 97); associate_ident(&mut schema, Keyword::namespaced("page", "title"), 98); associate_ident(&mut schema, Keyword::namespaced("page", "description"), 99); for x in 96..100 { - add_attribute(&mut schema, x, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + add_attribute( + &mut schema, + x, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); } let query = r#"[:find [?url ?description] @@ -541,44 +599,53 @@ fn test_complex_or_join() { [?page :page/url ?url] [?page :page/description ?description]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT `datoms04`.v AS `?url`, \ - `datoms05`.v AS `?description` \ - FROM (SELECT `datoms00`.e AS `?page` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 97 \ - AND `datoms00`.v = $v0 \ - UNION \ - SELECT `datoms01`.e AS `?page` \ - FROM `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 98 \ - AND `datoms01`.v = $v1 \ - UNION \ - SELECT `datoms02`.e AS `?page` \ - FROM `datoms` AS `datoms02`, \ - `datoms` AS `datoms03` \ - WHERE `datoms02`.a = 95 \ - AND `datoms03`.a = 96 \ - AND `datoms03`.v = $v1 \ - AND `datoms02`.v = `datoms03`.e) AS `c00`, \ - `datoms` AS `datoms04`, \ - `datoms` AS `datoms05` \ - WHERE `datoms04`.a = 97 \ - AND `datoms05`.a = 99 \ - AND `c00`.`?page` = `datoms04`.e \ - AND `c00`.`?page` = `datoms05`.e \ - LIMIT 1"); - assert_eq!(args, vec![make_arg("$v0", "http://foo.com/"), - make_arg("$v1", "Foo")]); + assert_eq!( + sql, + "SELECT `datoms04`.v AS `?url`, \ + `datoms05`.v AS `?description` \ + FROM (SELECT `datoms00`.e AS `?page` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 97 \ + AND `datoms00`.v = $v0 \ + UNION \ + SELECT `datoms01`.e AS `?page` \ + FROM `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 98 \ + AND `datoms01`.v = $v1 \ + UNION \ + SELECT `datoms02`.e AS `?page` \ + FROM `datoms` AS `datoms02`, \ + `datoms` AS `datoms03` \ + WHERE `datoms02`.a = 95 \ + AND `datoms03`.a = 96 \ + AND `datoms03`.v = $v1 \ + AND `datoms02`.v = `datoms03`.e) AS `c00`, \ + `datoms` AS `datoms04`, \ + `datoms` AS `datoms05` \ + WHERE `datoms04`.a = 97 \ + AND `datoms05`.a = 99 \ + AND `c00`.`?page` = `datoms04`.e \ + AND `c00`.`?page` = `datoms05`.e \ + LIMIT 1" + ); + assert_eq!( + args, + vec![make_arg("$v0", "http://foo.com/"), make_arg("$v1", "Foo")] + ); } #[test] fn test_complex_or_join_type_projection() { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("page", "title"), 98); - add_attribute(&mut schema, 98, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + add_attribute( + &mut schema, + 98, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); let query = r#"[:find [?y] :where @@ -586,19 +653,22 @@ fn test_complex_or_join_type_projection() { [6 :page/title ?y] [5 _ ?y])]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT `c00`.`?y` AS `?y`, \ - `c00`.`?y_value_type_tag` AS `?y_value_type_tag` \ - FROM (SELECT `datoms00`.v AS `?y`, \ - 10 AS `?y_value_type_tag` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.e = 6 \ - AND `datoms00`.a = 98 \ - UNION \ - SELECT `all_datoms01`.v AS `?y`, \ - `all_datoms01`.value_type_tag AS `?y_value_type_tag` \ - FROM `all_datoms` AS `all_datoms01` \ - WHERE `all_datoms01`.e = 5) AS `c00` \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT `c00`.`?y` AS `?y`, \ + `c00`.`?y_value_type_tag` AS `?y_value_type_tag` \ + FROM (SELECT `datoms00`.v AS `?y`, \ + 10 AS `?y_value_type_tag` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.e = 6 \ + AND `datoms00`.a = 98 \ + UNION \ + SELECT `all_datoms01`.v AS `?y`, \ + `all_datoms01`.value_type_tag AS `?y_value_type_tag` \ + FROM `all_datoms` AS `all_datoms01` \ + WHERE `all_datoms01`.e = 5) AS `c00` \ + LIMIT 1" + ); assert_eq!(args, vec![]); } @@ -609,15 +679,23 @@ fn test_not() { associate_ident(&mut schema, Keyword::namespaced("page", "title"), 98); associate_ident(&mut schema, Keyword::namespaced("page", "bookmarked"), 99); for x in 97..99 { - add_attribute(&mut schema, x, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + add_attribute( + &mut schema, + x, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); } - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::Boolean, - ..Default::default() - }); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::Boolean, + ..Default::default() + }, + ); let query = r#"[:find ?title :where [?page :page/title ?title] @@ -633,19 +711,35 @@ fn test_not_join() { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("page", "url"), 97); associate_ident(&mut schema, Keyword::namespaced("bookmarks", "page"), 98); - associate_ident(&mut schema, Keyword::namespaced("bookmarks", "date_created"), 99); - add_attribute(&mut schema, 97, Attribute { - value_type: ValueType::String, - ..Default::default() - }); - add_attribute(&mut schema, 98, Attribute { - value_type: ValueType::Ref, - ..Default::default() - }); - add_attribute(&mut schema, 99, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + associate_ident( + &mut schema, + Keyword::namespaced("bookmarks", "date_created"), + 99, + ); + add_attribute( + &mut schema, + 97, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 98, + Attribute { + value_type: ValueType::Ref, + ..Default::default() + }, + ); + add_attribute( + &mut schema, + 99, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); let query = r#"[:find ?url :where [?url :page/url] @@ -664,13 +758,19 @@ fn test_with_without_aggregate() { // Known type. let query = r#"[:find ?x :with ?y :where [?x :foo/bar ?y]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` WHERE `datoms00`.a = 99" + ); assert_eq!(args, vec![]); // Unknown type. let query = r#"[:find ?x :with ?y :where [?x _ ?y]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x` FROM `all_datoms` AS `all_datoms00`"); + assert_eq!( + sql, + "SELECT DISTINCT `all_datoms00`.e AS `?x` FROM `all_datoms` AS `all_datoms00`" + ); assert_eq!(args, vec![]); } @@ -681,19 +781,25 @@ fn test_order_by() { // Known type. let query = r#"[:find ?x :where [?x :foo/bar ?y] :order (desc ?y)]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?x`, `datoms00`.v AS `?y` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 \ - ORDER BY `?y` DESC"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x`, `datoms00`.v AS `?y` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 \ + ORDER BY `?y` DESC" + ); assert_eq!(args, vec![]); // Unknown type. let query = r#"[:find ?x :with ?y :where [?x _ ?y] :order ?y ?x]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, `all_datoms00`.v AS `?y`, \ - `all_datoms00`.value_type_tag AS `?y_value_type_tag` \ - FROM `all_datoms` AS `all_datoms00` \ - ORDER BY `?y_value_type_tag` ASC, `?y` ASC, `?x` ASC"); + assert_eq!( + sql, + "SELECT DISTINCT `all_datoms00`.e AS `?x`, `all_datoms00`.v AS `?y`, \ + `all_datoms00`.value_type_tag AS `?y_value_type_tag` \ + FROM `all_datoms` AS `all_datoms00` \ + ORDER BY `?y_value_type_tag` ASC, `?y` ASC, `?x` ASC" + ); assert_eq!(args, vec![]); } @@ -701,10 +807,14 @@ fn test_order_by() { fn test_complex_nested_or_join_type_projection() { let mut schema = Schema::default(); associate_ident(&mut schema, Keyword::namespaced("page", "title"), 98); - add_attribute(&mut schema, 98, Attribute { - value_type: ValueType::String, - ..Default::default() - }); + add_attribute( + &mut schema, + 98, + Attribute { + value_type: ValueType::String, + ..Default::default() + }, + ); let input = r#"[:find [?y] :where @@ -715,16 +825,19 @@ fn test_complex_nested_or_join_type_projection() { [_ :page/title ?y]))]"#; let SQLQuery { sql, args } = translate(&schema, input); - assert_eq!(sql, "SELECT `c00`.`?y` AS `?y` \ - FROM (SELECT `datoms00`.v AS `?y` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 98 \ - UNION \ - SELECT `datoms01`.v AS `?y` \ - FROM `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 98) \ - AS `c00` \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT `c00`.`?y` AS `?y` \ + FROM (SELECT `datoms00`.v AS `?y` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 98 \ + UNION \ + SELECT `datoms01`.v AS `?y` \ + FROM `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 98) \ + AS `c00` \ + LIMIT 1" + ); assert_eq!(args, vec![]); } @@ -735,17 +848,28 @@ fn test_ground_scalar() { // Verify that we accept inline constants. let query = r#"[:find ?x . :where [(ground "yyy") ?x]]"#; let constant = translate_to_constant(&schema, query); - assert_eq!(constant.project_without_rows().unwrap() - .into_scalar().unwrap(), - Some(TypedValue::typed_string("yyy").into())); + assert_eq!( + constant + .project_without_rows() + .unwrap() + .into_scalar() + .unwrap(), + Some(TypedValue::typed_string("yyy").into()) + ); // Verify that we accept bound input constants. let query = r#"[:find ?x . :in ?v :where [(ground ?v) ?x]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?v"), "aaa".into())]); + let inputs = + QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?v"), "aaa".into())]); let constant = translate_with_inputs_to_constant(&schema, query, inputs); - assert_eq!(constant.project_without_rows().unwrap() - .into_scalar().unwrap(), - Some(TypedValue::typed_string("aaa").into())); + assert_eq!( + constant + .project_without_rows() + .unwrap() + .into_scalar() + .unwrap(), + Some(TypedValue::typed_string("aaa").into()) + ); } #[test] @@ -755,19 +879,30 @@ fn test_ground_tuple() { // Verify that we accept inline constants. let query = r#"[:find ?x ?y :where [(ground [1 "yyy"]) [?x ?y]]]"#; let constant = translate_to_constant(&schema, query); - assert_eq!(constant.project_without_rows().unwrap() - .into_rel().unwrap(), - vec![vec![TypedValue::Long(1), TypedValue::typed_string("yyy")]].into()); + assert_eq!( + constant.project_without_rows().unwrap().into_rel().unwrap(), + vec![vec![TypedValue::Long(1), TypedValue::typed_string("yyy")]].into() + ); // Verify that we accept bound input constants. let query = r#"[:find [?x ?y] :in ?u ?v :where [(ground [?u ?v]) [?x ?y]]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?u"), TypedValue::Long(2)), - (Variable::from_valid_name("?v"), "aaa".into()),]); + let inputs = QueryInputs::with_value_sequence(vec![ + (Variable::from_valid_name("?u"), TypedValue::Long(2)), + (Variable::from_valid_name("?v"), "aaa".into()), + ]); let constant = translate_with_inputs_to_constant(&schema, query, inputs); - assert_eq!(constant.project_without_rows().unwrap() - .into_tuple().unwrap(), - Some(vec![TypedValue::Long(2).into(), TypedValue::typed_string("aaa").into()])); + assert_eq!( + constant + .project_without_rows() + .unwrap() + .into_tuple() + .unwrap(), + Some(vec![ + TypedValue::Long(2).into(), + TypedValue::typed_string("aaa").into() + ]) + ); // TODO: treat 2 as an input variable that could be bound late, rather than eagerly binding it. // In that case the query wouldn't be constant, and would look more like: @@ -783,19 +918,26 @@ fn test_ground_coll() { // Verify that we accept inline constants. let query = r#"[:find ?x :where [(ground ["xxx" "yyy"]) [?x ...]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x` FROM \ - (SELECT 0 AS `?x` WHERE 0 UNION ALL VALUES ($v0), ($v1)) AS `c00`"); - assert_eq!(args, vec![make_arg("$v0", "xxx"), - make_arg("$v1", "yyy")]); + assert_eq!( + sql, + "SELECT DISTINCT `c00`.`?x` AS `?x` FROM \ + (SELECT 0 AS `?x` WHERE 0 UNION ALL VALUES ($v0), ($v1)) AS `c00`" + ); + assert_eq!(args, vec![make_arg("$v0", "xxx"), make_arg("$v1", "yyy")]); // Verify that we accept bound input constants. let query = r#"[:find ?x :in ?u ?v :where [(ground [?u ?v]) [?x ...]]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?u"), TypedValue::Long(2)), - (Variable::from_valid_name("?v"), TypedValue::Long(3)),]); + let inputs = QueryInputs::with_value_sequence(vec![ + (Variable::from_valid_name("?u"), TypedValue::Long(2)), + (Variable::from_valid_name("?v"), TypedValue::Long(3)), + ]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); // TODO: treat 2 and 3 as input variables that could be bound late, rather than eagerly binding. - assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x` FROM \ - (SELECT 0 AS `?x` WHERE 0 UNION ALL VALUES (2), (3)) AS `c00`"); + assert_eq!( + sql, + "SELECT DISTINCT `c00`.`?x` AS `?x` FROM \ + (SELECT 0 AS `?x` WHERE 0 UNION ALL VALUES (2), (3)) AS `c00`" + ); assert_eq!(args, vec![]); } @@ -806,19 +948,26 @@ fn test_ground_rel() { // Verify that we accept inline constants. let query = r#"[:find ?x ?y :where [(ground [[1 "xxx"] [2 "yyy"]]) [[?x ?y]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \ - (SELECT 0 AS `?x`, 0 AS `?y` WHERE 0 UNION ALL VALUES (1, $v0), (2, $v1)) AS `c00`"); - assert_eq!(args, vec![make_arg("$v0", "xxx"), - make_arg("$v1", "yyy")]); + assert_eq!( + sql, + "SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \ + (SELECT 0 AS `?x`, 0 AS `?y` WHERE 0 UNION ALL VALUES (1, $v0), (2, $v1)) AS `c00`" + ); + assert_eq!(args, vec![make_arg("$v0", "xxx"), make_arg("$v1", "yyy")]); // Verify that we accept bound input constants. let query = r#"[:find ?x ?y :in ?u ?v :where [(ground [[?u 1] [?v 2]]) [[?x ?y]]]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?u"), TypedValue::Long(3)), - (Variable::from_valid_name("?v"), TypedValue::Long(4)),]); + let inputs = QueryInputs::with_value_sequence(vec![ + (Variable::from_valid_name("?u"), TypedValue::Long(3)), + (Variable::from_valid_name("?v"), TypedValue::Long(4)), + ]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); // TODO: treat 3 and 4 as input variables that could be bound late, rather than eagerly binding. - assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \ - (SELECT 0 AS `?x`, 0 AS `?y` WHERE 0 UNION ALL VALUES (3, 1), (4, 2)) AS `c00`"); + assert_eq!( + sql, + "SELECT DISTINCT `c00`.`?x` AS `?x`, `c00`.`?y` AS `?y` FROM \ + (SELECT 0 AS `?x`, 0 AS `?y` WHERE 0 UNION ALL VALUES (3, 1), (4, 2)) AS `c00`" + ); assert_eq!(args, vec![]); } @@ -834,25 +983,33 @@ fn test_compound_with_ground() { // This is confusing because the computed tables (like `c00`) are numbered sequentially in each // arm of the `or` rather than numbered globally. But SQLite scopes the names correctly, so it // works. In the future, we might number the computed tables globally to make this more clear. - assert_eq!(sql, "SELECT DISTINCT `c00`.`?x` AS `?x` FROM (\ - SELECT $v0 AS `?x` UNION \ - SELECT $v1 AS `?x`) AS `c00`"); - assert_eq!(args, vec![make_arg("$v0", "yyy"), - make_arg("$v1", "zzz"),]); + assert_eq!( + sql, + "SELECT DISTINCT `c00`.`?x` AS `?x` FROM (\ + SELECT $v0 AS `?x` UNION \ + SELECT $v1 AS `?x`) AS `c00`" + ); + assert_eq!(args, vec![make_arg("$v0", "yyy"), make_arg("$v1", "zzz"),]); // Verify that we can use ground to constrain the bindings produced by earlier clauses. let query = r#"[:find ?x . :where [_ :foo/bar ?x] [(ground "yyy") ?x]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1"); + assert_eq!( + sql, + "SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1" + ); assert_eq!(args, vec![make_arg("$v0", "yyy")]); // Verify that we can further constrain the bindings produced by our clause. let query = r#"[:find ?x . :where [(ground "yyy") ?x] [_ :foo/bar ?x]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1"); + assert_eq!( + sql, + "SELECT $v0 AS `?x` FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 AND `datoms00`.v = $v0 LIMIT 1" + ); assert_eq!(args, vec![make_arg("$v0", "yyy")]); } @@ -862,11 +1019,14 @@ fn test_unbound_attribute_with_ground_entity() { let query = r#"[:find ?x ?v :where [?x _ ?v] (not [(ground 17) ?x])]"#; let schema = prepopulated_schema(); let SQLQuery { sql, .. } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, \ - `all_datoms00`.v AS `?v`, \ - `all_datoms00`.value_type_tag AS `?v_value_type_tag` \ - FROM `all_datoms` AS `all_datoms00` \ - WHERE NOT EXISTS (SELECT 1 WHERE `all_datoms00`.e = 17)"); + assert_eq!( + sql, + "SELECT DISTINCT `all_datoms00`.e AS `?x`, \ + `all_datoms00`.v AS `?v`, \ + `all_datoms00`.value_type_tag AS `?v_value_type_tag` \ + FROM `all_datoms` AS `all_datoms00` \ + WHERE NOT EXISTS (SELECT 1 WHERE `all_datoms00`.e = 17)" + ); } #[test] @@ -874,15 +1034,17 @@ fn test_unbound_attribute_with_ground() { let query = r#"[:find ?x ?v :where [?x _ ?v] (not [(ground 17) ?v])]"#; let schema = prepopulated_schema(); let SQLQuery { sql, .. } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `all_datoms00`.e AS `?x`, \ - `all_datoms00`.v AS `?v`, \ - `all_datoms00`.value_type_tag AS `?v_value_type_tag` \ - FROM `all_datoms` AS `all_datoms00` \ - WHERE NOT EXISTS (SELECT 1 WHERE `all_datoms00`.v = 17 AND \ - (`all_datoms00`.value_type_tag = 5))"); + assert_eq!( + sql, + "SELECT DISTINCT `all_datoms00`.e AS `?x`, \ + `all_datoms00`.v AS `?v`, \ + `all_datoms00`.value_type_tag AS `?v_value_type_tag` \ + FROM `all_datoms` AS `all_datoms00` \ + WHERE NOT EXISTS (SELECT 1 WHERE `all_datoms00`.v = 17 AND \ + (`all_datoms00`.value_type_tag = 5))" + ); } - #[test] fn test_not_with_ground() { let mut schema = prepopulated_schema(); @@ -890,11 +1052,15 @@ fn test_not_with_ground() { associate_ident(&mut schema, Keyword::namespaced("db.type", "ref"), 23); associate_ident(&mut schema, Keyword::namespaced("db.type", "bool"), 28); associate_ident(&mut schema, Keyword::namespaced("db.type", "instant"), 29); - add_attribute(&mut schema, 7, Attribute { - value_type: ValueType::Ref, - multival: false, - ..Default::default() - }); + add_attribute( + &mut schema, + 7, + Attribute { + value_type: ValueType::Ref, + multival: false, + ..Default::default() + }, + ); // Scalar. // TODO: this kind of simple `not` should be implemented without the subquery. #476. @@ -908,11 +1074,13 @@ fn test_not_with_ground() { // TODO: we can generate better SQL for this, too. #476. let query = r#"[:find ?x :where [?x :db/valueType ?v] (not [(ground [:db.type/bool :db.type/instant]) [?v ...]])]"#; let SQLQuery { sql, .. } = translate(&schema, query); - assert_eq!(sql, - "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 7 AND NOT EXISTS \ - (SELECT 1 FROM (SELECT 0 AS `?v` WHERE 0 UNION ALL VALUES (28), (29)) AS `c00` \ - WHERE `datoms00`.v = `c00`.`?v`)"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?x` FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 7 AND NOT EXISTS \ + (SELECT 1 FROM (SELECT 0 AS `?v` WHERE 0 UNION ALL VALUES (28), (29)) AS `c00` \ + WHERE `datoms00`.v = `c00`.`?v`)" + ); } #[test] @@ -921,73 +1089,88 @@ fn test_fulltext() { let query = r#"[:find ?entity ?value ?tx ?score :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ - `fulltext_values00`.text AS `?value`, \ - `datoms01`.tx AS `?tx`, \ - 0e0 AS `?score` \ - FROM `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms01`.e AS `?entity`, \ + `fulltext_values00`.text AS `?value`, \ + `datoms01`.tx AS `?tx`, \ + 0e0 AS `?score` \ + FROM `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0" + ); assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); // Observe that the computed table isn't dropped, even though `?score` isn't bound in the final conjoining clause. - assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ - `fulltext_values00`.text AS `?value`, \ - `datoms01`.tx AS `?tx` \ - FROM `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms01`.e AS `?entity`, \ + `fulltext_values00`.text AS `?value`, \ + `datoms01`.tx AS `?tx` \ + FROM `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0" + ); assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx _]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); // Observe that the computed table isn't included at all when `?score` isn't bound. - assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ - `fulltext_values00`.text AS `?value`, \ - `datoms01`.tx AS `?tx` \ - FROM `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms01`.e AS `?entity`, \ + `fulltext_values00`.text AS `?value`, \ + `datoms01`.tx AS `?tx` \ + FROM `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0" + ); assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]] [?entity :foo/bar ?score]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms01`.e AS `?entity`, \ - `fulltext_values00`.text AS `?value`, \ - `datoms01`.tx AS `?tx` \ - FROM `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01`, \ - `datoms` AS `datoms02` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0 \ - AND `datoms02`.a = 99 \ - AND `datoms02`.v = 0e0 \ - AND `datoms01`.e = `datoms02`.e"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms01`.e AS `?entity`, \ + `fulltext_values00`.text AS `?value`, \ + `datoms01`.tx AS `?tx` \ + FROM `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01`, \ + `datoms` AS `datoms02` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0 \ + AND `datoms02`.a = 99 \ + AND `datoms02`.v = 0e0 \ + AND `datoms01`.e = `datoms02`.e" + ); assert_eq!(args, vec![make_arg("$v0", "needle"),]); let query = r#"[:find ?entity ?value ?tx :where [?entity :foo/bar ?score] [(fulltext $ :foo/fts "needle") [[?entity ?value ?tx ?score]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?entity`, \ - `fulltext_values01`.text AS `?value`, \ - `datoms02`.tx AS `?tx` \ - FROM `datoms` AS `datoms00`, \ - `fulltext_values` AS `fulltext_values01`, \ - `datoms` AS `datoms02` \ - WHERE `datoms00`.a = 99 \ - AND `datoms02`.a = 100 \ - AND `datoms02`.v = `fulltext_values01`.rowid \ - AND `fulltext_values01`.text MATCH $v0 \ - AND `datoms00`.v = 0e0 \ - AND `datoms00`.e = `datoms02`.e"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?entity`, \ + `fulltext_values01`.text AS `?value`, \ + `datoms02`.tx AS `?tx` \ + FROM `datoms` AS `datoms00`, \ + `fulltext_values` AS `fulltext_values01`, \ + `datoms` AS `datoms02` \ + WHERE `datoms00`.a = 99 \ + AND `datoms02`.a = 100 \ + AND `datoms02`.v = `fulltext_values01`.rowid \ + AND `fulltext_values01`.text MATCH $v0 \ + AND `datoms00`.v = 0e0 \ + AND `datoms00`.e = `datoms02`.e" + ); assert_eq!(args, vec![make_arg("$v0", "needle"),]); } @@ -1005,42 +1188,57 @@ fn test_fulltext_inputs() { // Without binding the value. q_once will err if you try this! let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); - assert_eq!(sql, "SELECT DISTINCT `fulltext_values00`.text AS `?val` \ - FROM \ - `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0"); + assert_eq!( + sql, + "SELECT DISTINCT `fulltext_values00`.text AS `?val` \ + FROM \ + `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0" + ); assert_eq!(args, vec![make_arg("$v0", "hello"),]); // With the value bound. - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?entity"), TypedValue::Ref(111))]); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?entity"), + TypedValue::Ref(111), + )]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); - assert_eq!(sql, "SELECT DISTINCT `fulltext_values00`.text AS `?val` \ - FROM \ - `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0 \ - AND `datoms01`.e = 111"); + assert_eq!( + sql, + "SELECT DISTINCT `fulltext_values00`.text AS `?val` \ + FROM \ + `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0 \ + AND `datoms01`.e = 111" + ); assert_eq!(args, vec![make_arg("$v0", "hello"),]); // Same again, but retrieving the entity. let query = r#"[:find ?entity . :in ?entity :where [(fulltext $ :foo/fts "hello") [[?entity _ _]]]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?entity"), TypedValue::Ref(111))]); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?entity"), + TypedValue::Ref(111), + )]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); - assert_eq!(sql, "SELECT 111 AS `?entity` FROM \ - `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0 \ - AND `datoms01`.e = 111 \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT 111 AS `?entity` FROM \ + `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0 \ + AND `datoms01`.e = 111 \ + LIMIT 1" + ); assert_eq!(args, vec![make_arg("$v0", "hello"),]); // A larger pattern. @@ -1049,21 +1247,27 @@ fn test_fulltext_inputs() { :where [(fulltext $ :foo/fts "hello") [[?entity ?value]]] [?entity :foo/bar ?friend]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?entity"), TypedValue::Ref(121))]); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?entity"), + TypedValue::Ref(121), + )]); let SQLQuery { sql, args } = translate_with_inputs(&schema, query, inputs); - assert_eq!(sql, "SELECT DISTINCT 121 AS `?entity`, \ - `fulltext_values00`.text AS `?value`, \ - `datoms02`.v AS `?friend` \ - FROM \ - `fulltext_values` AS `fulltext_values00`, \ - `datoms` AS `datoms01`, \ - `datoms` AS `datoms02` \ - WHERE `datoms01`.a = 100 \ - AND `datoms01`.v = `fulltext_values00`.rowid \ - AND `fulltext_values00`.text MATCH $v0 \ - AND `datoms01`.e = 121 \ - AND `datoms02`.e = 121 \ - AND `datoms02`.a = 99"); + assert_eq!( + sql, + "SELECT DISTINCT 121 AS `?entity`, \ + `fulltext_values00`.text AS `?value`, \ + `datoms02`.v AS `?friend` \ + FROM \ + `fulltext_values` AS `fulltext_values00`, \ + `datoms` AS `datoms01`, \ + `datoms` AS `datoms02` \ + WHERE `datoms01`.a = 100 \ + AND `datoms01`.v = `fulltext_values00`.rowid \ + AND `fulltext_values00`.text MATCH $v0 \ + AND `datoms01`.e = 121 \ + AND `datoms02`.e = 121 \ + AND `datoms02`.a = 99" + ); assert_eq!(args, vec![make_arg("$v0", "hello"),]); } @@ -1076,11 +1280,14 @@ fn test_instant_range() { [(> ?t #inst "2017-06-16T00:56:41.257Z")]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `datoms00`.e AS `?e` \ - FROM \ - `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99 \ - AND `datoms00`.v > 1497574601257000"); + assert_eq!( + sql, + "SELECT DISTINCT `datoms00`.e AS `?e` \ + FROM \ + `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99 \ + AND `datoms00`.v > 1497574601257000" + ); assert_eq!(args, vec![]); } @@ -1093,17 +1300,20 @@ fn test_project_aggregates() { let SQLQuery { sql, args } = translate(&schema, query); // No outer DISTINCT: we aggregate or group by every variable. - assert_eq!(sql, "SELECT * \ - FROM \ - (SELECT `?e` AS `?e`, max(`?t`) AS `(max ?t)` \ - FROM \ - (SELECT DISTINCT \ - `datoms00`.e AS `?e`, \ - `datoms00`.v AS `?t` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99) \ - GROUP BY `?e`) \ - WHERE `(max ?t)` IS NOT NULL"); + assert_eq!( + sql, + "SELECT * \ + FROM \ + (SELECT `?e` AS `?e`, max(`?t`) AS `(max ?t)` \ + FROM \ + (SELECT DISTINCT \ + `datoms00`.e AS `?e`, \ + `datoms00`.v AS `?t` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99) \ + GROUP BY `?e`) \ + WHERE `(max ?t)` IS NOT NULL" + ); assert_eq!(args, vec![]); let query = r#"[:find (max ?t) @@ -1111,17 +1321,20 @@ fn test_project_aggregates() { :where [?e :foo/bar ?t]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT * \ - FROM \ - (SELECT max(`?t`) AS `(max ?t)` \ - FROM \ - (SELECT DISTINCT \ - `datoms00`.v AS `?t`, \ - `datoms00`.e AS `?e` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99)\ - ) \ - WHERE `(max ?t)` IS NOT NULL"); + assert_eq!( + sql, + "SELECT * \ + FROM \ + (SELECT max(`?t`) AS `(max ?t)` \ + FROM \ + (SELECT DISTINCT \ + `datoms00`.v AS `?t`, \ + `datoms00`.e AS `?e` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99)\ + ) \ + WHERE `(max ?t)` IS NOT NULL" + ); assert_eq!(args, vec![]); // ORDER BY lifted to outer query if there is no LIMIT. @@ -1132,19 +1345,22 @@ fn test_project_aggregates() { [?t :foo/bar ?x] :order ?a]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT * \ - FROM \ - (SELECT max(`?x`) AS `(max ?x)`, `?a` AS `?a` \ - FROM \ - (SELECT DISTINCT \ - `datoms01`.v AS `?x`, \ - `datoms00`.a AS `?a`, \ - `datoms00`.e AS `?e` \ - FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 99 AND `datoms00`.v = `datoms01`.e) \ - GROUP BY `?a`) \ - WHERE `(max ?x)` IS NOT NULL \ - ORDER BY `?a` ASC"); + assert_eq!( + sql, + "SELECT * \ + FROM \ + (SELECT max(`?x`) AS `(max ?x)`, `?a` AS `?a` \ + FROM \ + (SELECT DISTINCT \ + `datoms01`.v AS `?x`, \ + `datoms00`.a AS `?a`, \ + `datoms00`.e AS `?e` \ + FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 99 AND `datoms00`.v = `datoms01`.e) \ + GROUP BY `?a`) \ + WHERE `(max ?x)` IS NOT NULL \ + ORDER BY `?a` ASC" + ); assert_eq!(args, vec![]); // ORDER BY duplicated in outer query if there is a LIMIT. @@ -1156,21 +1372,24 @@ fn test_project_aggregates() { :order (desc ?a) :limit 10]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT * \ - FROM \ - (SELECT max(`?x`) AS `(max ?x)`, `?a` AS `?a` \ - FROM \ - (SELECT DISTINCT \ - `datoms01`.v AS `?x`, \ - `datoms00`.a AS `?a`, \ - `datoms00`.e AS `?e` \ - FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` \ - WHERE `datoms01`.a = 99 AND `datoms00`.v = `datoms01`.e) \ - GROUP BY `?a` \ - ORDER BY `?a` DESC \ - LIMIT 10) \ - WHERE `(max ?x)` IS NOT NULL \ - ORDER BY `?a` DESC"); + assert_eq!( + sql, + "SELECT * \ + FROM \ + (SELECT max(`?x`) AS `(max ?x)`, `?a` AS `?a` \ + FROM \ + (SELECT DISTINCT \ + `datoms01`.v AS `?x`, \ + `datoms00`.a AS `?a`, \ + `datoms00`.e AS `?e` \ + FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` \ + WHERE `datoms01`.a = 99 AND `datoms00`.v = `datoms01`.e) \ + GROUP BY `?a` \ + ORDER BY `?a` DESC \ + LIMIT 10) \ + WHERE `(max ?x)` IS NOT NULL \ + ORDER BY `?a` DESC" + ); assert_eq!(args, vec![]); // No outer SELECT * for non-nullable aggregates. @@ -1179,13 +1398,16 @@ fn test_project_aggregates() { :where [?e :foo/bar ?t]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT count(`?t`) AS `(count ?t)` \ - FROM \ - (SELECT DISTINCT \ - `datoms00`.v AS `?t`, \ - `datoms00`.e AS `?e` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99)"); + assert_eq!( + sql, + "SELECT count(`?t`) AS `(count ?t)` \ + FROM \ + (SELECT DISTINCT \ + `datoms00`.v AS `?t`, \ + `datoms00`.e AS `?e` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99)" + ); assert_eq!(args, vec![]); } @@ -1198,16 +1420,19 @@ fn test_project_the() { let SQLQuery { sql, args } = translate(&schema, query); // We shouldn't NULL-check (the). - assert_eq!(sql, "SELECT * \ - FROM \ - (SELECT `?e` AS `?e`, max(`?t`) AS `(max ?t)` \ - FROM \ - (SELECT DISTINCT \ - `datoms00`.e AS `?e`, \ - `datoms00`.v AS `?t` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.a = 99)) \ - WHERE `(max ?t)` IS NOT NULL"); + assert_eq!( + sql, + "SELECT * \ + FROM \ + (SELECT `?e` AS `?e`, max(`?t`) AS `(max ?t)` \ + FROM \ + (SELECT DISTINCT \ + `datoms00`.e AS `?e`, \ + `datoms00`.v AS `?t` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.a = 99)) \ + WHERE `(max ?t)` IS NOT NULL" + ); assert_eq!(args, vec![]); } @@ -1216,69 +1441,87 @@ fn test_tx_before_and_after() { let schema = prepopulated_typed_schema(ValueType::Long); let query = r#"[:find ?x :where [?x _ _ ?tx] [(tx-after ?tx 12345)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT \ - `datoms00`.e AS `?x` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.tx > 12345"); + assert_eq!( + sql, + "SELECT DISTINCT \ + `datoms00`.e AS `?x` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.tx > 12345" + ); assert_eq!(args, vec![]); let query = r#"[:find ?x :where [?x _ _ ?tx] [(tx-before ?tx 12345)]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT \ - `datoms00`.e AS `?x` \ - FROM `datoms` AS `datoms00` \ - WHERE `datoms00`.tx < 12345"); + assert_eq!( + sql, + "SELECT DISTINCT \ + `datoms00`.e AS `?x` \ + FROM `datoms` AS `datoms00` \ + WHERE `datoms00`.tx < 12345" + ); assert_eq!(args, vec![]); } - #[test] fn test_tx_ids() { let mut schema = prepopulated_typed_schema(ValueType::Double); associate_ident(&mut schema, Keyword::namespaced("db", "txInstant"), 101); - add_attribute(&mut schema, 101, Attribute { - value_type: ValueType::Instant, - multival: false, - index: true, - ..Default::default() - }); + add_attribute( + &mut schema, + 101, + Attribute { + value_type: ValueType::Instant, + multival: false, + index: true, + ..Default::default() + }, + ); let query = r#"[:find ?tx :where [(tx-ids $ 1000 2000) [[?tx]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `transactions00`.tx AS `?tx` \ - FROM `transactions` AS `transactions00` \ - WHERE 1000 <= `transactions00`.tx \ - AND `transactions00`.tx < 2000"); + assert_eq!( + sql, + "SELECT DISTINCT `transactions00`.tx AS `?tx` \ + FROM `transactions` AS `transactions00` \ + WHERE 1000 <= `transactions00`.tx \ + AND `transactions00`.tx < 2000" + ); assert_eq!(args, vec![]); // This is rather artificial but verifies that binding the arguments to (tx-ids) works. let query = r#"[:find ?tx :where [?first :db/txInstant #inst "2016-01-01T11:00:00.000Z"] [?last :db/txInstant #inst "2017-01-01T11:00:00.000Z"] [(tx-ids $ ?first ?last) [?tx ...]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `transactions02`.tx AS `?tx` \ - FROM `datoms` AS `datoms00`, \ - `datoms` AS `datoms01`, \ - `transactions` AS `transactions02` \ - WHERE `datoms00`.a = 101 \ - AND `datoms00`.v = 1451646000000000 \ - AND `datoms01`.a = 101 \ - AND `datoms01`.v = 1483268400000000 \ - AND `datoms00`.e <= `transactions02`.tx \ - AND `transactions02`.tx < `datoms01`.e"); + assert_eq!( + sql, + "SELECT DISTINCT `transactions02`.tx AS `?tx` \ + FROM `datoms` AS `datoms00`, \ + `datoms` AS `datoms01`, \ + `transactions` AS `transactions02` \ + WHERE `datoms00`.a = 101 \ + AND `datoms00`.v = 1451646000000000 \ + AND `datoms01`.a = 101 \ + AND `datoms01`.v = 1483268400000000 \ + AND `datoms00`.e <= `transactions02`.tx \ + AND `transactions02`.tx < `datoms01`.e" + ); assert_eq!(args, vec![]); // In practice the following query would be inefficient because of the filter on all_datoms.tx, // but that is what (tx-data) is for. let query = r#"[:find ?e ?a ?v ?tx :where [(tx-ids $ 1000 2000) [[?tx]]] [?e ?a ?v ?tx]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `all_datoms01`.e AS `?e`, \ - `all_datoms01`.a AS `?a`, \ - `all_datoms01`.v AS `?v`, \ - `all_datoms01`.value_type_tag AS `?v_value_type_tag`, \ - `transactions00`.tx AS `?tx` \ - FROM `transactions` AS `transactions00`, \ - `all_datoms` AS `all_datoms01` \ - WHERE 1000 <= `transactions00`.tx \ - AND `transactions00`.tx < 2000 \ - AND `transactions00`.tx = `all_datoms01`.tx"); + assert_eq!( + sql, + "SELECT DISTINCT `all_datoms01`.e AS `?e`, \ + `all_datoms01`.a AS `?a`, \ + `all_datoms01`.v AS `?v`, \ + `all_datoms01`.value_type_tag AS `?v_value_type_tag`, \ + `transactions00`.tx AS `?tx` \ + FROM `transactions` AS `transactions00`, \ + `all_datoms` AS `all_datoms01` \ + WHERE 1000 <= `transactions00`.tx \ + AND `transactions00`.tx < 2000 \ + AND `transactions00`.tx = `all_datoms01`.tx" + ); assert_eq!(args, vec![]); } @@ -1288,27 +1531,33 @@ fn test_tx_data() { let query = r#"[:find ?e ?a ?v ?tx ?added :where [(tx-data $ 1000) [[?e ?a ?v ?tx ?added]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `transactions00`.e AS `?e`, \ - `transactions00`.a AS `?a`, \ - `transactions00`.v AS `?v`, \ - `transactions00`.value_type_tag AS `?v_value_type_tag`, \ - `transactions00`.tx AS `?tx`, \ - `transactions00`.added AS `?added` \ - FROM `transactions` AS `transactions00` \ - WHERE `transactions00`.tx = 1000"); + assert_eq!( + sql, + "SELECT DISTINCT `transactions00`.e AS `?e`, \ + `transactions00`.a AS `?a`, \ + `transactions00`.v AS `?v`, \ + `transactions00`.value_type_tag AS `?v_value_type_tag`, \ + `transactions00`.tx AS `?tx`, \ + `transactions00`.added AS `?added` \ + FROM `transactions` AS `transactions00` \ + WHERE `transactions00`.tx = 1000" + ); assert_eq!(args, vec![]); // Ensure that we don't project columns that we don't need, even if they are bound to named // variables or to placeholders. let query = r#"[:find [?a ?v ?added] :where [(tx-data $ 1000) [[?e ?a ?v _ ?added]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT `transactions00`.a AS `?a`, \ - `transactions00`.v AS `?v`, \ - `transactions00`.value_type_tag AS `?v_value_type_tag`, \ - `transactions00`.added AS `?added` \ - FROM `transactions` AS `transactions00` \ - WHERE `transactions00`.tx = 1000 \ - LIMIT 1"); + assert_eq!( + sql, + "SELECT `transactions00`.a AS `?a`, \ + `transactions00`.v AS `?v`, \ + `transactions00`.value_type_tag AS `?v_value_type_tag`, \ + `transactions00`.added AS `?added` \ + FROM `transactions` AS `transactions00` \ + WHERE `transactions00`.tx = 1000 \ + LIMIT 1" + ); assert_eq!(args, vec![]); // This is awkward since the transactions table is queried twice, once to list transaction IDs @@ -1316,16 +1565,19 @@ fn test_tx_data() { // improving this, perhaps by optimizing certain combinations of functions and bindings. let query = r#"[:find ?e ?a ?v ?tx ?added :where [(tx-ids $ 1000 2000) [[?tx]]] [(tx-data $ ?tx) [[?e ?a ?v _ ?added]]]]"#; let SQLQuery { sql, args } = translate(&schema, query); - assert_eq!(sql, "SELECT DISTINCT `transactions01`.e AS `?e`, \ - `transactions01`.a AS `?a`, \ - `transactions01`.v AS `?v`, \ - `transactions01`.value_type_tag AS `?v_value_type_tag`, \ - `transactions00`.tx AS `?tx`, \ - `transactions01`.added AS `?added` \ - FROM `transactions` AS `transactions00`, \ - `transactions` AS `transactions01` \ - WHERE 1000 <= `transactions00`.tx \ - AND `transactions00`.tx < 2000 \ - AND `transactions01`.tx = `transactions00`.tx"); + assert_eq!( + sql, + "SELECT DISTINCT `transactions01`.e AS `?e`, \ + `transactions01`.a AS `?a`, \ + `transactions01`.v AS `?v`, \ + `transactions01`.value_type_tag AS `?v_value_type_tag`, \ + `transactions00`.tx AS `?tx`, \ + `transactions01`.added AS `?added` \ + FROM `transactions` AS `transactions00`, \ + `transactions` AS `transactions01` \ + WHERE 1000 <= `transactions00`.tx \ + AND `transactions00`.tx < 2000 \ + AND `transactions01`.tx = `transactions00`.tx" + ); assert_eq!(args, vec![]); } diff --git a/query-pull-traits/errors.rs b/query-pull-traits/errors.rs index bef70eec..3471b82e 100644 --- a/query-pull-traits/errors.rs +++ b/query-pull-traits/errors.rs @@ -10,13 +10,9 @@ use std; // To refer to std::result::Result. -use db_traits::errors::{ - DbError, -}; +use db_traits::errors::DbError; -use core_traits::{ - Entid, -}; +use core_traits::Entid; pub type Result = std::result::Result; diff --git a/query-pull/Cargo.toml b/query-pull/Cargo.toml index 8b217b4e..b19f0f66 100644 --- a/query-pull/Cargo.toml +++ b/query-pull/Cargo.toml @@ -13,7 +13,7 @@ failure = "0.1.1" path = "../query-pull-traits" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.edn] diff --git a/query-pull/src/lib.rs b/query-pull/src/lib.rs index 658590fa..5c5c4082 100644 --- a/query-pull/src/lib.rs +++ b/query-pull/src/lib.rs @@ -56,85 +56,72 @@ ///! (pull ?person [:person/friend]) /// [*])) ///! ``` - extern crate failure; extern crate rusqlite; +extern crate core_traits; extern crate edn; extern crate mentat_core; -extern crate core_traits; extern crate mentat_db; extern crate query_pull_traits; -use std::collections::{ - BTreeMap, - BTreeSet, -}; +use std::collections::{BTreeMap, BTreeSet}; -use std::iter::{ - once, -}; +use std::iter::once; -use core_traits::{ - Binding, - Entid, - TypedValue, - StructuredMap, -}; +use core_traits::{Binding, Entid, StructuredMap, TypedValue}; -use mentat_core::{ - Cloned, - HasSchema, - Keyword, - Schema, - ValueRc, -}; +use mentat_core::{Cloned, HasSchema, Keyword, Schema, ValueRc}; use mentat_db::cache; -use edn::query::{ - NamedPullAttribute, - PullAttributeSpec, - PullConcreteAttribute, -}; +use edn::query::{NamedPullAttribute, PullAttributeSpec, PullConcreteAttribute}; -use query_pull_traits::errors::{ - PullError, - Result, -}; +use query_pull_traits::errors::{PullError, Result}; type PullResults = BTreeMap>; -pub fn pull_attributes_for_entity(schema: &Schema, - db: &rusqlite::Connection, - entity: Entid, - attributes: A) -> Result - where A: IntoIterator { - let attrs = attributes.into_iter() - .map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into())) - .collect(); +pub fn pull_attributes_for_entity( + schema: &Schema, + db: &rusqlite::Connection, + entity: Entid, + attributes: A, +) -> Result +where + A: IntoIterator, +{ + let attrs = attributes + .into_iter() + .map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into())) + .collect(); Puller::prepare(schema, attrs)? .pull(schema, db, once(entity)) - .map(|m| m.into_iter() - .next() - .map(|(k, vs)| { - assert_eq!(k, entity); - vs.cloned() - }) - .unwrap_or_else(StructuredMap::default)) + .map(|m| { + m.into_iter() + .next() + .map(|(k, vs)| { + assert_eq!(k, entity); + vs.cloned() + }) + .unwrap_or_else(StructuredMap::default) + }) } -pub fn pull_attributes_for_entities(schema: &Schema, - db: &rusqlite::Connection, - entities: E, - attributes: A) -> Result - where E: IntoIterator, - A: IntoIterator { - let attrs = attributes.into_iter() - .map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into())) - .collect(); - Puller::prepare(schema, attrs)? - .pull(schema, db, entities) +pub fn pull_attributes_for_entities( + schema: &Schema, + db: &rusqlite::Connection, + entities: E, + attributes: A, +) -> Result +where + E: IntoIterator, + A: IntoIterator, +{ + 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. @@ -159,9 +146,10 @@ impl Puller { let lookup_name = |i: &Entid| { // In the unlikely event that we have an attribute with no name, we bail. - schema.get_ident(*i) - .map(|ident| ValueRc::new(ident.clone())) - .ok_or_else(|| PullError::UnnamedAttribute(*i)) + schema + .get_ident(*i) + .map(|ident| ValueRc::new(ident.clone())) + .ok_or_else(|| PullError::UnnamedAttribute(*i)) }; let mut names: BTreeMap> = Default::default(); @@ -178,13 +166,12 @@ impl Puller { attrs.insert(*id); } break; - }, + } &PullAttributeSpec::Attribute(NamedPullAttribute { ref attribute, ref alias, }) => { - let alias = alias.as_ref() - .map(|ref r| r.to_value_rc()); + let alias = alias.as_ref().map(|ref r| r.to_value_rc()); match attribute { // Handle :db/id. &PullConcreteAttribute::Ident(ref i) if i.as_ref() == db_id.as_ref() => { @@ -193,21 +180,21 @@ impl Puller { Err(PullError::RepeatedDbId)? } db_id_alias = Some(alias.unwrap_or_else(|| db_id.to_value_rc())); - }, + } &PullConcreteAttribute::Ident(ref i) => { if let Some(entid) = schema.get_entid(i) { let name = alias.unwrap_or_else(|| i.to_value_rc()); names.insert(entid.into(), name); attrs.insert(entid.into()); } - }, + } &PullConcreteAttribute::Entid(ref entid) => { let name = alias.map(Ok).unwrap_or_else(|| lookup_name(entid))?; names.insert(*entid, name); attrs.insert(*entid); - }, + } } - }, + } } } @@ -218,11 +205,15 @@ impl Puller { }) } - pub fn pull(&self, - schema: &Schema, - db: &rusqlite::Connection, - entities: E) -> Result - where E: IntoIterator { + pub fn pull( + &self, + schema: &Schema, + db: &rusqlite::Connection, + entities: E, + ) -> Result + where + E: IntoIterator, + { // We implement pull by: // - 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. @@ -238,7 +229,8 @@ impl Puller { schema, db, self.attribute_spec.clone(), - &entities)?; + &entities, + )?; // Now construct the appropriate result format. // TODO: should we walk `e` then `a`, or `a` then `e`? Possibly the right answer @@ -248,21 +240,24 @@ impl Puller { // Collect :db/id if requested. if let Some(ref alias) = self.db_id_alias { for e in entities.iter() { - let r = maps.entry(*e) - .or_insert(ValueRc::new(StructuredMap::default())); + let r = maps + .entry(*e) + .or_insert(ValueRc::new(StructuredMap::default())); let m = ValueRc::get_mut(r).unwrap(); m.insert(alias.clone(), Binding::Scalar(TypedValue::Ref(*e))); } } - for (name, cache) in self.attributes.iter().filter_map(|(a, name)| - caches.forward_attribute_cache_for_attribute(schema, *a) - .map(|cache| (name.clone(), cache))) { - + for (name, cache) in self.attributes.iter().filter_map(|(a, name)| { + caches + .forward_attribute_cache_for_attribute(schema, *a) + .map(|cache| (name.clone(), cache)) + }) { for e in entities.iter() { if let Some(binding) = cache.binding_for_e(*e) { - let r = maps.entry(*e) - .or_insert(ValueRc::new(StructuredMap::default())); + let r = maps + .entry(*e) + .or_insert(ValueRc::new(StructuredMap::default())); // Get into the inner map so we can accumulate a value. // We can unwrap here because we created all of these maps… @@ -275,5 +270,4 @@ impl Puller { Ok(maps) } - } diff --git a/query-sql/Cargo.toml b/query-sql/Cargo.toml index f44a5a0c..d56c9ffb 100644 --- a/query-sql/Cargo.toml +++ b/query-sql/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.1" workspace = ".." [dependencies] +rusqlite = "0.21.0" [dependencies.edn] path = "../edn" diff --git a/query-sql/src/lib.rs b/query-sql/src/lib.rs index 3aa9a0cf..adcae99e 100644 --- a/query-sql/src/lib.rs +++ b/query-sql/src/lib.rs @@ -8,52 +8,29 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -#[macro_use] extern crate mentat_core; +#[macro_use] +extern crate mentat_core; extern crate core_traits; -extern crate sql_traits; extern crate edn; extern crate mentat_query_algebrizer; extern crate mentat_sql; +extern crate sql_traits; use std::boxed::Box; -use core_traits::{ - Entid, - TypedValue, - ValueType, -}; +use core_traits::{Entid, TypedValue, ValueType}; -use mentat_core::{ - SQLTypeAffinity, -}; +use mentat_core::SQLTypeAffinity; -use edn::query::{ - Direction, - Limit, - Variable, -}; +use edn::query::{Direction, Limit, Variable}; use mentat_query_algebrizer::{ - Column, - OrderBy, - QualifiedAlias, - QueryValue, - SourceAlias, - TableAlias, - VariableColumn, + Column, OrderBy, QualifiedAlias, QueryValue, SourceAlias, TableAlias, VariableColumn, }; -use sql_traits::errors::{ - BuildQueryResult, - SQLError, -}; +use sql_traits::errors::{BuildQueryResult, SQLError}; -use mentat_sql::{ - QueryBuilder, - QueryFragment, - SQLiteQueryBuilder, - SQLQuery, -}; +use mentat_sql::{QueryBuilder, QueryFragment, SQLQuery, SQLiteQueryBuilder}; //--------------------------------------------------------- // A Mentat-focused representation of a SQL query. @@ -67,18 +44,21 @@ use mentat_sql::{ pub enum ColumnOrExpression { Column(QualifiedAlias), ExistingColumn(Name), - Entid(Entid), // Because it's so common. - Integer(i32), // We use these for type codes etc. + Entid(Entid), // Because it's so common. + Integer(i32), // We use these for type codes etc. Long(i64), Value(TypedValue), // Some aggregates (`min`, `max`, `avg`) can be over 0 rows, and therefore can be `NULL`; that // needs special treatment. - NullableAggregate(Box, ValueType), // Track the return type. - Expression(Box, ValueType), // Track the return type. + NullableAggregate(Box, ValueType), // Track the return type. + Expression(Box, ValueType), // Track the return type. } pub enum Expression { - Unary { sql_op: &'static str, arg: ColumnOrExpression }, + Unary { + sql_op: &'static str, + arg: ColumnOrExpression, + }, } /// `QueryValue` and `ColumnOrExpression` are almost identical… merge somehow? @@ -111,20 +91,16 @@ pub enum GroupBy { } impl QueryFragment for GroupBy { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { match self { - &GroupBy::ProjectedColumn(ref name) => { - out.push_identifier(name.as_str()) - }, - &GroupBy::QueryColumn(ref qa) => { - qualified_alias_push_sql(out, qa) - }, + &GroupBy::ProjectedColumn(ref name) => out.push_identifier(name.as_str()), + &GroupBy::QueryColumn(ref qa) => qualified_alias_push_sql(out, qa), } } } #[derive(Copy, Clone)] -pub struct Op(pub &'static str); // TODO: we can do better than this! +pub struct Op(pub &'static str); // TODO: we can do better than this! pub enum Constraint { Infix { @@ -153,14 +129,14 @@ pub enum Constraint { }, TypeCheck { value: ColumnOrExpression, - affinity: SQLTypeAffinity - } + affinity: SQLTypeAffinity, + }, } impl Constraint { pub fn not_equal(left: ColumnOrExpression, right: ColumnOrExpression) -> Constraint { Constraint::Infix { - op: Op("<>"), // ANSI SQL for future-proofing! + op: Op("<>"), // ANSI SQL for future-proofing! left: left, right: right, } @@ -224,7 +200,7 @@ pub enum Values { } pub enum FromClause { - TableList(TableList), // Short-hand for a pile of inner joins. + TableList(TableList), // Short-hand for a pile of inner joins. Join(Join), Nothing, } @@ -239,32 +215,30 @@ pub struct SelectQuery { pub limit: Limit, } -fn push_variable_column(qb: &mut QueryBuilder, vc: &VariableColumn) -> BuildQueryResult { +fn push_variable_column(qb: &mut dyn QueryBuilder, vc: &VariableColumn) -> BuildQueryResult { match vc { - &VariableColumn::Variable(ref v) => { - qb.push_identifier(v.as_str()) - }, + &VariableColumn::Variable(ref v) => qb.push_identifier(v.as_str()), &VariableColumn::VariableTypeTag(ref v) => { qb.push_identifier(format!("{}_value_type_tag", v.name()).as_str()) - }, + } } } -fn push_column(qb: &mut QueryBuilder, col: &Column) -> BuildQueryResult { +fn push_column(qb: &mut dyn QueryBuilder, col: &Column) -> BuildQueryResult { match col { &Column::Fixed(ref d) => { qb.push_sql(d.as_str()); Ok(()) - }, + } &Column::Fulltext(ref d) => { qb.push_sql(d.as_str()); Ok(()) - }, + } &Column::Variable(ref vc) => push_variable_column(qb, vc), &Column::Transactions(ref d) => { qb.push_sql(d.as_str()); Ok(()) - }, + } } } @@ -272,54 +246,48 @@ fn push_column(qb: &mut QueryBuilder, col: &Column) -> BuildQueryResult { // Turn that representation into SQL. impl QueryFragment for ColumnOrExpression { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { use self::ColumnOrExpression::*; match self { - &Column(ref qa) => { - qualified_alias_push_sql(out, qa) - }, - &ExistingColumn(ref alias) => { - out.push_identifier(alias.as_str()) - }, + &Column(ref qa) => qualified_alias_push_sql(out, qa), + &ExistingColumn(ref alias) => out.push_identifier(alias.as_str()), &Entid(entid) => { out.push_sql(entid.to_string().as_str()); Ok(()) - }, + } &Integer(integer) => { out.push_sql(integer.to_string().as_str()); Ok(()) - }, + } &Long(long) => { out.push_sql(long.to_string().as_str()); Ok(()) - }, - &Value(ref v) => { - out.push_typed_value(v) - }, - &NullableAggregate(ref e, _) | - &Expression(ref e, _) => { - e.push_sql(out) - }, + } + &Value(ref v) => out.push_typed_value(v), + &NullableAggregate(ref e, _) | &Expression(ref e, _) => e.push_sql(out), } } } impl QueryFragment for Expression { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { match self { - &Expression::Unary { ref sql_op, ref arg } => { - out.push_sql(sql_op); // No need to escape built-ins. + &Expression::Unary { + ref sql_op, + ref arg, + } => { + out.push_sql(sql_op); // No need to escape built-ins. out.push_sql("("); arg.push_sql(out)?; out.push_sql(")"); Ok(()) - }, + } } } } impl QueryFragment for Projection { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { use self::Projection::*; match self { &One => out.push_sql("1"), @@ -336,14 +304,14 @@ impl QueryFragment for Projection { out.push_sql(" AS "); out.push_identifier(alias.as_str())?; } - }, + } }; Ok(()) } } impl QueryFragment for Op { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { // No escaping needed. out.push_sql(self.0); Ok(()) @@ -351,53 +319,57 @@ impl QueryFragment for Op { } impl QueryFragment for Constraint { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { use self::Constraint::*; match self { - &Infix { ref op, ref left, ref right } => { + &Infix { + ref op, + ref left, + ref right, + } => { left.push_sql(out)?; out.push_sql(" "); op.push_sql(out)?; out.push_sql(" "); right.push_sql(out) - }, + } &IsNull { ref value } => { value.push_sql(out)?; out.push_sql(" IS NULL"); Ok(()) - }, + } &IsNotNull { ref value } => { value.push_sql(out)?; out.push_sql(" IS NOT NULL"); Ok(()) - }, + } &And { ref constraints } => { // An empty intersection is true. if constraints.is_empty() { out.push_sql("1"); - return Ok(()) + return Ok(()); } out.push_sql("("); - interpose!(constraint, constraints, - { constraint.push_sql(out)? }, - { out.push_sql(" AND ") }); + interpose!(constraint, constraints, { constraint.push_sql(out)? }, { + out.push_sql(" AND ") + }); out.push_sql(")"); Ok(()) - }, + } &Or { ref constraints } => { // An empty alternation is false. if constraints.is_empty() { out.push_sql("0"); - return Ok(()) + return Ok(()); } out.push_sql("("); - interpose!(constraint, constraints, - { constraint.push_sql(out)? }, - { out.push_sql(" OR ") }); + interpose!(constraint, constraints, { constraint.push_sql(out)? }, { + out.push_sql(" OR ") + }); out.push_sql(")"); Ok(()) } @@ -405,17 +377,18 @@ impl QueryFragment for Constraint { &In { ref left, ref list } => { left.push_sql(out)?; out.push_sql(" IN ("); - interpose!(item, list, - { item.push_sql(out)? }, - { out.push_sql(", ") }); + interpose!(item, list, { item.push_sql(out)? }, { out.push_sql(", ") }); out.push_sql(")"); Ok(()) - }, + } &NotExists { ref subquery } => { out.push_sql("NOT EXISTS "); subquery.push_sql(out) - }, - &TypeCheck { ref value, ref affinity } => { + } + &TypeCheck { + ref value, + ref affinity, + } => { out.push_sql("typeof("); value.push_sql(out)?; out.push_sql(") = "); @@ -427,27 +400,27 @@ impl QueryFragment for Constraint { SQLTypeAffinity::Blob => "'blob'", }); Ok(()) - }, + } } } } impl QueryFragment for JoinOp { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { out.push_sql(" JOIN "); Ok(()) } } // We don't own QualifiedAlias or QueryFragment, so we can't implement the trait. -fn qualified_alias_push_sql(out: &mut QueryBuilder, qa: &QualifiedAlias) -> BuildQueryResult { +fn qualified_alias_push_sql(out: &mut dyn QueryBuilder, qa: &QualifiedAlias) -> BuildQueryResult { out.push_identifier(qa.0.as_str())?; out.push_sql("."); push_column(out, &qa.1) } // We don't own SourceAlias or QueryFragment, so we can't implement the trait. -fn source_alias_push_sql(out: &mut QueryBuilder, sa: &SourceAlias) -> BuildQueryResult { +fn source_alias_push_sql(out: &mut dyn QueryBuilder, sa: &SourceAlias) -> BuildQueryResult { let &SourceAlias(ref table, ref alias) = sa; out.push_identifier(table.name())?; out.push_sql(" AS "); @@ -455,20 +428,18 @@ fn source_alias_push_sql(out: &mut QueryBuilder, sa: &SourceAlias) -> BuildQuery } impl QueryFragment for TableList { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { if self.0.is_empty() { return Ok(()); } - interpose!(t, self.0, - { t.push_sql(out)? }, - { out.push_sql(", ") }); + interpose!(t, self.0, { t.push_sql(out)? }, { out.push_sql(", ") }); Ok(()) } } impl QueryFragment for Join { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { self.left.push_sql(out)?; self.op.push_sql(out)?; self.right.push_sql(out) @@ -476,37 +447,37 @@ impl QueryFragment for Join { } impl QueryFragment for TableOrSubquery { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { use self::TableOrSubquery::*; match self { &Table(ref sa) => source_alias_push_sql(out, sa), &Union(ref subqueries, ref table_alias) => { out.push_sql("("); - interpose!(subquery, subqueries, - { subquery.push_sql(out)? }, - { out.push_sql(" UNION ") }); + interpose!(subquery, subqueries, { subquery.push_sql(out)? }, { + out.push_sql(" UNION ") + }); out.push_sql(") AS "); out.push_identifier(table_alias.as_str()) - }, + } &Subquery(ref subquery) => { out.push_sql("("); subquery.push_sql(out)?; out.push_sql(")"); Ok(()) - }, + } &Values(ref values, ref table_alias) => { // XXX: does this work for Values::Unnamed? out.push_sql("("); values.push_sql(out)?; out.push_sql(") AS "); out.push_identifier(table_alias.as_str()) - }, + } } } } impl QueryFragment for Values { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { // There are at least 3 ways to name the columns of a VALUES table: // 1) the columns are named "", ":1", ":2", ... -- but this is undocumented. See // http://stackoverflow.com/a/40921724. @@ -519,10 +490,15 @@ impl QueryFragment for Values { // place table to query, so for now we implement option 3. if let &Values::Named(ref names, _) = self { out.push_sql("SELECT "); - interpose!(alias, names, - { out.push_sql("0 AS "); - out.push_identifier(alias.as_str())? }, - { out.push_sql(", ") }); + interpose!( + alias, + names, + { + out.push_sql("0 AS "); + out.push_identifier(alias.as_str())? + }, + { out.push_sql(", ") } + ); out.push_sql(" WHERE 0 UNION ALL "); } @@ -534,20 +510,24 @@ impl QueryFragment for Values { out.push_sql("VALUES "); - interpose_iter!(outer, values, - { out.push_sql("("); - interpose!(inner, outer, - { out.push_typed_value(inner)? }, - { out.push_sql(", ") }); - out.push_sql(")"); - }, - { out.push_sql(", ") }); + interpose_iter!( + outer, + values, + { + out.push_sql("("); + interpose!(inner, outer, { out.push_typed_value(inner)? }, { + out.push_sql(", ") + }); + out.push_sql(")"); + }, + { out.push_sql(", ") } + ); Ok(()) } } impl QueryFragment for FromClause { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { use self::FromClause::*; match self { &TableList(ref table_list) => { @@ -557,11 +537,11 @@ impl QueryFragment for FromClause { out.push_sql(" FROM "); table_list.push_sql(out) } - }, + } &Join(ref join) => { out.push_sql(" FROM "); join.push_sql(out) - }, + } &Nothing => Ok(()), } } @@ -573,21 +553,23 @@ impl QueryFragment for FromClause { fn format_select_var(var: &str) -> String { use std::iter::once; let without_question = var.split_at(1).1; - let replaced_iter = without_question.chars().map(|c| - if c.is_ascii_alphanumeric() { c } else { '_' }); + let replaced_iter = + without_question + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }); // Prefix with `i` (Avoiding this copy is probably not worth the trouble but whatever). once('i').chain(replaced_iter).collect() } impl SelectQuery { - fn push_variable_param(&self, var: &Variable, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_variable_param(&self, var: &Variable, out: &mut dyn QueryBuilder) -> BuildQueryResult { let bind_param = format_select_var(var.as_str()); out.push_bind_param(bind_param.as_str()) } } impl QueryFragment for SelectQuery { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { if self.distinct { out.push_sql("SELECT DISTINCT "); } else { @@ -598,31 +580,42 @@ impl QueryFragment for SelectQuery { if !self.constraints.is_empty() { out.push_sql(" WHERE "); - interpose!(constraint, self.constraints, - { constraint.push_sql(out)? }, - { out.push_sql(" AND ") }); + interpose!( + constraint, + self.constraints, + { constraint.push_sql(out)? }, + { out.push_sql(" AND ") } + ); } match &self.group_by { group_by if !group_by.is_empty() => { out.push_sql(" GROUP BY "); - interpose!(group, group_by, - { group.push_sql(out)? }, - { out.push_sql(", ") }); - }, - _ => {}, + interpose!(group, group_by, { group.push_sql(out)? }, { + out.push_sql(", ") + }); + } + _ => {} } if !self.order.is_empty() { out.push_sql(" ORDER BY "); - interpose!(&OrderBy(ref dir, ref var), self.order, - { push_variable_column(out, var)?; - match dir { - &Direction::Ascending => { out.push_sql(" ASC"); }, - &Direction::Descending => { out.push_sql(" DESC"); }, - }; - }, - { out.push_sql(", ") }); + interpose!( + &OrderBy(ref dir, ref var), + self.order, + { + push_variable_column(out, var)?; + match dir { + &Direction::Ascending => { + out.push_sql(" ASC"); + } + &Direction::Descending => { + out.push_sql(" DESC"); + } + }; + }, + { out.push_sql(", ") } + ); } match &self.limit { @@ -631,12 +624,12 @@ impl QueryFragment for SelectQuery { // Guaranteed to be non-negative: u64. out.push_sql(" LIMIT "); out.push_sql(limit.to_string().as_str()); - }, + } &Limit::Variable(ref var) => { // Guess this wasn't bound yet. Produce an argument. out.push_sql(" LIMIT "); self.push_variable_param(var, out)?; - }, + } } Ok(()) @@ -655,40 +648,42 @@ mod tests { use super::*; use std::rc::Rc; - use mentat_query_algebrizer::{ - Column, - DatomsColumn, - DatomsTable, - FulltextColumn, - }; + use mentat_query_algebrizer::{Column, DatomsColumn, DatomsTable, FulltextColumn}; - fn build_query(c: &QueryFragment) -> SQLQuery { + fn build_query(c: &dyn QueryFragment) -> SQLQuery { let mut builder = SQLiteQueryBuilder::new(); c.push_sql(&mut builder) - .map(|_| builder.finish()) - .expect("to produce a query for the given constraint") + .map(|_| builder.finish()) + .expect("to produce a query for the given constraint") } - fn build(c: &QueryFragment) -> String { + fn build(c: &dyn QueryFragment) -> String { build_query(c).sql } #[test] fn test_in_constraint() { let none = Constraint::In { - left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), Column::Fixed(DatomsColumn::Value))), + left: ColumnOrExpression::Column(QualifiedAlias::new( + "datoms01".to_string(), + Column::Fixed(DatomsColumn::Value), + )), list: vec![], }; let one = Constraint::In { - left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Value)), - list: vec![ - ColumnOrExpression::Entid(123), - ], + left: ColumnOrExpression::Column(QualifiedAlias::new( + "datoms01".to_string(), + DatomsColumn::Value, + )), + list: vec![ColumnOrExpression::Entid(123)], }; let three = Constraint::In { - left: ColumnOrExpression::Column(QualifiedAlias::new("datoms01".to_string(), DatomsColumn::Value)), + left: ColumnOrExpression::Column(QualifiedAlias::new( + "datoms01".to_string(), + DatomsColumn::Value, + )), list: vec![ ColumnOrExpression::Entid(123), ColumnOrExpression::Entid(456), @@ -704,22 +699,20 @@ mod tests { #[test] fn test_and_constraint() { let c = Constraint::And { - constraints: vec![ - Constraint::And { - constraints: vec![ - Constraint::Infix { - op: Op("="), - left: ColumnOrExpression::Entid(123), - right: ColumnOrExpression::Entid(456), - }, - Constraint::Infix { - op: Op("="), - left: ColumnOrExpression::Entid(789), - right: ColumnOrExpression::Entid(246), - }, - ], - }, - ], + constraints: vec![Constraint::And { + constraints: vec![ + Constraint::Infix { + op: Op("="), + left: ColumnOrExpression::Entid(123), + right: ColumnOrExpression::Entid(456), + }, + Constraint::Infix { + op: Op("="), + left: ColumnOrExpression::Entid(789), + right: ColumnOrExpression::Entid(246), + }, + ], + }], }; // Two sets of parens: the outermost AND only has one child, @@ -731,47 +724,92 @@ mod tests { fn test_unnamed_values() { let build = |len, values| build(&Values::Unnamed(len, values)); - assert_eq!(build(1, vec![TypedValue::Long(1)]), - "VALUES (1)"); + assert_eq!(build(1, vec![TypedValue::Long(1)]), "VALUES (1)"); - assert_eq!(build(2, vec![TypedValue::Boolean(false), TypedValue::Long(1)]), - "VALUES (0, 1)"); + assert_eq!( + build(2, vec![TypedValue::Boolean(false), TypedValue::Long(1)]), + "VALUES (0, 1)" + ); - assert_eq!(build(2, vec![TypedValue::Boolean(false), TypedValue::Long(1), - TypedValue::Boolean(true), TypedValue::Long(2)]), - "VALUES (0, 1), (1, 2)"); + assert_eq!( + build( + 2, + vec![ + TypedValue::Boolean(false), + TypedValue::Long(1), + TypedValue::Boolean(true), + TypedValue::Long(2) + ] + ), + "VALUES (0, 1), (1, 2)" + ); } #[test] fn test_named_values() { - let build = |names: Vec<_>, values| build(&Values::Named(names.into_iter().map(Variable::from_valid_name).collect(), values)); - assert_eq!(build(vec!["?a"], vec![TypedValue::Long(1)]), - "SELECT 0 AS `?a` WHERE 0 UNION ALL VALUES (1)"); + let build = |names: Vec<_>, values| { + build(&Values::Named( + names.into_iter().map(Variable::from_valid_name).collect(), + values, + )) + }; + assert_eq!( + build(vec!["?a"], vec![TypedValue::Long(1)]), + "SELECT 0 AS `?a` WHERE 0 UNION ALL VALUES (1)" + ); - assert_eq!(build(vec!["?a", "?b"], vec![TypedValue::Boolean(false), TypedValue::Long(1)]), - "SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1)"); + assert_eq!( + build( + vec!["?a", "?b"], + vec![TypedValue::Boolean(false), TypedValue::Long(1)] + ), + "SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1)" + ); - assert_eq!(build(vec!["?a", "?b"], - vec![TypedValue::Boolean(false), TypedValue::Long(1), - TypedValue::Boolean(true), TypedValue::Long(2)]), - "SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1), (1, 2)"); + assert_eq!( + build( + vec!["?a", "?b"], + vec![ + TypedValue::Boolean(false), + TypedValue::Long(1), + TypedValue::Boolean(true), + TypedValue::Long(2) + ] + ), + "SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1), (1, 2)" + ); } #[test] fn test_matches_constraint() { let c = Constraint::Infix { op: Op("MATCHES"), - left: ColumnOrExpression::Column(QualifiedAlias("fulltext01".to_string(), Column::Fulltext(FulltextColumn::Text))), + left: ColumnOrExpression::Column(QualifiedAlias( + "fulltext01".to_string(), + Column::Fulltext(FulltextColumn::Text), + )), right: ColumnOrExpression::Value("needle".into()), }; let q = build_query(&c); assert_eq!("`fulltext01`.text MATCHES $v0", q.sql); - assert_eq!(vec![("$v0".to_string(), Rc::new(mentat_sql::Value::Text("needle".to_string())))], q.args); + assert_eq!( + vec![( + "$v0".to_string(), + Rc::new(rusqlite::types::Value::Text("needle".to_string())) + )], + q.args + ); let c = Constraint::Infix { op: Op("="), - left: ColumnOrExpression::Column(QualifiedAlias("fulltext01".to_string(), Column::Fulltext(FulltextColumn::Rowid))), - right: ColumnOrExpression::Column(QualifiedAlias("datoms02".to_string(), Column::Fixed(DatomsColumn::Value))), + left: ColumnOrExpression::Column(QualifiedAlias( + "fulltext01".to_string(), + Column::Fulltext(FulltextColumn::Rowid), + )), + right: ColumnOrExpression::Column(QualifiedAlias( + "datoms02".to_string(), + Column::Fixed(DatomsColumn::Value), + )), }; assert_eq!("`fulltext01`.rowid = `datoms02`.v", build(&c)); } @@ -789,27 +827,40 @@ mod tests { let mut query = SelectQuery { distinct: true, - projection: Projection::Columns( - vec![ - ProjectedColumn( - ColumnOrExpression::Column(QualifiedAlias::new(datoms00.clone(), DatomsColumn::Entity)), - "x".to_string()), - ]), + projection: Projection::Columns(vec![ProjectedColumn( + ColumnOrExpression::Column(QualifiedAlias::new( + datoms00.clone(), + DatomsColumn::Entity, + )), + "x".to_string(), + )]), from: FromClause::TableList(TableList(source_aliases)), constraints: vec![ Constraint::Infix { op: eq.clone(), - left: ColumnOrExpression::Column(QualifiedAlias::new(datoms01.clone(), DatomsColumn::Value)), - right: ColumnOrExpression::Column(QualifiedAlias::new(datoms00.clone(), DatomsColumn::Value)), + left: ColumnOrExpression::Column(QualifiedAlias::new( + datoms01.clone(), + DatomsColumn::Value, + )), + right: ColumnOrExpression::Column(QualifiedAlias::new( + datoms00.clone(), + DatomsColumn::Value, + )), }, Constraint::Infix { op: eq.clone(), - left: ColumnOrExpression::Column(QualifiedAlias::new(datoms00.clone(), DatomsColumn::Attribute)), + left: ColumnOrExpression::Column(QualifiedAlias::new( + datoms00.clone(), + DatomsColumn::Attribute, + )), right: ColumnOrExpression::Entid(65537), }, Constraint::Infix { op: eq.clone(), - left: ColumnOrExpression::Column(QualifiedAlias::new(datoms01.clone(), DatomsColumn::Attribute)), + left: ColumnOrExpression::Column(QualifiedAlias::new( + datoms01.clone(), + DatomsColumn::Attribute, + )), right: ColumnOrExpression::Entid(65536), }, ], @@ -829,7 +880,6 @@ mod tests { println!("{}", sql); assert_eq!("SELECT `datoms00`.e AS `x` FROM `datoms` AS `datoms00`, `datoms` AS `datoms01` WHERE `datoms01`.v = `datoms00`.v AND `datoms00`.a = 65537 AND `datoms01`.a = 65536", sql); assert!(args.is_empty()); - } #[test] diff --git a/sql-traits/errors.rs b/sql-traits/errors.rs index e57c674f..d51b8838 100644 --- a/sql-traits/errors.rs +++ b/sql-traits/errors.rs @@ -14,7 +14,7 @@ pub enum SQLError { InvalidParameterName(String), #[fail(display = "parameter name could be generated: '{}'", _0)] - BindParamCouldBeGenerated(String) + BindParamCouldBeGenerated(String), } pub type BuildQueryResult = Result<(), SQLError>; diff --git a/sql/Cargo.toml b/sql/Cargo.toml index 4aa1bfa6..5d6d1982 100644 --- a/sql/Cargo.toml +++ b/sql/Cargo.toml @@ -8,10 +8,10 @@ sqlcipher = ["rusqlite/sqlcipher"] [dependencies] failure = "0.1.1" -ordered-float = "0.5" +ordered-float = "1.0.2" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.core_traits] diff --git a/sql/src/lib.rs b/sql/src/lib.rs index ff3dd16c..91a10de1 100644 --- a/sql/src/lib.rs +++ b/sql/src/lib.rs @@ -14,8 +14,8 @@ extern crate ordered_float; extern crate rusqlite; extern crate core_traits; -extern crate sql_traits; extern crate mentat_core; +extern crate sql_traits; use std::rc::Rc; @@ -23,19 +23,11 @@ use std::collections::HashMap; use ordered_float::OrderedFloat; -use core_traits::{ - TypedValue, -}; +use core_traits::TypedValue; -use sql_traits::errors::{ - BuildQueryResult, - SQLError, -}; +use sql_traits::errors::{BuildQueryResult, SQLError}; -use mentat_core::{ - ToMicros, - ValueRc, -}; +use mentat_core::{ToMicros, ValueRc}; /// We want to accumulate values that will later be substituted into a SQL statement execution. /// This struct encapsulates the generated string and the _initial_ argument list. @@ -59,23 +51,23 @@ pub trait QueryBuilder { } pub trait QueryFragment { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult; + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult; } -impl QueryFragment for Box { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { +impl QueryFragment for Box { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { QueryFragment::push_sql(&**self, out) } } -impl<'a> QueryFragment for &'a QueryFragment { - fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { +impl<'a> QueryFragment for &'a dyn QueryFragment { + fn push_sql(&self, out: &mut dyn QueryBuilder) -> BuildQueryResult { QueryFragment::push_sql(&**self, out) } } impl QueryFragment for () { - fn push_sql(&self, _out: &mut QueryBuilder) -> BuildQueryResult { + fn push_sql(&self, _out: &mut dyn QueryBuilder) -> BuildQueryResult { Ok(()) } } @@ -89,10 +81,10 @@ pub struct SQLiteQueryBuilder { // We can't just use an InternSet on the rusqlite::types::Value instances, because that // includes f64, so it's not Hash or Eq. - // Instead we track byte and String arguments separately, mapping them to their argument name, + // Instead, we track the byte and String arguments separately, mapping them to their argument name, // in order to dedupe. We'll add these to the regular argument vector later. - byte_args: HashMap, String>, // From value to argument name. - string_args: HashMap, String>, // From value to argument name. + byte_args: HashMap, String>, // From value to argument name. + string_args: HashMap, String>, // From value to argument name. args: Vec<(String, Rc)>, // (arg, value). } @@ -156,20 +148,21 @@ impl QueryBuilder for SQLiteQueryBuilder { // floats and not integers. This is most noticeable for fulltext scores, which // will currently (2017-06) always be 0, and need to round-trip as doubles. self.push_sql(format!("{:e}", v).as_str()); - }, + } &Instant(dt) => { - self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead? - }, + self.push_sql(format!("{}", dt.to_micros()).as_str()); // TODO: argument instead? + } &Uuid(ref u) => { let bytes = u.as_bytes(); - if let Some(arg) = self.byte_args.get(bytes.as_ref()).cloned() { // Why, borrow checker, why?! + if let Some(arg) = self.byte_args.get(bytes.as_ref()).cloned() { + // Why, borrow checker, why?! self.push_named_arg(arg.as_str()); } else { let arg = self.next_argument_name(); self.push_named_arg(arg.as_str()); self.byte_args.insert(bytes.clone().to_vec(), arg); } - }, + } // These are both `Rc`. Unfortunately, we can't use that fact when // turning these into rusqlite Values. // However, we can check to see whether there's an existing var that matches… @@ -181,12 +174,12 @@ impl QueryBuilder for SQLiteQueryBuilder { self.push_named_arg(arg.as_str()); self.string_args.insert(s.clone(), arg); } - }, + } &Keyword(ref s) => { // TODO: intern. let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string())); self.push_static_arg(v); - }, + } } Ok(()) } @@ -201,12 +194,16 @@ impl QueryBuilder for SQLiteQueryBuilder { // Do some validation first. // This is not free, but it's probably worth it for now. if !name.chars().all(|c| char::is_alphanumeric(c) || c == '_') { - return Err(SQLError::InvalidParameterName(name.to_string())) + return Err(SQLError::InvalidParameterName(name.to_string())); } - if name.starts_with(self.arg_prefix.as_str()) && - name.chars().skip(self.arg_prefix.len()).all(char::is_numeric) { - return Err(SQLError::BindParamCouldBeGenerated(name.to_string())) + if name.starts_with(self.arg_prefix.as_str()) + && name + .chars() + .skip(self.arg_prefix.len()) + .all(char::is_numeric) + { + return Err(SQLError::BindParamCouldBeGenerated(name.to_string())); } self.push_sql("$"); @@ -219,11 +216,15 @@ impl QueryBuilder for SQLiteQueryBuilder { // dedupe them. Now we need to turn them into rusqlite Values. let mut args = self.args; let string_args = self.string_args.into_iter().map(|(val, arg)| { - (arg, Rc::new(rusqlite::types::Value::Text(val.as_ref().clone()))) - }); - let byte_args = self.byte_args.into_iter().map(|(val, arg)| { - (arg, Rc::new(rusqlite::types::Value::Blob(val))) + ( + arg, + Rc::new(rusqlite::types::Value::Text(val.as_ref().clone())), + ) }); + let byte_args = self + .byte_args + .into_iter() + .map(|(val, arg)| (arg, Rc::new(rusqlite::types::Value::Blob(val)))); args.extend(string_args); args.extend(byte_args); @@ -262,9 +263,16 @@ mod tests { s.push_typed_value(&TypedValue::Double(1.0.into())).unwrap(); let q = s.finish(); - assert_eq!(q.sql.as_str(), "SELECT `foo` WHERE `bar` = $v0 OR $v1 OR `bar` = 1e0"); - assert_eq!(q.args, - vec![("$v0".to_string(), string_arg("frobnicate")), - ("$v1".to_string(), string_arg("swoogle"))]); + assert_eq!( + q.sql.as_str(), + "SELECT `foo` WHERE `bar` = $v0 OR $v1 OR `bar` = 1e0" + ); + assert_eq!( + q.args, + vec![ + ("$v0".to_string(), string_arg("frobnicate")), + ("$v1".to_string(), string_arg("swoogle")) + ] + ); } } diff --git a/src/conn.rs b/src/conn.rs index f8d28fe1..57a89e70 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -10,86 +10,37 @@ #![allow(dead_code)] -use std::borrow::{ - Borrow, -}; +use std::borrow::Borrow; -use std::collections::{ - BTreeMap, -}; +use std::collections::BTreeMap; -use std::sync::{ - Arc, - Mutex, -}; +use std::sync::{Arc, Mutex}; use rusqlite; -use rusqlite::{ - TransactionBehavior, -}; +use rusqlite::TransactionBehavior; use edn; -pub use core_traits::{ - Attribute, - Entid, - KnownEntid, - StructuredMap, - TypedValue, - ValueType, -}; +pub use core_traits::{Attribute, Entid, KnownEntid, StructuredMap, TypedValue, ValueType}; -use mentat_core::{ - HasSchema, - Keyword, - Schema, - TxReport, - ValueRc, -}; +use mentat_core::{HasSchema, Keyword, Schema, TxReport, ValueRc}; -use mentat_db::cache::{ - InProgressSQLiteAttributeCache, - SQLiteAttributeCache, -}; +use mentat_db::cache::{InProgressSQLiteAttributeCache, SQLiteAttributeCache}; use mentat_db::db; use mentat_db::{ - InProgressObserverTransactWatcher, - PartitionMap, - TxObservationService, - TxObserver, + InProgressObserverTransactWatcher, PartitionMap, TxObservationService, TxObserver, }; -use mentat_query_pull::{ - pull_attributes_for_entities, - pull_attributes_for_entity, -}; +use mentat_query_pull::{pull_attributes_for_entities, pull_attributes_for_entity}; -use mentat_transaction::{ - CacheAction, - CacheDirection, - Metadata, - InProgress, - InProgressRead, -}; +use mentat_transaction::{CacheAction, CacheDirection, InProgress, InProgressRead, Metadata}; -use public_traits::errors::{ - Result, - MentatError, -}; +use public_traits::errors::{MentatError, Result}; use mentat_transaction::query::{ - Known, - PreparedResult, - QueryExplanation, - QueryInputs, - QueryOutput, - lookup_value_for_attribute, - lookup_values_for_attribute, - q_explain, - q_once, - q_prepare, - q_uncached, + lookup_value_for_attribute, lookup_values_for_attribute, q_explain, q_once, q_prepare, + q_uncached, Known, PreparedResult, QueryExplanation, QueryInputs, QueryOutput, }; /// A mutable, safe reference to the current Mentat store. @@ -122,7 +73,12 @@ impl Conn { // Intentionally not public. fn new(partition_map: PartitionMap, schema: Schema) -> Conn { Conn { - metadata: Mutex::new(Metadata::new(0, partition_map, Arc::new(schema), Default::default())), + metadata: Mutex::new(Metadata::new( + 0, + partition_map, + Arc::new(schema), + Default::default(), + )), tx_observer_service: Mutex::new(TxObservationService::new()), } } @@ -161,118 +117,138 @@ impl Conn { } /// Query the Mentat store, using the given connection and the current metadata. - pub fn q_once(&self, - sqlite: &rusqlite::Connection, - query: &str, - inputs: T) -> Result - where T: Into> { - + pub fn q_once( + &self, + sqlite: &rusqlite::Connection, + query: &str, + inputs: T, + ) -> Result + where + T: Into>, + { // Doesn't clone, unlike `current_schema`. let metadata = self.metadata.lock().unwrap(); let known = Known::new(&*metadata.schema, Some(&metadata.attribute_cache)); - q_once(sqlite, - known, - query, - inputs) + q_once(sqlite, known, query, inputs) } /// Query the Mentat store, using the given connection and the current metadata, /// but without using the cache. - pub fn q_uncached(&self, - sqlite: &rusqlite::Connection, - query: &str, - inputs: T) -> Result - where T: Into> { - + pub fn q_uncached( + &self, + sqlite: &rusqlite::Connection, + query: &str, + inputs: T, + ) -> Result + where + T: Into>, + { let metadata = self.metadata.lock().unwrap(); - q_uncached(sqlite, - &*metadata.schema, // Doesn't clone, unlike `current_schema`. - query, - inputs) + q_uncached( + sqlite, + &*metadata.schema, // Doesn't clone, unlike `current_schema`. + query, + inputs, + ) } - pub fn q_prepare<'sqlite, 'query, T>(&self, - sqlite: &'sqlite rusqlite::Connection, - query: &'query str, - inputs: T) -> PreparedResult<'sqlite> - where T: Into> { - - let metadata = self.metadata.lock().unwrap(); - let known = Known::new(&*metadata.schema, Some(&metadata.attribute_cache)); - q_prepare(sqlite, - known, - query, - inputs) - } - - pub fn q_explain(&self, - sqlite: &rusqlite::Connection, - query: &str, - inputs: T) -> Result - where T: Into> + pub fn q_prepare<'sqlite, 'query, T>( + &self, + sqlite: &'sqlite rusqlite::Connection, + query: &'query str, + inputs: T, + ) -> PreparedResult<'sqlite> + where + T: Into>, { let metadata = self.metadata.lock().unwrap(); let known = Known::new(&*metadata.schema, Some(&metadata.attribute_cache)); - q_explain(sqlite, - known, - query, - inputs) + q_prepare(sqlite, known, query, inputs) } - pub fn pull_attributes_for_entities(&self, - sqlite: &rusqlite::Connection, - entities: E, - attributes: A) -> Result>> - where E: IntoIterator, - A: IntoIterator { + pub fn q_explain( + &self, + sqlite: &rusqlite::Connection, + query: &str, + inputs: T, + ) -> Result + where + T: Into>, + { + let metadata = self.metadata.lock().unwrap(); + let known = Known::new(&*metadata.schema, Some(&metadata.attribute_cache)); + q_explain(sqlite, known, query, inputs) + } + + pub fn pull_attributes_for_entities( + &self, + sqlite: &rusqlite::Connection, + entities: E, + attributes: A, + ) -> Result>> + where + E: IntoIterator, + A: IntoIterator, + { let metadata = self.metadata.lock().unwrap(); let schema = &*metadata.schema; - pull_attributes_for_entities(schema, sqlite, entities, attributes) - .map_err(|e| e.into()) + pull_attributes_for_entities(schema, sqlite, entities, attributes).map_err(|e| e.into()) } - pub fn pull_attributes_for_entity(&self, - sqlite: &rusqlite::Connection, - entity: Entid, - attributes: A) -> Result - where A: IntoIterator { + pub fn pull_attributes_for_entity( + &self, + sqlite: &rusqlite::Connection, + entity: Entid, + attributes: A, + ) -> Result + where + A: IntoIterator, + { let metadata = self.metadata.lock().unwrap(); let schema = &*metadata.schema; - pull_attributes_for_entity(schema, sqlite, entity, attributes) - .map_err(|e| e.into()) + pull_attributes_for_entity(schema, sqlite, entity, attributes).map_err(|e| e.into()) } - pub fn lookup_values_for_attribute(&self, - sqlite: &rusqlite::Connection, - entity: Entid, - attribute: &edn::Keyword) -> Result> { + pub fn lookup_values_for_attribute( + &self, + sqlite: &rusqlite::Connection, + entity: Entid, + attribute: &edn::Keyword, + ) -> Result> { let metadata = self.metadata.lock().unwrap(); let known = Known::new(&*metadata.schema, Some(&metadata.attribute_cache)); lookup_values_for_attribute(sqlite, known, entity, attribute) } - pub fn lookup_value_for_attribute(&self, - sqlite: &rusqlite::Connection, - entity: Entid, - attribute: &edn::Keyword) -> Result> { + pub fn lookup_value_for_attribute( + &self, + sqlite: &rusqlite::Connection, + entity: Entid, + attribute: &edn::Keyword, + ) -> Result> { let metadata = self.metadata.lock().unwrap(); let known = Known::new(&*metadata.schema, Some(&metadata.attribute_cache)); lookup_value_for_attribute(sqlite, known, entity, attribute) } /// Take a SQLite transaction. - fn begin_transaction_with_behavior<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result> { + fn begin_transaction_with_behavior<'m, 'conn>( + &'m mut self, + sqlite: &'conn mut rusqlite::Connection, + behavior: TransactionBehavior, + ) -> Result> { let tx = sqlite.transaction_with_behavior(behavior)?; - let (current_generation, current_partition_map, current_schema, cache_cow) = - { + let (current_generation, current_partition_map, current_schema, cache_cow) = { // The mutex is taken during this block. let ref current: Metadata = *self.metadata.lock().unwrap(); - (current.generation, - // Expensive, but the partition map is updated after every committed transaction. - current.partition_map.clone(), - // Cheap. - current.schema.clone(), - current.attribute_cache.clone()) + ( + current.generation, + // Expensive, but the partition map is updated after every committed transaction. + current.partition_map.clone(), + // Cheap. + current.schema.clone(), + current.attribute_cache.clone(), + ) }; Ok(InProgress { @@ -290,12 +266,18 @@ impl Conn { // Helper to avoid passing connections around. // Make both args mutable so that we can't have parallel access. - pub fn begin_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result> { + pub fn begin_read<'m, 'conn>( + &'m mut self, + sqlite: &'conn mut rusqlite::Connection, + ) -> Result> { self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred) .map(|ip| InProgressRead { in_progress: ip }) } - pub fn begin_uncached_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result> { + pub fn begin_uncached_read<'m, 'conn>( + &'m mut self, + sqlite: &'conn mut rusqlite::Connection, + ) -> Result> { self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred) .map(|mut ip| { ip.use_caching(false); @@ -307,15 +289,23 @@ impl Conn { /// connections from taking immediate or exclusive transactions. This is appropriate for our /// writes and `InProgress`: it means we are ready to write whenever we want to, and nobody else /// can start a transaction that's not `DEFERRED`, but we don't need exclusivity yet. - pub fn begin_transaction<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result> { + pub fn begin_transaction<'m, 'conn>( + &'m mut self, + sqlite: &'conn mut rusqlite::Connection, + ) -> Result> { self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Immediate) } /// Transact entities against the Mentat store, using the given connection and the current /// metadata. - pub fn transact(&mut self, - sqlite: &mut rusqlite::Connection, - transaction: B) -> Result where B: Borrow { + pub fn transact( + &mut self, + sqlite: &mut rusqlite::Connection, + transaction: B, + ) -> Result + where + B: Borrow, + { // Parse outside the SQL transaction. This is a tradeoff: we are limiting the scope of the // transaction, and indeed we don't even create a SQL transaction if the provided input is // invalid, but it means SQLite errors won't be found until the parse is complete, and if @@ -334,40 +324,47 @@ impl Conn { /// `cache_action` determines if the attribute should be added or removed from the cache. /// CacheAction::Add is idempotent - each attribute is only added once. /// CacheAction::Remove throws an error if the attribute does not currently exist in the cache. - pub fn cache(&mut self, - sqlite: &mut rusqlite::Connection, - schema: &Schema, - attribute: &Keyword, - cache_direction: CacheDirection, - cache_action: CacheAction) -> Result<()> { + pub fn cache( + &mut self, + sqlite: &mut rusqlite::Connection, + schema: &Schema, + attribute: &Keyword, + cache_direction: CacheDirection, + cache_action: CacheAction, + ) -> Result<()> { let mut metadata = self.metadata.lock().unwrap(); let attribute_entid: Entid; // Immutable borrow of metadata. { - attribute_entid = metadata.schema - .attribute_for_ident(&attribute) - .ok_or_else(|| MentatError::UnknownAttribute(attribute.to_string()))?.1.into(); + attribute_entid = metadata + .schema + .attribute_for_ident(&attribute) + .ok_or_else(|| MentatError::UnknownAttribute(attribute.to_string()))? + .1 + .into(); } let cache = &mut metadata.attribute_cache; match cache_action { - CacheAction::Register => { - match cache_direction { - CacheDirection::Both => cache.register(schema, sqlite, attribute_entid), - CacheDirection::Forward => cache.register_forward(schema, sqlite, attribute_entid), - CacheDirection::Reverse => cache.register_reverse(schema, sqlite, attribute_entid), - }.map_err(|e| e.into()) - }, + CacheAction::Register => match cache_direction { + CacheDirection::Both => cache.register(schema, sqlite, attribute_entid), + CacheDirection::Forward => cache.register_forward(schema, sqlite, attribute_entid), + CacheDirection::Reverse => cache.register_reverse(schema, sqlite, attribute_entid), + } + .map_err(|e| e.into()), CacheAction::Deregister => { cache.unregister(attribute_entid); Ok(()) - }, + } } } pub fn register_observer(&mut self, key: String, observer: Arc) { - self.tx_observer_service.lock().unwrap().register(key, observer); + self.tx_observer_service + .lock() + .unwrap() + .register(key, observer); } pub fn unregister_observer(&mut self, key: &String) { @@ -379,36 +376,21 @@ impl Conn { mod tests { use super::*; - extern crate time; + use time; - use std::time::{ - Instant, - }; + use std::time::Instant; - use core_traits::{ - Binding, - TypedValue, - }; + use core_traits::{Binding, TypedValue}; - use mentat_core::{ - CachedAttributes, - }; + use mentat_core::CachedAttributes; - use mentat_transaction::query::{ - Variable, - }; + use mentat_transaction::query::Variable; - use ::{ - IntoResult, - QueryInputs, - QueryResults, - }; + use mentat_transaction::query::{IntoResult, QueryInputs, QueryResults}; use mentat_db::USER0; - use mentat_transaction::{ - Queryable, - }; + use mentat_transaction::Queryable; #[test] fn test_transact_does_not_collide_existing_entids() { @@ -417,21 +399,23 @@ mod tests { // Let's find out the next ID that'll be allocated. We're going to try to collide with it // a bit later. - let next = conn.metadata.lock().expect("metadata") - .partition_map[":db.part/user"].next_entid(); + let next = + conn.metadata.lock().expect("metadata").partition_map[":db.part/user"].next_entid(); let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next + 1); match conn.transact(&mut sqlite, t.as_str()) { Err(MentatError::DbError(e)) => { - assert_eq!(e.kind(), ::db_traits::errors::DbErrorKind::UnallocatedEntid(next + 1)); - }, + assert_eq!( + e.kind(), + ::db_traits::errors::DbErrorKind::UnallocatedEntid(next + 1) + ); + } x => panic!("expected db error, got {:?}", x), } // Transact two more tempids. let t = "[[:db/add \"one\" :db.schema/attribute \"more\"]]"; - let report = conn.transact(&mut sqlite, t) - .expect("transact succeeded"); + let report = conn.transact(&mut sqlite, t).expect("transact succeeded"); assert_eq!(report.tempids["more"], next); assert_eq!(report.tempids["one"], next + 1); } @@ -442,7 +426,8 @@ mod tests { let mut conn = Conn::connect(&mut sqlite).unwrap(); // Let's find out the next ID that'll be allocated. We're going to try to collide with it. - let next = conn.metadata.lock().expect("metadata").partition_map[":db.part/user"].next_entid(); + let next = + conn.metadata.lock().expect("metadata").partition_map[":db.part/user"].next_entid(); // If this were to be resolved, we'd get [:db/add 65537 :db.schema/attribute 65537], but // we should reject this, because the first ID was provided by the user! @@ -451,16 +436,18 @@ mod tests { match conn.transact(&mut sqlite, t.as_str()) { Err(MentatError::DbError(e)) => { // All this, despite this being the ID we were about to allocate! - assert_eq!(e.kind(), ::db_traits::errors::DbErrorKind::UnallocatedEntid(next)); - }, + assert_eq!( + e.kind(), + ::db_traits::errors::DbErrorKind::UnallocatedEntid(next) + ); + } x => panic!("expected db error, got {:?}", x), } // And if we subsequently transact in a way that allocates one ID, we _will_ use that one. // Note that `10` is a bootstrapped entid; we use it here as a known-good value. let t = "[[:db/add 10 :db.schema/attribute \"temp\"]]"; - let report = conn.transact(&mut sqlite, t) - .expect("transact succeeded"); + let report = conn.transact(&mut sqlite, t).expect("transact succeeded"); assert_eq!(report.tempids["temp"], next); } @@ -478,14 +465,16 @@ mod tests { let tempid_offset = get_next_entid(&conn); let t = "[[:db/add \"one\" :db/ident :a/keyword1] \ - [:db/add \"two\" :db/ident :a/keyword2]]"; + [:db/add \"two\" :db/ident :a/keyword2]]"; // This can refer to `t`, 'cos they occur in separate txes. let t2 = "[{:db.schema/attribute \"three\", :db/ident :a/keyword1}]"; // Scoped borrow of `conn`. { - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); let report = in_progress.transact(t).expect("transacted successfully"); let one = report.tempids.get("one").expect("found one").clone(); let two = report.tempids.get("two").expect("found two").clone(); @@ -493,11 +482,20 @@ mod tests { assert!(one == tempid_offset || one == tempid_offset + 1); assert!(two == tempid_offset || two == tempid_offset + 1); - println!("RES: {:?}", in_progress.q_once("[:find ?v :where [?x :db/ident ?v]]", None).unwrap()); + println!( + "RES: {:?}", + in_progress + .q_once("[:find ?v :where [?x :db/ident ?v]]", None) + .unwrap() + ); - let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None) - .expect("query succeeded"); - assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one).into()))); + let during = in_progress + .q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None) + .expect("query succeeded"); + assert_eq!( + during.results, + QueryResults::Scalar(Some(TypedValue::Ref(one).into())) + ); let report = in_progress.transact(t2).expect("t2 succeeded"); in_progress.commit().expect("commit succeeded"); @@ -515,16 +513,25 @@ mod tests { fn test_simple_prepared_query() { let mut c = db::new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = conn.transact(&mut c, r#"[ + let report = conn + .transact( + &mut c, + r#"[ [:db/add "u" :foo/boolean true] [:db/add "p" :foo/boolean false] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let yes = report.tempids.get("u").expect("found it").clone(); let vv = Variable::from_valid_name("?v"); @@ -536,16 +543,26 @@ mod tests { // N.B., you might choose to algebrize _without_ validating that the // types are known. In this query we know that `?v` must be a boolean, // and so we can kinda generate our own required input types! - let mut prepared = read.q_prepare(r#"[:find [?x ...] + let mut prepared = read + .q_prepare( + r#"[:find [?x ...] :in ?v :where [?x :foo/boolean ?v]]"#, - values).expect("prepare succeeded"); + values, + ) + .expect("prepare succeeded"); let yeses = prepared.run(None).expect("result"); - assert_eq!(yeses.results, QueryResults::Coll(vec![TypedValue::Ref(yes).into()])); + assert_eq!( + yeses.results, + QueryResults::Coll(vec![TypedValue::Ref(yes).into()]) + ); let yeses_again = prepared.run(None).expect("result"); - assert_eq!(yeses_again.results, QueryResults::Coll(vec![TypedValue::Ref(yes).into()])); + assert_eq!( + yeses_again.results, + QueryResults::Coll(vec![TypedValue::Ref(yes).into()]) + ); } #[test] @@ -559,11 +576,13 @@ mod tests { assert_eq!(tempid_offset, USER0); let t = "[[:db/add \"one\" :db/ident :a/keyword1] \ - [:db/add \"two\" :db/ident :a/keyword2]]"; + [:db/add \"two\" :db/ident :a/keyword2]]"; // Scoped borrow of `sqlite`. { - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); let report = in_progress.transact(t).expect("transacted successfully"); let one = report.tempids.get("one").expect("found it").clone(); @@ -575,22 +594,36 @@ mod tests { assert!(two == tempid_offset || two == tempid_offset + 1); // Inside the InProgress we can see our changes. - let during = in_progress.q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None) - .expect("query succeeded"); + let during = in_progress + .q_once("[:find ?x . :where [?x :db/ident :a/keyword1]]", None) + .expect("query succeeded"); - assert_eq!(during.results, QueryResults::Scalar(Some(TypedValue::Ref(one).into()))); + assert_eq!( + during.results, + QueryResults::Scalar(Some(TypedValue::Ref(one).into())) + ); // And we can do direct lookup, too. - let kw = in_progress.lookup_value_for_attribute(one, &edn::Keyword::namespaced("db", "ident")) - .expect("lookup succeeded"); - assert_eq!(kw, Some(TypedValue::Keyword(edn::Keyword::namespaced("a", "keyword1").into()))); + let kw = in_progress + .lookup_value_for_attribute(one, &edn::Keyword::namespaced("db", "ident")) + .expect("lookup succeeded"); + assert_eq!( + kw, + Some(TypedValue::Keyword( + edn::Keyword::namespaced("a", "keyword1").into() + )) + ); - in_progress.rollback() - .expect("rollback succeeded"); + in_progress.rollback().expect("rollback succeeded"); } - let after = conn.q_once(&mut sqlite, "[:find ?x . :where [?x :db/ident :a/keyword1]]", None) - .expect("query succeeded"); + let after = conn + .q_once( + &mut sqlite, + "[:find ?x . :where [?x :db/ident :a/keyword1]]", + None, + ) + .expect("query succeeded"); assert_eq!(after.results, QueryResults::Scalar(None)); // The DB part table is unchanged. @@ -610,34 +643,39 @@ mod tests { // Bad EDN: missing closing ']'. let report = conn.transact(&mut sqlite, "[[:db/add \"t\" :db/ident :a/keyword]"); match report.expect_err("expected transact to fail for bad edn") { - MentatError::EdnParseError(_) => { }, + MentatError::EdnParseError(_) => {} x => panic!("expected EDN parse error, got {:?}", x), } // Good EDN. - let report = conn.transact(&mut sqlite, "[[:db/add \"t\" :db/ident :a/keyword]]").unwrap(); + let report = conn + .transact(&mut sqlite, "[[:db/add \"t\" :db/ident :a/keyword]]") + .unwrap(); assert_eq!(report.tx_id, 0x10000000 + 2); // Bad transaction data: missing leading :db/add. let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]"); match report.expect_err("expected transact error") { - MentatError::EdnParseError(_) => { }, + MentatError::EdnParseError(_) => {} x => panic!("expected EDN parse error, got {:?}", x), } // Good transaction data. - let report = conn.transact(&mut sqlite, "[[:db/add \"u\" :db/ident :b/keyword]]").unwrap(); + let report = conn + .transact(&mut sqlite, "[[:db/add \"u\" :db/ident :b/keyword]]") + .unwrap(); assert_eq!(report.tx_id, 0x10000000 + 3); // Bad transaction based on state of store: conflicting upsert. - let report = conn.transact(&mut sqlite, "[[:db/add \"u\" :db/ident :a/keyword] - [:db/add \"u\" :db/ident :b/keyword]]"); + let report = conn.transact( + &mut sqlite, + "[[:db/add \"u\" :db/ident :a/keyword] + [:db/add \"u\" :db/ident :b/keyword]]", + ); match report.expect_err("expected transact error") { - MentatError::DbError(e) => { - match e.kind() { - ::db_traits::errors::DbErrorKind::SchemaConstraintViolation(_) => {}, - _ => panic!("expected SchemaConstraintViolation"), - } + MentatError::DbError(e) => match e.kind() { + ::db_traits::errors::DbErrorKind::SchemaConstraintViolation(_) => {} + _ => panic!("expected SchemaConstraintViolation"), }, x => panic!("expected db error, got {:?}", x), } @@ -647,15 +685,26 @@ mod tests { fn test_add_to_cache_failure_no_attribute() { let mut sqlite = db::new_connection("").unwrap(); let mut conn = Conn::connect(&mut sqlite).unwrap(); - let _report = conn.transact(&mut sqlite, r#"[ + let _report = conn + .transact( + &mut sqlite, + r#"[ { :db/ident :foo/bar :db/valueType :db.type/long }, { :db/ident :foo/baz - :db/valueType :db.type/boolean }]"#).unwrap(); + :db/valueType :db.type/boolean }]"#, + ) + .unwrap(); let kw = kw!(:foo/bat); let schema = conn.current_schema(); - let res = conn.cache(&mut sqlite, &schema, &kw, CacheDirection::Forward, CacheAction::Register); + let res = conn.cache( + &mut sqlite, + &schema, + &kw, + CacheDirection::Forward, + CacheAction::Register, + ); match res.expect_err("expected cache to fail") { MentatError::UnknownAttribute(msg) => assert_eq!(msg, ":foo/bat"), x => panic!("expected UnknownAttribute error, got {:?}", x), @@ -665,19 +714,25 @@ mod tests { // TODO expand tests to cover lookup_value_for_attribute comparing with and without caching #[test] fn test_lookup_attribute_with_caching() { - let mut sqlite = db::new_connection("").unwrap(); let mut conn = Conn::connect(&mut sqlite).unwrap(); - let _report = conn.transact(&mut sqlite, r#"[ + let _report = conn + .transact( + &mut sqlite, + r#"[ { :db/ident :foo/bar :db/valueType :db.type/long }, { :db/ident :foo/baz - :db/valueType :db.type/boolean }]"#).expect("transaction expected to succeed"); + :db/valueType :db.type/boolean }]"#, + ) + .expect("transaction expected to succeed"); { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("transaction"); for _ in 1..100 { - let _report = in_progress.transact(r#"[ + let _report = in_progress + .transact( + r#"[ { :foo/bar 100 :foo/baz false }, { :foo/bar 200 @@ -689,12 +744,18 @@ mod tests { { :foo/bar 400 :foo/baz false }, { :foo/bar 500 - :foo/baz true }]"#).expect("transaction expected to succeed"); + :foo/baz true }]"#, + ) + .expect("transaction expected to succeed"); } in_progress.commit().expect("Committed"); } - let entities = conn.q_once(&sqlite, r#"[:find ?e . :where [?e :foo/bar 400]]"#, None).expect("Expected query to work").into_scalar().expect("expected rel results"); + let entities = conn + .q_once(&sqlite, r#"[:find ?e . :where [?e :foo/bar 400]]"#, None) + .expect("Expected query to work") + .into_scalar() + .expect("expected rel results"); let first = entities.expect("expected a result"); let entid = match first { Binding::Scalar(TypedValue::Ref(entid)) => entid, @@ -703,17 +764,28 @@ mod tests { let kw = kw!(:foo/bar); let start = Instant::now(); - let uncached_val = conn.lookup_value_for_attribute(&sqlite, entid, &kw).expect("Expected value on lookup"); + let uncached_val = conn + .lookup_value_for_attribute(&sqlite, entid, &kw) + .expect("Expected value on lookup"); let finish = Instant::now(); let uncached_elapsed_time = finish.duration_since(start); println!("Uncached time: {:?}", uncached_elapsed_time); let schema = conn.current_schema(); - conn.cache(&mut sqlite, &schema, &kw, CacheDirection::Forward, CacheAction::Register).expect("expected caching to work"); + conn.cache( + &mut sqlite, + &schema, + &kw, + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("expected caching to work"); for _ in 1..5 { let start = Instant::now(); - let cached_val = conn.lookup_value_for_attribute(&sqlite, entid, &kw).expect("Expected value on lookup"); + let cached_val = conn + .lookup_value_for_attribute(&sqlite, entid, &kw) + .expect("Expected value on lookup"); let finish = Instant::now(); let cached_elapsed_time = finish.duration_since(start); assert_eq!(cached_val, uncached_val); @@ -728,12 +800,20 @@ mod tests { let mut sqlite = db::new_connection("").unwrap(); let mut conn = Conn::connect(&mut sqlite).unwrap(); - let db_ident = (*conn.current_schema()).get_entid(&kw!(:db/ident)).expect("db_ident").0; - let db_type = (*conn.current_schema()).get_entid(&kw!(:db/valueType)).expect("db_ident").0; + let db_ident = (*conn.current_schema()) + .get_entid(&kw!(:db/ident)) + .expect("db_ident") + .0; + let db_type = (*conn.current_schema()) + .get_entid(&kw!(:db/valueType)) + .expect("db_ident") + .0; println!("db/ident is {}", db_ident); println!("db/type is {}", db_type); - let query = format!("[:find ?ident . :where [?e {} :db/doc][?e {} ?type][?type {} ?ident]]", - db_ident, db_type, db_ident); + let query = format!( + "[:find ?ident . :where [?e {} :db/doc][?e {} ?type][?type {} ?ident]]", + db_ident, db_type, db_ident + ); println!("Query is {}", query); @@ -742,26 +822,52 @@ mod tests { { let mut ip = conn.begin_transaction(&mut sqlite).expect("began"); - let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); - assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string").into())); + let ident = ip + .q_once(query.as_str(), None) + .into_scalar_result() + .expect("query"); + assert_eq!( + ident, + Some(TypedValue::typed_ns_keyword("db.type", "string").into()) + ); - let start = time::PreciseTime::now(); - ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); - let end = time::PreciseTime::now(); - println!("Uncached took {}µs", start.to(end).num_microseconds().unwrap()); + let start = time::Instant::now(); + ip.q_once(query.as_str(), None) + .into_scalar_result() + .expect("query"); + let end = time::Instant::now(); + println!("Uncached took {}µs", (end - start).whole_microseconds()); - ip.cache(&kw!(:db/ident), CacheDirection::Forward, CacheAction::Register).expect("registered"); - ip.cache(&kw!(:db/valueType), CacheDirection::Forward, CacheAction::Register).expect("registered"); + ip.cache( + &kw!(:db/ident), + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("registered"); + ip.cache( + &kw!(:db/valueType), + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("registered"); assert!(ip.cache.is_attribute_cached_forward(db_ident)); - let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); - assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string").into())); + let ident = ip + .q_once(query.as_str(), None) + .into_scalar_result() + .expect("query"); + assert_eq!( + ident, + Some(TypedValue::typed_ns_keyword("db.type", "string").into()) + ); - let start = time::PreciseTime::now(); - ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); - let end = time::PreciseTime::now(); - println!("Cached took {}µs", start.to(end).num_microseconds().unwrap()); + let start = time::Instant::now(); + ip.q_once(query.as_str(), None) + .into_scalar_result() + .expect("query"); + let end = time::Instant::now(); + println!("Cached took {}µs", (end - start).whole_microseconds()); // If we roll back the change, our caching operations are also rolled back. ip.rollback().expect("rolled back"); @@ -772,10 +878,26 @@ mod tests { { let mut ip = conn.begin_transaction(&mut sqlite).expect("began"); - let ident = ip.q_once(query.as_str(), None).into_scalar_result().expect("query"); - assert_eq!(ident, Some(TypedValue::typed_ns_keyword("db.type", "string").into())); - ip.cache(&kw!(:db/ident), CacheDirection::Forward, CacheAction::Register).expect("registered"); - ip.cache(&kw!(:db/valueType), CacheDirection::Forward, CacheAction::Register).expect("registered"); + let ident = ip + .q_once(query.as_str(), None) + .into_scalar_result() + .expect("query"); + assert_eq!( + ident, + Some(TypedValue::typed_ns_keyword("db.type", "string").into()) + ); + ip.cache( + &kw!(:db/ident), + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("registered"); + ip.cache( + &kw!(:db/valueType), + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("registered"); assert!(ip.cache.is_attribute_cached_forward(db_ident)); diff --git a/src/lib.rs b/src/lib.rs index c4e72814..43ce6a8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,76 +8,28 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate failure; - #[macro_use] extern crate lazy_static; -extern crate rusqlite; +pub use edn; -extern crate uuid; - -pub extern crate edn; -extern crate mentat_core; #[macro_use] extern crate core_traits; -extern crate mentat_db; -extern crate db_traits; -extern crate mentat_query_algebrizer; -extern crate query_algebrizer_traits; -extern crate mentat_query_projector; -extern crate query_projector_traits; -extern crate mentat_query_pull; -extern crate query_pull_traits; -extern crate sql_traits; -extern crate mentat_sql; -extern crate public_traits; -extern crate mentat_transaction; - -#[cfg(feature = "syncable")] -extern crate mentat_tolstoy; - -#[cfg(feature = "syncable")] -extern crate tolstoy_traits; pub use core_traits::{ - Attribute, - Binding, - Entid, - KnownEntid, - StructuredMap, - TypedValue, - ValueType, - now, + now, Attribute, Binding, Entid, KnownEntid, StructuredMap, TypedValue, ValueType, }; -pub use mentat_core::{ - DateTime, - HasSchema, - Keyword, - Schema, - TxReport, - Utc, - Uuid, -}; +pub use mentat_core::{DateTime, HasSchema, Keyword, Schema, TxReport, Utc, Uuid}; -pub use edn::query::{ - FindSpec, -}; +pub use edn::query::FindSpec; pub use mentat_db::{ - CORE_SCHEMA_VERSION, - DB_SCHEMA_CORE, - AttributeSet, - TxObserver, - new_connection, + new_connection, AttributeSet, TxObserver, CORE_SCHEMA_VERSION, DB_SCHEMA_CORE, }; #[cfg(feature = "sqlcipher")] -pub use mentat_db::{ - new_connection_with_key, - change_encryption_key, -}; +pub use mentat_db::{change_encryption_key, new_connection_with_key}; /// Produce the appropriate `Variable` for the provided valid ?-prefixed name. /// This lives here because we can't re-export macros: @@ -130,47 +82,23 @@ macro_rules! kw { } pub use public_traits::errors; -pub use public_traits::errors::{ - MentatError, - Result, -}; +pub use public_traits::errors::{MentatError, Result}; -pub use edn::{ - FromMicros, - FromMillis, - ParseError, - ToMicros, - ToMillis, -}; +pub use edn::{FromMicros, FromMillis, ParseError, ToMicros, ToMillis}; +pub use mentat_query_projector::BindingTuple; pub use query_algebrizer_traits::errors::AlgebrizerError; -pub use query_projector_traits::errors::{ - ProjectorError, -}; -pub use mentat_query_projector::{ - BindingTuple, -}; +pub use query_projector_traits::errors::ProjectorError; pub use query_pull_traits::errors::PullError; pub use sql_traits::errors::SQLError; -pub use mentat_transaction::{ - Metadata, -}; +pub use mentat_transaction::Metadata; -pub use mentat_transaction::query; pub use mentat_transaction::entity_builder; +pub use mentat_transaction::query; pub use mentat_transaction::query::{ - IntoResult, - PlainSymbol, - QueryExecutionResult, - QueryExplanation, - QueryInputs, - QueryOutput, - QueryPlanStep, - QueryResults, - RelResult, - Variable, - q_once, + q_once, IntoResult, PlainSymbol, QueryExecutionResult, QueryExplanation, QueryInputs, + QueryOutput, QueryPlanStep, QueryResults, RelResult, Variable, }; pub mod conn; @@ -182,39 +110,23 @@ pub mod vocabulary; mod sync; #[cfg(feature = "syncable")] -pub use sync::{ - Syncable, -}; +pub use sync::Syncable; #[cfg(feature = "syncable")] -pub use mentat_tolstoy::{ - SyncReport, -}; +pub use mentat_tolstoy::SyncReport; -pub use query_builder::{ - QueryBuilder, -}; +pub use query_builder::QueryBuilder; -pub use conn::{ - Conn, -}; +pub use conn::Conn; -pub use mentat_transaction::{ - CacheAction, - CacheDirection, - InProgress, - Pullable, - Queryable, -}; +pub use mentat_transaction::{CacheAction, CacheDirection, InProgress, Pullable, Queryable}; -pub use store::{ - Store, -}; +pub use store::Store; #[cfg(test)] mod tests { - use edn::symbols::Keyword; use super::*; + use edn::symbols::Keyword; #[test] fn can_import_edn() { @@ -224,9 +136,18 @@ mod tests { #[test] fn test_kw() { assert_eq!(kw!(:foo/bar), Keyword::namespaced("foo", "bar")); - assert_eq!(kw!(:org.mozilla.foo/bar_baz), Keyword::namespaced("org.mozilla.foo", "bar_baz")); - assert_eq!(kw!(:_foo_/_bar_._baz_), Keyword::namespaced("_foo_", "_bar_._baz_")); - assert_eq!(kw!(:_org_._mozilla_._foo_/_bar_._baz_), Keyword::namespaced("_org_._mozilla_._foo_", "_bar_._baz_")); + assert_eq!( + kw!(:org.mozilla.foo/bar_baz), + Keyword::namespaced("org.mozilla.foo", "bar_baz") + ); + assert_eq!( + kw!(:_foo_/_bar_._baz_), + Keyword::namespaced("_foo_", "_bar_._baz_") + ); + assert_eq!( + kw!(:_org_._mozilla_._foo_/_bar_._baz_), + Keyword::namespaced("_org_._mozilla_._foo_", "_bar_._baz_") + ); } #[test] diff --git a/src/query_builder.rs b/src/query_builder.rs index 833faf24..06e2ebbd 100644 --- a/src/query_builder.rs +++ b/src/query_builder.rs @@ -9,37 +9,15 @@ // specific language governing permissions and limitations under the License. #![macro_use] -use std::collections::{ - BTreeMap, -}; +use std::collections::BTreeMap; -pub use core_traits::{ - Entid, - Binding, - TypedValue, - ValueType, -}; +pub use core_traits::{Binding, Entid, TypedValue, ValueType}; -use mentat_core::{ - DateTime, - Keyword, - Utc, -}; +use mentat_core::{DateTime, Keyword, Utc}; -use ::{ - HasSchema, - Queryable, - QueryInputs, - QueryOutput, - RelResult, - Store, - Variable, -}; +use super::{HasSchema, QueryInputs, QueryOutput, Queryable, RelResult, Store, Variable}; -use public_traits::errors::{ - MentatError, - Result, -}; +use public_traits::errors::{MentatError, Result}; pub struct QueryBuilder<'a> { query: String, @@ -49,44 +27,74 @@ pub struct QueryBuilder<'a> { } impl<'a> QueryBuilder<'a> { - pub fn new(store: &'a mut Store, query: T) -> QueryBuilder where T: Into { - QueryBuilder { query: query.into(), values: BTreeMap::new(), types: BTreeMap::new(), store } + pub fn new(store: &'a mut Store, query: T) -> QueryBuilder<'_> + where + T: Into, + { + QueryBuilder { + query: query.into(), + values: BTreeMap::new(), + types: BTreeMap::new(), + store, + } } - pub fn bind_value(&mut self, var: &str, value: T) -> &mut Self where T: Into { - self.values.insert(Variable::from_valid_name(var), value.into()); + pub fn bind_value(&mut self, var: &str, value: T) -> &mut Self + where + T: Into, + { + self.values + .insert(Variable::from_valid_name(var), value.into()); self } pub fn bind_ref_from_kw(&mut self, var: &str, value: Keyword) -> Result<&mut Self> { - let entid = self.store.conn().current_schema().get_entid(&value).ok_or(MentatError::UnknownAttribute(value.to_string()))?; - self.values.insert(Variable::from_valid_name(var), TypedValue::Ref(entid.into())); + let entid = self + .store + .conn() + .current_schema() + .get_entid(&value) + .ok_or(MentatError::UnknownAttribute(value.to_string()))?; + self.values.insert( + Variable::from_valid_name(var), + TypedValue::Ref(entid.into()), + ); Ok(self) } - pub fn bind_ref(&mut self, var: &str, value: T) -> &mut Self where T: Into { - self.values.insert(Variable::from_valid_name(var), TypedValue::Ref(value.into())); - self + pub fn bind_ref(&mut self, var: &str, value: T) -> &mut Self + where + T: Into, + { + self.values.insert( + Variable::from_valid_name(var), + TypedValue::Ref(value.into()), + ); + self } pub fn bind_long(&mut self, var: &str, value: i64) -> &mut Self { - self.values.insert(Variable::from_valid_name(var), TypedValue::Long(value)); - self + self.values + .insert(Variable::from_valid_name(var), TypedValue::Long(value)); + self } pub fn bind_instant(&mut self, var: &str, value: i64) -> &mut Self { - self.values.insert(Variable::from_valid_name(var), TypedValue::instant(value)); + self.values + .insert(Variable::from_valid_name(var), TypedValue::instant(value)); - self + self } pub fn bind_date_time(&mut self, var: &str, value: DateTime) -> &mut Self { - self.values.insert(Variable::from_valid_name(var), TypedValue::Instant(value)); - self + self.values + .insert(Variable::from_valid_name(var), TypedValue::Instant(value)); + self } pub fn bind_type(&mut self, var: &str, value_type: ValueType) -> &mut Self { - self.types.insert(Variable::from_valid_name(var), value_type); + self.types + .insert(Variable::from_valid_name(var), value_type); self } @@ -121,50 +129,64 @@ impl<'a> QueryBuilder<'a> { #[cfg(test)] mod test { - use super::{ - QueryBuilder, - TypedValue, - Store, - }; + use super::{QueryBuilder, Store, TypedValue}; #[test] fn test_scalar_query() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "u" :foo/boolean true] [:db/add "p" :foo/boolean false] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let yes = report.tempids.get("u").expect("found it").clone(); - let entid = QueryBuilder::new(&mut store, r#"[:find ?x . + let entid = QueryBuilder::new( + &mut store, + r#"[:find ?x . :in ?v - :where [?x :foo/boolean ?v]]"#) - .bind_value("?v", true) - .execute_scalar().expect("ScalarResult") - .map_or(None, |t| t.into_entid()); + :where [?x :foo/boolean ?v]]"#, + ) + .bind_value("?v", true) + .execute_scalar() + .expect("ScalarResult") + .map_or(None, |t| t.into_entid()); assert_eq!(entid, Some(yes)); } #[test] fn test_coll_query() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] @@ -175,20 +197,26 @@ mod test { [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let u_yes = report.tempids.get("u").expect("found it").clone(); let l_yes = report.tempids.get("l").expect("found it").clone(); let n_yes = report.tempids.get("n").expect("found it").clone(); - let entids: Vec = QueryBuilder::new(&mut store, r#"[:find [?x ...] + let entids: Vec = QueryBuilder::new( + &mut store, + r#"[:find [?x ...] :in ?v - :where [?x :foo/boolean ?v]]"#) - .bind_value("?v", true) - .execute_coll().expect("CollResult") - .into_iter() - .map(|v| v.into_entid().expect("val")) - .collect(); + :where [?x :foo/boolean ?v]]"#, + ) + .bind_value("?v", true) + .execute_coll() + .expect("CollResult") + .into_iter() + .map(|v| v.into_entid().expect("val")) + .collect(); assert_eq!(entids, vec![l_yes, n_yes, u_yes]); } @@ -196,16 +224,22 @@ mod test { #[test] fn test_coll_query_by_row() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] @@ -216,16 +250,25 @@ mod test { [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let n_yes = report.tempids.get("n").expect("found it").clone(); - let results = QueryBuilder::new(&mut store, r#"[:find [?x ...] + let results = QueryBuilder::new( + &mut store, + r#"[:find [?x ...] :in ?v - :where [?x :foo/boolean ?v]]"#) - .bind_value("?v", true) - .execute_coll().expect("CollResult"); - let entid = results.get(1).map_or(None, |t| t.to_owned().into_entid()).expect("entid"); + :where [?x :foo/boolean ?v]]"#, + ) + .bind_value("?v", true) + .execute_coll() + .expect("CollResult"); + let entid = results + .get(1) + .map_or(None, |t| t.to_owned().into_entid()) + .expect("entid"); assert_eq!(entid, n_yes); } @@ -233,16 +276,22 @@ mod test { #[test] fn test_tuple_query_result_by_column() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] @@ -253,19 +302,32 @@ mod test { [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let n_yes = report.tempids.get("n").expect("found it").clone(); - let results = QueryBuilder::new(&mut store, r#"[:find [?x, ?i] + let results = QueryBuilder::new( + &mut store, + r#"[:find [?x, ?i] :in ?v ?i :where [?x :foo/boolean ?v] - [?x :foo/long ?i]]"#) - .bind_value("?v", true) - .bind_long("?i", 27) - .execute_tuple().expect("TupleResult").expect("Vec"); - let entid = results.get(0).map_or(None, |t| t.to_owned().into_entid()).expect("entid"); - let long_val = results.get(1).map_or(None, |t| t.to_owned().into_long()).expect("long"); + [?x :foo/long ?i]]"#, + ) + .bind_value("?v", true) + .bind_long("?i", 27) + .execute_tuple() + .expect("TupleResult") + .expect("Vec"); + let entid = results + .get(0) + .map_or(None, |t| t.to_owned().into_entid()) + .expect("entid"); + let long_val = results + .get(1) + .map_or(None, |t| t.to_owned().into_long()) + .expect("long"); assert_eq!(entid, n_yes); assert_eq!(long_val, 27); @@ -274,16 +336,22 @@ mod test { #[test] fn test_tuple_query_result_by_iter() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] @@ -294,17 +362,24 @@ mod test { [:db/add "p" :foo/long 24] [:db/add "u" :foo/boolean true] [:db/add "u" :foo/long 23] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let n_yes = report.tempids.get("n").expect("found it").clone(); - let results: Vec<_> = QueryBuilder::new(&mut store, r#"[:find [?x, ?i] + let results: Vec<_> = QueryBuilder::new( + &mut store, + r#"[:find [?x, ?i] :in ?v ?i :where [?x :foo/boolean ?v] - [?x :foo/long ?i]]"#) - .bind_value("?v", true) - .bind_long("?i", 27) - .execute_tuple().expect("TupleResult").unwrap_or(vec![]); + [?x :foo/long ?i]]"#, + ) + .bind_value("?v", true) + .bind_long("?i", 27) + .execute_tuple() + .expect("TupleResult") + .unwrap_or(vec![]); let entid = TypedValue::Ref(n_yes.clone()).into(); let long_val = TypedValue::Long(27).into(); @@ -314,23 +389,31 @@ mod test { #[test] fn test_rel_query_result() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let l_yes = report.tempids.get("l").expect("found it").clone(); let m_yes = report.tempids.get("m").expect("found it").clone(); @@ -343,60 +426,116 @@ mod test { long_val: i64, }; - let mut results: Vec = QueryBuilder::new(&mut store, r#"[:find ?x ?v ?i + let mut results: Vec = QueryBuilder::new( + &mut store, + r#"[:find ?x ?v ?i :where [?x :foo/boolean ?v] - [?x :foo/long ?i]]"#) - .execute_rel().expect("RelResult") - .into_iter() - .map(|row| { - Res { - entid: row.get(0).map_or(None, |t| t.to_owned().into_entid()).expect("entid"), - boolean: row.get(1).map_or(None, |t| t.to_owned().into_boolean()).expect("boolean"), - long_val: row.get(2).map_or(None, |t| t.to_owned().into_long()).expect("long"), - } - }) - .collect(); + [?x :foo/long ?i]]"#, + ) + .execute_rel() + .expect("RelResult") + .into_iter() + .map(|row| Res { + entid: row + .get(0) + .map_or(None, |t| t.to_owned().into_entid()) + .expect("entid"), + boolean: row + .get(1) + .map_or(None, |t| t.to_owned().into_boolean()) + .expect("boolean"), + long_val: row + .get(2) + .map_or(None, |t| t.to_owned().into_long()) + .expect("long"), + }) + .collect(); let res1 = results.pop().expect("res"); - assert_eq!(res1, Res { entid: n_yes, boolean: true, long_val: 27 }); + assert_eq!( + res1, + Res { + entid: n_yes, + boolean: true, + long_val: 27 + } + ); let res2 = results.pop().expect("res"); - assert_eq!(res2, Res { entid: m_yes, boolean: false, long_val: 26 }); + assert_eq!( + res2, + Res { + entid: m_yes, + boolean: false, + long_val: 26 + } + ); let res3 = results.pop().expect("res"); - assert_eq!(res3, Res { entid: l_yes, boolean: true, long_val: 25 }); + assert_eq!( + res3, + Res { + entid: l_yes, + boolean: true, + long_val: 25 + } + ); assert_eq!(results.pop(), None); } #[test] fn test_bind_ref() { let mut store = Store::open("").expect("store connection"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] [:db/add "s" :db/cardinality :db.cardinality/one] [:db/add "t" :db/ident :foo/long] [:db/add "t" :db/valueType :db.type/long] [:db/add "t" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let report = store.transact(r#"[ + let report = store + .transact( + r#"[ [:db/add "l" :foo/boolean true] [:db/add "l" :foo/long 25] [:db/add "m" :foo/boolean false] [:db/add "m" :foo/long 26] [:db/add "n" :foo/boolean true] [:db/add "n" :foo/long 27] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let l_yes = report.tempids.get("l").expect("found it").clone(); - let results = QueryBuilder::new(&mut store, r#"[:find [?v ?i] + let results = QueryBuilder::new( + &mut store, + r#"[:find [?v ?i] :in ?x :where [?x :foo/boolean ?v] - [?x :foo/long ?i]]"#) - .bind_ref("?x", l_yes) - .execute_tuple().expect("TupleResult") - .unwrap_or(vec![]); - assert_eq!(results.get(0).map_or(None, |t| t.to_owned().into_boolean()).expect("boolean"), true); - assert_eq!(results.get(1).map_or(None, |t| t.to_owned().into_long()).expect("long"), 25); + [?x :foo/long ?i]]"#, + ) + .bind_ref("?x", l_yes) + .execute_tuple() + .expect("TupleResult") + .unwrap_or(vec![]); + assert_eq!( + results + .get(0) + .map_or(None, |t| t.to_owned().into_boolean()) + .expect("boolean"), + true + ); + assert_eq!( + results + .get(1) + .map_or(None, |t| t.to_owned().into_long()) + .expect("long"), + 25 + ); } } diff --git a/src/store.rs b/src/store.rs index bd5a5389..27476308 100644 --- a/src/store.rs +++ b/src/store.rs @@ -10,68 +10,34 @@ #![allow(dead_code)] -use std::collections::{ - BTreeMap, -}; +use std::collections::BTreeMap; -use std::sync::{ - Arc, -}; +use std::sync::Arc; use rusqlite; use edn; -use core_traits::{ - Entid, - StructuredMap, - TypedValue, -}; +use core_traits::{Entid, StructuredMap, TypedValue}; -use mentat_core::{ - Keyword, - TxReport, - ValueRc, -}; -use mentat_db::{ - TxObserver, -}; +use mentat_core::{Keyword, TxReport, ValueRc}; +use mentat_db::TxObserver; use mentat_transaction::{ - CacheAction, - CacheDirection, - InProgress, - InProgressRead, - Pullable, - Queryable, + CacheAction, CacheDirection, InProgress, InProgressRead, Pullable, Queryable, }; -use conn::{ - Conn, -}; +use super::conn::Conn; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use mentat_transaction::query::{ - PreparedResult, - QueryExplanation, - QueryInputs, - QueryOutput, -}; +use mentat_transaction::query::{PreparedResult, QueryExplanation, QueryInputs, QueryOutput}; #[cfg(feature = "syncable")] -use mentat_tolstoy::{ - SyncReport, - SyncResult, - SyncFollowup, -}; +use mentat_tolstoy::{SyncFollowup, SyncReport, SyncResult}; #[cfg(feature = "syncable")] -use sync::{ - Syncable, -}; +use super::sync::Syncable; /// A convenience wrapper around a single SQLite connection and a Conn. This is suitable /// for applications that don't require complex connection management. @@ -83,7 +49,7 @@ pub struct Store { impl Store { /// Open a store at the supplied path, ensuring that it includes the bootstrap schema. pub fn open(path: &str) -> Result { - let mut connection = ::new_connection(path)?; + let mut connection = crate::new_connection(path)?; let conn = Conn::connect(&mut connection)?; Ok(Store { conn: conn, @@ -109,12 +75,12 @@ impl Store { match report { SyncReport::Merge(SyncFollowup::FullSync) => { reports.push(report); - continue - }, + continue; + } _ => { reports.push(report); - break - }, + break; + } } } if reports.len() == 1 { @@ -156,7 +122,11 @@ impl Store { #[cfg(test)] pub fn is_registered_as_observer(&self, key: &String) -> bool { - self.conn.tx_observer_service.lock().unwrap().is_registered(key) + self.conn + .tx_observer_service + .lock() + .unwrap() + .is_registered(key) } } @@ -179,11 +149,13 @@ impl Store { pub fn cache(&mut self, attr: &Keyword, direction: CacheDirection) -> Result<()> { let schema = &self.conn.current_schema(); - self.conn.cache(&mut self.sqlite, - schema, - attr, - direction, - CacheAction::Register) + self.conn.cache( + &mut self.sqlite, + schema, + attr, + direction, + CacheAction::Register, + ) } pub fn register_observer(&mut self, key: String, observer: Arc) { @@ -201,41 +173,71 @@ impl Store { impl Queryable for Store { fn q_once(&self, query: &str, inputs: T) -> Result - where T: Into> { + where + T: Into>, + { self.conn.q_once(&self.sqlite, query, inputs) } - fn q_prepare(&self, query: &str, inputs: T) -> PreparedResult - where T: Into> { + fn q_prepare(&self, query: &str, inputs: T) -> PreparedResult<'_> + where + T: Into>, + { self.conn.q_prepare(&self.sqlite, query, inputs) } fn q_explain(&self, query: &str, inputs: T) -> Result - where T: Into> { + where + T: Into>, + { self.conn.q_explain(&self.sqlite, query, inputs) } - fn lookup_values_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into { - self.conn.lookup_values_for_attribute(&self.sqlite, entity.into(), attribute) + fn lookup_values_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into, + { + self.conn + .lookup_values_for_attribute(&self.sqlite, entity.into(), attribute) } - fn lookup_value_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into { - self.conn.lookup_value_for_attribute(&self.sqlite, entity.into(), attribute) + fn lookup_value_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into, + { + self.conn + .lookup_value_for_attribute(&self.sqlite, entity.into(), attribute) } } impl Pullable for Store { - fn pull_attributes_for_entities(&self, entities: E, attributes: A) -> Result>> - where E: IntoIterator, - A: IntoIterator { - self.conn.pull_attributes_for_entities(&self.sqlite, entities, attributes) + fn pull_attributes_for_entities( + &self, + entities: E, + attributes: A, + ) -> Result>> + where + E: IntoIterator, + A: IntoIterator, + { + self.conn + .pull_attributes_for_entities(&self.sqlite, entities, attributes) } fn pull_attributes_for_entity(&self, entity: Entid, attributes: A) -> Result - where A: IntoIterator { - self.conn.pull_attributes_for_entity(&self.sqlite, entity, attributes) + where + A: IntoIterator, + { + self.conn + .pull_attributes_for_entity(&self.sqlite, entity, attributes) } } @@ -243,60 +245,31 @@ impl Pullable for Store { mod tests { use super::*; - extern crate time; + use time; use uuid::Uuid; - use std::collections::{ - BTreeSet, - }; - use std::path::{ - Path, - PathBuf, - }; + use std::collections::BTreeSet; + use std::path::{Path, PathBuf}; use std::sync::mpsc; - use std::sync::{ - Mutex, - }; - use std::time::{ - Duration, - }; + use std::sync::Mutex; + use std::time::Duration; - use mentat_db::cache::{ - SQLiteAttributeCache, - }; + use mentat_db::cache::SQLiteAttributeCache; - use core_traits::{ - TypedValue, - ValueType, - }; + use core_traits::{TypedValue, ValueType}; - use mentat_core::{ - CachedAttributes, - HasSchema, - }; + use mentat_core::{CachedAttributes, HasSchema}; - use mentat_transaction::entity_builder::{ - BuildTerms, - }; + use mentat_transaction::entity_builder::BuildTerms; - use mentat_transaction::query::{ - PreparedQuery, - }; + use mentat_transaction::query::PreparedQuery; - use ::{ - QueryInputs, - }; + use mentat_query_algebrizer::QueryInputs; - use ::vocabulary::{ - AttributeBuilder, - Definition, - VersionedStore, - }; + use crate::vocabulary::{AttributeBuilder, Definition, VersionedStore}; - use core_traits::attribute::{ - Unique, - }; + use core_traits::attribute::Unique; fn fixture_path(rest: &str) -> PathBuf { let fixtures = Path::new("fixtures/"); @@ -307,11 +280,33 @@ mod tests { fn test_prepared_query_with_cache() { let mut store = Store::open("").expect("opened"); let mut in_progress = store.begin_transaction().expect("began"); - in_progress.import(fixture_path("cities.schema")).expect("transacted schema"); - in_progress.import(fixture_path("all_seattle.edn")).expect("transacted data"); - in_progress.cache(&kw!(:neighborhood/district), CacheDirection::Forward, CacheAction::Register).expect("cache done"); - in_progress.cache(&kw!(:district/name), CacheDirection::Forward, CacheAction::Register).expect("cache done"); - in_progress.cache(&kw!(:neighborhood/name), CacheDirection::Reverse, CacheAction::Register).expect("cache done"); + in_progress + .import(fixture_path("cities.schema")) + .expect("transacted schema"); + in_progress + .import(fixture_path("all_seattle.edn")) + .expect("transacted data"); + in_progress + .cache( + &kw!(:neighborhood/district), + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("cache done"); + in_progress + .cache( + &kw!(:district/name), + CacheDirection::Forward, + CacheAction::Register, + ) + .expect("cache done"); + in_progress + .cache( + &kw!(:neighborhood/name), + CacheDirection::Reverse, + CacheAction::Register, + ) + .expect("cache done"); let query = r#"[:find ?district :in ?hood @@ -320,21 +315,29 @@ mod tests { [?neighborhood :neighborhood/district ?d] [?d :district/name ?district]]"#; let hood = "Beacon Hill"; - let inputs = QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::typed_string(hood).into())]); - let mut prepared = in_progress.q_prepare(query, inputs) - .expect("prepared"); + let inputs = QueryInputs::with_value_sequence(vec![( + var!(?hood), + TypedValue::typed_string(hood).into(), + )]); + let mut prepared = in_progress.q_prepare(query, inputs).expect("prepared"); match &prepared { - &PreparedQuery::Constant { select: ref _select } => {}, + &PreparedQuery::Constant { + select: ref _select, + } => {} _ => panic!(), }; - - let start = time::PreciseTime::now(); + let start = time::Instant::now(); let results = prepared.run(None).expect("results"); - let end = time::PreciseTime::now(); - println!("Prepared cache execution took {}µs", start.to(end).num_microseconds().unwrap()); - assert_eq!(results.into_rel().expect("result"), - vec![vec![TypedValue::typed_string("Greater Duwamish")]].into()); + let end = time::Instant::now(); + println!( + "Prepared cache execution took {}µs", + (end - start).whole_microseconds() + ); + assert_eq!( + results.into_rel().expect("result"), + vec![vec![TypedValue::typed_string("Greater Duwamish")]].into() + ); } trait StoreCache { @@ -364,7 +367,9 @@ mod tests { { let mut in_progress = store.begin_transaction().expect("begun"); - in_progress.transact(r#"[ + in_progress + .transact( + r#"[ { :db/ident :foo/bar :db/cardinality :db.cardinality/one :db/index true @@ -375,20 +380,47 @@ mod tests { :db/valueType :db.type/boolean } { :db/ident :foo/x :db/cardinality :db.cardinality/many - :db/valueType :db.type/long }]"#).expect("transact"); + :db/valueType :db.type/long }]"#, + ) + .expect("transact"); // Cache one…. - in_progress.cache(&kw!(:foo/bar), CacheDirection::Reverse, CacheAction::Register).expect("cache done"); + in_progress + .cache( + &kw!(:foo/bar), + CacheDirection::Reverse, + CacheAction::Register, + ) + .expect("cache done"); in_progress.commit().expect("commit"); } - let foo_bar = store.conn.current_schema().get_entid(&kw!(:foo/bar)).expect("foo/bar").0; - let foo_baz = store.conn.current_schema().get_entid(&kw!(:foo/baz)).expect("foo/baz").0; - let foo_x = store.conn.current_schema().get_entid(&kw!(:foo/x)).expect("foo/x").0; + let foo_bar = store + .conn + .current_schema() + .get_entid(&kw!(:foo/bar)) + .expect("foo/bar") + .0; + let foo_baz = store + .conn + .current_schema() + .get_entid(&kw!(:foo/baz)) + .expect("foo/baz") + .0; + let foo_x = store + .conn + .current_schema() + .get_entid(&kw!(:foo/x)) + .expect("foo/x") + .0; // … and cache the others via the store. - store.cache(&kw!(:foo/baz), CacheDirection::Both).expect("cache done"); - store.cache(&kw!(:foo/x), CacheDirection::Forward).expect("cache done"); + store + .cache(&kw!(:foo/baz), CacheDirection::Both) + .expect("cache done"); + store + .cache(&kw!(:foo/x), CacheDirection::Forward) + .expect("cache done"); { assert!(store.is_attribute_cached_reverse(foo_bar)); assert!(store.is_attribute_cached_forward(foo_baz)); @@ -406,85 +438,168 @@ mod tests { assert!(in_progress.cache.is_attribute_cached_reverse(foo_baz)); assert!(in_progress.cache.is_attribute_cached_forward(foo_x)); - assert!(in_progress.cache.overlay.is_attribute_cached_reverse(foo_bar)); - assert!(in_progress.cache.overlay.is_attribute_cached_forward(foo_baz)); - assert!(in_progress.cache.overlay.is_attribute_cached_reverse(foo_baz)); + assert!(in_progress + .cache + .overlay + .is_attribute_cached_reverse(foo_bar)); + assert!(in_progress + .cache + .overlay + .is_attribute_cached_forward(foo_baz)); + assert!(in_progress + .cache + .overlay + .is_attribute_cached_reverse(foo_baz)); assert!(in_progress.cache.overlay.is_attribute_cached_forward(foo_x)); } - in_progress.transact(r#"[ + in_progress + .transact( + r#"[ {:foo/bar 15, :foo/baz false, :foo/x [1, 2, 3]} {:foo/bar 99, :foo/baz true} {:foo/bar -2, :foo/baz true} - ]"#).expect("transact"); + ]"#, + ) + .expect("transact"); // Data is in the cache. - let first = in_progress.cache.get_entid_for_value(foo_bar, &TypedValue::Long(15)).expect("id"); - assert_eq!(in_progress.cache.get_value_for_entid(&in_progress.schema, foo_baz, first).expect("val"), &TypedValue::Boolean(false)); + let first = in_progress + .cache + .get_entid_for_value(foo_bar, &TypedValue::Long(15)) + .expect("id"); + assert_eq!( + in_progress + .cache + .get_value_for_entid(&in_progress.schema, foo_baz, first) + .expect("val"), + &TypedValue::Boolean(false) + ); // All three values for :foo/x. - let all_three: BTreeSet = in_progress.cache - .get_values_for_entid(&in_progress.schema, foo_x, first) - .expect("val") - .iter().cloned().collect(); - assert_eq!(all_three, vec![1, 2, 3].into_iter().map(TypedValue::Long).collect()); + let all_three: BTreeSet = in_progress + .cache + .get_values_for_entid(&in_progress.schema, foo_x, first) + .expect("val") + .iter() + .cloned() + .collect(); + assert_eq!( + all_three, + vec![1, 2, 3].into_iter().map(TypedValue::Long).collect() + ); in_progress.commit().expect("commit"); } // Data is still in the cache. { - let first = store.get_entid_for_value(foo_bar, &TypedValue::Long(15)).expect("id"); + let first = store + .get_entid_for_value(foo_bar, &TypedValue::Long(15)) + .expect("id"); let cache: SQLiteAttributeCache = store.conn.current_cache(); - assert_eq!(cache.get_value_for_entid(&store.conn.current_schema(), foo_baz, first).expect("val"), &TypedValue::Boolean(false)); + assert_eq!( + cache + .get_value_for_entid(&store.conn.current_schema(), foo_baz, first) + .expect("val"), + &TypedValue::Boolean(false) + ); - let all_three: BTreeSet = cache.get_values_for_entid(&store.conn.current_schema(), foo_x, first) - .expect("val") - .iter().cloned().collect(); - assert_eq!(all_three, vec![1, 2, 3].into_iter().map(TypedValue::Long).collect()); + let all_three: BTreeSet = cache + .get_values_for_entid(&store.conn.current_schema(), foo_x, first) + .expect("val") + .iter() + .cloned() + .collect(); + assert_eq!( + all_three, + vec![1, 2, 3].into_iter().map(TypedValue::Long).collect() + ); } // We can remove data and the cache reflects it, immediately and after commit. { let mut in_progress = store.begin_transaction().expect("began"); - let first = in_progress.cache.get_entid_for_value(foo_bar, &TypedValue::Long(15)).expect("id"); - in_progress.transact(format!("[[:db/retract {} :foo/x 2]]", first).as_str()).expect("transact"); + let first = in_progress + .cache + .get_entid_for_value(foo_bar, &TypedValue::Long(15)) + .expect("id"); + in_progress + .transact(format!("[[:db/retract {} :foo/x 2]]", first).as_str()) + .expect("transact"); - let only_two: BTreeSet = in_progress.cache - .get_values_for_entid(&in_progress.schema, foo_x, first) - .expect("val") - .iter().cloned().collect(); - assert_eq!(only_two, vec![1, 3].into_iter().map(TypedValue::Long).collect()); + let only_two: BTreeSet = in_progress + .cache + .get_values_for_entid(&in_progress.schema, foo_x, first) + .expect("val") + .iter() + .cloned() + .collect(); + assert_eq!( + only_two, + vec![1, 3].into_iter().map(TypedValue::Long).collect() + ); // Rollback: unchanged. } { - let first = store.get_entid_for_value(foo_bar, &TypedValue::Long(15)).expect("id"); + let first = store + .get_entid_for_value(foo_bar, &TypedValue::Long(15)) + .expect("id"); let cache: SQLiteAttributeCache = store.conn.current_cache(); - assert_eq!(cache.get_value_for_entid(&store.conn.current_schema(), foo_baz, first).expect("val"), &TypedValue::Boolean(false)); + assert_eq!( + cache + .get_value_for_entid(&store.conn.current_schema(), foo_baz, first) + .expect("val"), + &TypedValue::Boolean(false) + ); - let all_three: BTreeSet = cache.get_values_for_entid(&store.conn.current_schema(), foo_x, first) - .expect("val") - .iter().cloned().collect(); - assert_eq!(all_three, vec![1, 2, 3].into_iter().map(TypedValue::Long).collect()); + let all_three: BTreeSet = cache + .get_values_for_entid(&store.conn.current_schema(), foo_x, first) + .expect("val") + .iter() + .cloned() + .collect(); + assert_eq!( + all_three, + vec![1, 2, 3].into_iter().map(TypedValue::Long).collect() + ); } // Try again, but this time commit. { let mut in_progress = store.begin_transaction().expect("began"); - let first = in_progress.cache.get_entid_for_value(foo_bar, &TypedValue::Long(15)).expect("id"); - in_progress.transact(format!("[[:db/retract {} :foo/x 2]]", first).as_str()).expect("transact"); + let first = in_progress + .cache + .get_entid_for_value(foo_bar, &TypedValue::Long(15)) + .expect("id"); + in_progress + .transact(format!("[[:db/retract {} :foo/x 2]]", first).as_str()) + .expect("transact"); in_progress.commit().expect("committed"); } { - let first = store.get_entid_for_value(foo_bar, &TypedValue::Long(15)).expect("id"); + let first = store + .get_entid_for_value(foo_bar, &TypedValue::Long(15)) + .expect("id"); let cache: SQLiteAttributeCache = store.conn.current_cache(); - assert_eq!(cache.get_value_for_entid(&store.conn.current_schema(), foo_baz, first).expect("val"), &TypedValue::Boolean(false)); + assert_eq!( + cache + .get_value_for_entid(&store.conn.current_schema(), foo_baz, first) + .expect("val"), + &TypedValue::Boolean(false) + ); - let only_two: BTreeSet = cache.get_values_for_entid(&store.conn.current_schema(), foo_x, first) - .expect("val") - .iter().cloned().collect(); - assert_eq!(only_two, vec![1, 3].into_iter().map(TypedValue::Long).collect()); + let only_two: BTreeSet = cache + .get_values_for_entid(&store.conn.current_schema(), foo_x, first) + .expect("val") + .iter() + .cloned() + .collect(); + assert_eq!( + only_two, + vec![1, 3].into_iter().map(TypedValue::Long).collect() + ); } } @@ -517,43 +632,55 @@ mod tests { fn add_schema(conn: &mut Store) { // transact some schema let mut in_progress = conn.begin_transaction().expect("expected in progress"); - in_progress.ensure_vocabulary(&Definition::new( - kw!(:todo/items), - 1, - vec![ - (kw!(:todo/uuid), - AttributeBuilder::helpful() - .value_type(ValueType::Uuid) - .multival(false) - .unique(Unique::Value) - .index(true) - .build()), - (kw!(:todo/name), - AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .fulltext(true) - .build()), - (kw!(:todo/completion_date), - AttributeBuilder::helpful() - .value_type(ValueType::Instant) - .multival(false) - .build()), - (kw!(:label/name), - AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .unique(Unique::Value) - .fulltext(true) - .index(true) - .build()), - (kw!(:label/color), - AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .build()), - ], - )).expect("expected vocubulary"); + in_progress + .ensure_vocabulary(&Definition::new( + kw!(:todo/items), + 1, + vec![ + ( + kw!(:todo/uuid), + AttributeBuilder::helpful() + .value_type(ValueType::Uuid) + .multival(false) + .unique(Unique::Value) + .index(true) + .build(), + ), + ( + kw!(:todo/name), + AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .fulltext(true) + .build(), + ), + ( + kw!(:todo/completion_date), + AttributeBuilder::helpful() + .value_type(ValueType::Instant) + .multival(false) + .build(), + ), + ( + kw!(:label/name), + AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .unique(Unique::Value) + .fulltext(true) + .index(true) + .build(), + ), + ( + kw!(:label/color), + AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .build(), + ), + ], + )) + .expect("expected vocubulary"); in_progress.commit().expect("Expected vocabulary committed"); } @@ -569,8 +696,18 @@ mod tests { let mut conn = Store::open("").unwrap(); add_schema(&mut conn); - let name_entid: Entid = conn.conn().current_schema().get_entid(&kw!(:todo/name)).expect("entid to exist for name").into(); - let date_entid: Entid = conn.conn().current_schema().get_entid(&kw!(:todo/completion_date)).expect("entid to exist for completion_date").into(); + let name_entid: Entid = conn + .conn() + .current_schema() + .get_entid(&kw!(:todo/name)) + .expect("entid to exist for name") + .into(); + let date_entid: Entid = conn + .conn() + .current_schema() + .get_entid(&kw!(:todo/completion_date)) + .expect("entid to exist for completion_date") + .into(); let mut registered_attrs = BTreeSet::new(); registered_attrs.insert(name_entid.clone()); registered_attrs.insert(date_entid.clone()); @@ -602,8 +739,18 @@ mod tests { let mut tx_ids = Vec::new(); let mut changesets = Vec::new(); - let db_tx_instant_entid: Entid = conn.conn().current_schema().get_entid(&kw!(:db/txInstant)).expect("entid to exist for :db/txInstant").into(); - let uuid_entid: Entid = conn.conn().current_schema().get_entid(&kw!(:todo/uuid)).expect("entid to exist for name").into(); + let db_tx_instant_entid: Entid = conn + .conn() + .current_schema() + .get_entid(&kw!(:db/txInstant)) + .expect("entid to exist for :db/txInstant") + .into(); + let uuid_entid: Entid = conn + .conn() + .current_schema() + .get_entid(&kw!(:todo/uuid)) + .expect("entid to exist for name") + .into(); { let mut in_progress = conn.begin_transaction().expect("expected transaction"); for i in 0..3 { @@ -612,12 +759,18 @@ mod tests { let name = format!("todo{}", i); let uuid = Uuid::new_v4(); let mut builder = in_progress.builder().describe_tempid(&name); - builder.add(kw!(:todo/uuid), TypedValue::Uuid(uuid)).expect("Expected added uuid"); + builder + .add(kw!(:todo/uuid), TypedValue::Uuid(uuid)) + .expect("Expected added uuid"); changeset.insert(uuid_entid.clone()); - builder.add(kw!(:todo/name), TypedValue::typed_string(name)).expect("Expected added name"); + builder + .add(kw!(:todo/name), TypedValue::typed_string(name)) + .expect("Expected added name"); changeset.insert(name_entid.clone()); if i % 2 == 0 { - builder.add(kw!(:todo/completion_date), TypedValue::current_instant()).expect("Expected added date"); + builder + .add(kw!(:todo/completion_date), TypedValue::current_instant()) + .expect("Expected added date"); changeset.insert(date_entid.clone()); } let (ip, r) = builder.transact(); @@ -627,8 +780,12 @@ mod tests { in_progress = ip; } let mut builder = in_progress.builder().describe_tempid("Label"); - builder.add(kw!(:label/name), TypedValue::typed_string("Label 1")).expect("Expected added name"); - builder.add(kw!(:label/color), TypedValue::typed_string("blue")).expect("Expected added color"); + builder + .add(kw!(:label/name), TypedValue::typed_string("Label 1")) + .expect("Expected added name"); + builder + .add(kw!(:label/color), TypedValue::typed_string("blue")) + .expect("Expected added color"); builder.commit().expect("expect transaction to occur"); } @@ -647,8 +804,18 @@ mod tests { let mut conn = Store::open("").unwrap(); add_schema(&mut conn); - let name_entid: Entid = conn.conn().current_schema().get_entid(&kw!(:todo/name)).expect("entid to exist for name").into(); - let date_entid: Entid = conn.conn().current_schema().get_entid(&kw!(:todo/completion_date)).expect("entid to exist for completion_date").into(); + let name_entid: Entid = conn + .conn() + .current_schema() + .get_entid(&kw!(:todo/name)) + .expect("entid to exist for name") + .into(); + let date_entid: Entid = conn + .conn() + .current_schema() + .get_entid(&kw!(:todo/completion_date)) + .expect("entid to exist for completion_date") + .into(); let mut registered_attrs = BTreeSet::new(); registered_attrs.insert(name_entid.clone()); registered_attrs.insert(date_entid.clone()); @@ -683,8 +850,12 @@ mod tests { for i in 0..3 { let name = format!("label{}", i); let mut builder = in_progress.builder().describe_tempid(&name); - builder.add(kw!(:label/name), TypedValue::typed_string(name)).expect("Expected added name"); - builder.add(kw!(:label/color), TypedValue::typed_string("blue")).expect("Expected added color"); + builder + .add(kw!(:label/name), TypedValue::typed_string(name)) + .expect("Expected added name"); + builder + .add(kw!(:label/color), TypedValue::typed_string("blue")) + .expect("Expected added color"); let (ip, _) = builder.transact(); in_progress = ip; } diff --git a/src/sync.rs b/src/sync.rs index 0b5a1d30..4067b019 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -10,19 +10,11 @@ use uuid::Uuid; -use mentat_transaction::{ - InProgress, -}; +use mentat_transaction::InProgress; -use errors::{ - Result, -}; +use super::errors::Result; -use mentat_tolstoy::{ - Syncer, - RemoteClient, - SyncReport, -}; +use mentat_tolstoy::{RemoteClient, SyncReport, Syncer}; pub trait Syncable { fn sync(&mut self, server_uri: &String, user_uuid: &String) -> Result; @@ -36,11 +28,8 @@ impl<'a, 'c> Syncable for InProgress<'a, 'c> { // and to separate concerns. // But for all intents and purposes, Syncer operates over a "mentat transaction", // which is exactly what InProgress represents. - let mut remote_client = RemoteClient::new( - server_uri.to_string(), - Uuid::parse_str(&user_uuid)? - ); - Syncer::sync(self, &mut remote_client) - .map_err(|e| e.into()) + let mut remote_client = + RemoteClient::new(server_uri.to_string(), Uuid::parse_str(&user_uuid)?); + Syncer::sync(self, &mut remote_client).map_err(|e| e.into()) } } diff --git a/src/vocabulary.rs b/src/vocabulary.rs index e708117a..614b3314 100644 --- a/src/vocabulary.rs +++ b/src/vocabulary.rs @@ -8,7 +8,6 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. - //! This module exposes an interface for programmatic management of vocabularies. //! //! A vocabulary is defined by a name, a version number, and a collection of attribute definitions. @@ -93,41 +92,20 @@ use std::collections::BTreeMap; -use core_traits::{ - KnownEntid, -}; +use core_traits::KnownEntid; -use core_traits::attribute::{ - Unique, -}; +use core_traits::attribute::Unique; -use ::{ +use super::{ + Attribute, Binding, Entid, HasSchema, IntoResult, Keyword, TypedValue, ValueType, CORE_SCHEMA_VERSION, - Attribute, - Entid, - HasSchema, - IntoResult, - Keyword, - Binding, - TypedValue, - ValueType, }; -use ::errors::{ - MentatError, - Result, -}; +use super::errors::{MentatError, Result}; -use mentat_transaction::{ - InProgress, - Queryable, -}; +use mentat_transaction::{InProgress, Queryable}; -use mentat_transaction::entity_builder::{ - BuildTerms, - TermBuilder, - Terms, -}; +use mentat_transaction::entity_builder::{BuildTerms, TermBuilder, Terms}; /// AttributeBuilder is how you build vocabulary definitions to apply to a store. pub use mentat_db::AttributeBuilder; @@ -158,8 +136,8 @@ pub struct Definition { pub name: Keyword, pub version: Version, pub attributes: Vec<(Keyword, Attribute)>, - pub pre: fn(&mut InProgress, &Vocabulary) -> Result<()>, - pub post: fn(&mut InProgress, &Vocabulary) -> Result<()>, + pub pre: fn(&mut InProgress<'_, '_>, &Vocabulary) -> Result<()>, + pub post: fn(&mut InProgress<'_, '_>, &Vocabulary) -> Result<()>, } /// ``` @@ -245,13 +223,15 @@ pub struct Definition { /// } /// ``` impl Definition { - pub fn no_op(_ip: &mut InProgress, _from: &Vocabulary) -> Result<()> { + pub fn no_op(_ip: &mut InProgress<'_, '_>, _from: &Vocabulary) -> Result<()> { Ok(()) } pub fn new(name: N, version: Version, attributes: A) -> Definition - where N: Into, - A: Into> { + where + N: Into, + A: Into>, + { Definition { name: name.into(), version: version, @@ -263,13 +243,13 @@ impl Definition { /// Called with an in-progress transaction and the previous vocabulary version /// if the definition's version is later than that of the vocabulary in the store. - fn pre(&self, ip: &mut InProgress, from: &Vocabulary) -> Result<()> { + fn pre(&self, ip: &mut InProgress<'_, '_>, from: &Vocabulary) -> Result<()> { (self.pre)(ip, from) } /// Called with an in-progress transaction and the previous vocabulary version /// if the definition's version is later than that of the vocabulary in the store. - fn post(&self, ip: &mut InProgress, from: &Vocabulary) -> Result<()> { + fn post(&self, ip: &mut InProgress<'_, '_>, from: &Vocabulary) -> Result<()> { (self.post)(ip, from) } } @@ -293,7 +273,7 @@ impl Vocabulary { /// A collection of named `Vocabulary` instances, as retrieved from the store. #[derive(Debug, Default, Clone)] -pub struct Vocabularies(pub BTreeMap); // N.B., this has a copy of the attributes in Schema! +pub struct Vocabularies(pub BTreeMap); // N.B., this has a copy of the attributes in Schema! impl Vocabularies { pub fn len(&self) -> usize { @@ -304,58 +284,27 @@ impl Vocabularies { self.0.get(name) } - pub fn iter(&self) -> ::std::collections::btree_map::Iter { + pub fn iter(&self) -> ::std::collections::btree_map::Iter<'_, Keyword, Vocabulary> { self.0.iter() } } lazy_static! { - static ref DB_SCHEMA_CORE: Keyword = { - kw!(:db.schema/core) - }; - static ref DB_SCHEMA_ATTRIBUTE: Keyword = { - kw!(:db.schema/attribute) - }; - static ref DB_SCHEMA_VERSION: Keyword = { - kw!(:db.schema/version) - }; - static ref DB_IDENT: Keyword = { - kw!(:db/ident) - }; - static ref DB_UNIQUE: Keyword = { - kw!(:db/unique) - }; - static ref DB_UNIQUE_VALUE: Keyword = { - kw!(:db.unique/value) - }; - static ref DB_UNIQUE_IDENTITY: Keyword = { - kw!(:db.unique/identity) - }; - static ref DB_IS_COMPONENT: Keyword = { - Keyword::namespaced("db", "isComponent") - }; - static ref DB_VALUE_TYPE: Keyword = { - Keyword::namespaced("db", "valueType") - }; - static ref DB_INDEX: Keyword = { - kw!(:db/index) - }; - static ref DB_FULLTEXT: Keyword = { - kw!(:db/fulltext) - }; - static ref DB_CARDINALITY: Keyword = { - kw!(:db/cardinality) - }; - static ref DB_CARDINALITY_ONE: Keyword = { - kw!(:db.cardinality/one) - }; - static ref DB_CARDINALITY_MANY: Keyword = { - kw!(:db.cardinality/many) - }; - - static ref DB_NO_HISTORY: Keyword = { - Keyword::namespaced("db", "noHistory") - }; + static ref DB_SCHEMA_CORE: Keyword = { kw!(:db.schema/core) }; + static ref DB_SCHEMA_ATTRIBUTE: Keyword = { kw!(:db.schema/attribute) }; + static ref DB_SCHEMA_VERSION: Keyword = { kw!(:db.schema/version) }; + static ref DB_IDENT: Keyword = { kw!(:db/ident) }; + static ref DB_UNIQUE: Keyword = { kw!(:db/unique) }; + static ref DB_UNIQUE_VALUE: Keyword = { kw!(:db.unique/value) }; + static ref DB_UNIQUE_IDENTITY: Keyword = { kw!(:db.unique/identity) }; + static ref DB_IS_COMPONENT: Keyword = { Keyword::namespaced("db", "isComponent") }; + static ref DB_VALUE_TYPE: Keyword = { Keyword::namespaced("db", "valueType") }; + static ref DB_INDEX: Keyword = { kw!(:db/index) }; + static ref DB_FULLTEXT: Keyword = { kw!(:db/fulltext) }; + static ref DB_CARDINALITY: Keyword = { kw!(:db/cardinality) }; + static ref DB_CARDINALITY_ONE: Keyword = { kw!(:db.cardinality/one) }; + static ref DB_CARDINALITY_MANY: Keyword = { kw!(:db.cardinality/many) }; + static ref DB_NO_HISTORY: Keyword = { Keyword::namespaced("db", "noHistory") }; } trait HasCoreSchema { @@ -370,7 +319,10 @@ trait HasCoreSchema { fn core_attribute(&self, ident: &Keyword) -> Result; } -impl HasCoreSchema for T where T: HasSchema { +impl HasCoreSchema for T +where + T: HasSchema, +{ fn core_type(&self, t: ValueType) -> Result { self.entid_for_type(t) .ok_or_else(|| MentatError::MissingCoreVocabulary(DB_SCHEMA_VERSION.clone()).into()) @@ -389,10 +341,16 @@ impl HasCoreSchema for T where T: HasSchema { } impl Definition { - fn description_for_attributes<'s, T, R>(&'s self, attributes: &[R], via: &T, diff: Option>) -> Result - where T: HasCoreSchema, - R: ::std::borrow::Borrow<(Keyword, Attribute)> { - + fn description_for_attributes<'s, T, R>( + &'s self, + attributes: &[R], + via: &T, + diff: Option>, + ) -> Result + where + T: HasCoreSchema, + R: ::std::borrow::Borrow<(Keyword, Attribute)>, + { // The attributes we'll need to describe this vocabulary. let a_version = via.core_attribute(&DB_SCHEMA_VERSION)?; let a_ident = via.core_attribute(&DB_IDENT)?; @@ -445,9 +403,21 @@ impl Definition { // These are all unconditional because we use attribute descriptions to _alter_, not // just to _add_, and so absence is distinct from negation! builder.add(tempid.clone(), a_index, TypedValue::Boolean(attr.index))?; - builder.add(tempid.clone(), a_fulltext, TypedValue::Boolean(attr.fulltext))?; - builder.add(tempid.clone(), a_is_component, TypedValue::Boolean(attr.component))?; - builder.add(tempid.clone(), a_no_history, TypedValue::Boolean(attr.no_history))?; + builder.add( + tempid.clone(), + a_fulltext, + TypedValue::Boolean(attr.fulltext), + )?; + builder.add( + tempid.clone(), + a_is_component, + TypedValue::Boolean(attr.component), + )?; + builder.add( + tempid.clone(), + a_no_history, + TypedValue::Boolean(attr.no_history), + )?; if let Some(u) = attr.unique { let uu = match u { @@ -456,23 +426,22 @@ impl Definition { }; builder.add(tempid.clone(), a_unique, uu)?; } else { - let existing_unique = - if let Some(ref diff) = diff { - diff.get(kw).and_then(|a| a.unique) - } else { - None - }; - match existing_unique { + let existing_unique = if let Some(ref diff) = diff { + diff.get(kw).and_then(|a| a.unique) + } else { + None + }; + match existing_unique { None => { // Nothing to do. - }, + } Some(Unique::Identity) => { builder.retract(tempid.clone(), a_unique, v_unique_identity.clone())?; - }, + } Some(Unique::Value) => { builder.retract(tempid.clone(), a_unique, v_unique_value.clone())?; - }, - } + } + } } } @@ -480,9 +449,14 @@ impl Definition { } /// Return a sequence of terms that describes this vocabulary definition and its attributes. - fn description_diff(&self, via: &T, from: &Vocabulary) -> Result where T: HasSchema { - let relevant = self.attributes.iter() - .filter_map(|&(ref keyword, _)| + fn description_diff(&self, via: &T, from: &Vocabulary) -> Result + where + T: HasSchema, + { + let relevant = self + .attributes + .iter() + .filter_map(|&(ref keyword, _)| // Look up the keyword to see if it's currently in use. via.get_entid(keyword) @@ -491,12 +465,15 @@ impl Definition { // Collect enough that we can do lookups. .map(|e| (keyword.clone(), e))) - .collect(); + .collect(); self.description_for_attributes(self.attributes.as_slice(), via, Some(relevant)) } /// Return a sequence of terms that describes this vocabulary definition and its attributes. - fn description(&self, via: &T) -> Result where T: HasSchema { + fn description(&self, via: &T) -> Result + where + T: HasSchema, + { self.description_for_attributes(self.attributes.as_slice(), via, None) } } @@ -518,7 +495,9 @@ pub enum VocabularyCheck<'definition> { PresentButTooNew { newer_version: Vocabulary }, /// The provided definition is present in the store, but some of its attributes are not. - PresentButMissingAttributes { attributes: Vec<&'definition (Keyword, Attribute)> }, + PresentButMissingAttributes { + attributes: Vec<&'definition (Keyword, Attribute)>, + }, } /// This enum captures the outcome of attempting to ensure that a vocabulary definition is present @@ -549,7 +528,10 @@ pub trait HasVocabularies { /// This trait captures the ability of a store to check and install/upgrade vocabularies. pub trait VersionedStore: HasVocabularies + HasSchema { /// Check whether the vocabulary described by the provided metadata is present in the store. - fn check_vocabulary<'definition>(&self, definition: &'definition Definition) -> Result> { + fn check_vocabulary<'definition>( + &self, + definition: &'definition Definition, + ) -> Result> { if let Some(vocabulary) = self.read_vocabulary_named(&definition.name)? { // The name is present. // Check the version. @@ -567,12 +549,12 @@ pub trait VersionedStore: HasVocabularies + HasSchema { // different definitions for an attribute. That's a coding error. // We can't accept this vocabulary. bail!(MentatError::ConflictingAttributeDefinitions( - definition.name.to_string(), - definition.version, - pair.0.to_string(), - existing.clone(), - pair.1.clone()) - ); + definition.name.to_string(), + definition.version, + pair.0.to_string(), + existing.clone(), + pair.1.clone() + )); } } } @@ -582,14 +564,20 @@ pub trait VersionedStore: HasVocabularies + HasSchema { if missing.is_empty() { Ok(VocabularyCheck::Present) } else { - Ok(VocabularyCheck::PresentButMissingAttributes { attributes: missing }) + Ok(VocabularyCheck::PresentButMissingAttributes { + attributes: missing, + }) } } else if vocabulary.version < definition.version { // Ours is newer. Upgrade. - Ok(VocabularyCheck::PresentButNeedsUpdate { older_version: vocabulary }) + Ok(VocabularyCheck::PresentButNeedsUpdate { + older_version: vocabulary, + }) } else { // The vocabulary in the store is newer. We are outdated. - Ok(VocabularyCheck::PresentButTooNew { newer_version: vocabulary }) + Ok(VocabularyCheck::PresentButTooNew { + newer_version: vocabulary, + }) } } else { // The vocabulary isn't present in the store. Install it. @@ -607,16 +595,22 @@ pub trait VersionedStore: HasVocabularies + HasSchema { /// /// Use this function instead of calling `ensure_vocabulary` if you need to have pre/post /// functions invoked when vocabulary changes are necessary. - fn ensure_vocabularies(&mut self, vocabularies: &mut VocabularySource) -> Result>; + fn ensure_vocabularies( + &mut self, + vocabularies: &mut dyn VocabularySource, + ) -> Result>; /// Make sure that our expectations of the core vocabulary — basic types and attributes — are met. fn verify_core_schema(&self) -> Result<()> { if let Some(core) = self.read_vocabulary_named(&DB_SCHEMA_CORE)? { if core.version != CORE_SCHEMA_VERSION { - bail!(MentatError::UnexpectedCoreSchema(CORE_SCHEMA_VERSION, Some(core.version))); + bail!(MentatError::UnexpectedCoreSchema( + CORE_SCHEMA_VERSION, + Some(core.version) + )); } - // TODO: check things other than the version. + // TODO: check things other than the version. } else { // This would be seriously messed up. bail!(MentatError::UnexpectedCoreSchema(CORE_SCHEMA_VERSION, None)); @@ -630,7 +624,7 @@ pub trait VersionedStore: HasVocabularies + HasSchema { /// vocabularies — you can retrieve the requested definition and the resulting `VocabularyCheck` /// by name. pub trait VocabularyStatus { - fn get(&self, name: &Keyword) -> Option<(&Definition, &VocabularyCheck)>; + fn get(&self, name: &Keyword) -> Option<(&Definition, &VocabularyCheck<'_>)>; fn version(&self, name: &Keyword) -> Option; } @@ -641,7 +635,8 @@ struct CheckedVocabularies<'a> { impl<'a> CheckedVocabularies<'a> { fn add(&mut self, definition: &'a Definition, check: VocabularyCheck<'a>) { - self.items.insert(definition.name.clone(), (definition, check)); + self.items + .insert(definition.name.clone(), (definition, check)); } fn is_empty(&self) -> bool { @@ -650,7 +645,7 @@ impl<'a> CheckedVocabularies<'a> { } impl<'a> VocabularyStatus for CheckedVocabularies<'a> { - fn get(&self, name: &Keyword) -> Option<(&Definition, &VocabularyCheck)> { + fn get(&self, name: &Keyword) -> Option<(&Definition, &VocabularyCheck<'_>)> { self.items.get(name).map(|&(ref d, ref c)| (*d, c)) } @@ -661,15 +656,29 @@ impl<'a> VocabularyStatus for CheckedVocabularies<'a> { trait VocabularyMechanics { fn install_vocabulary(&mut self, definition: &Definition) -> Result; - fn install_attributes_for<'definition>(&mut self, definition: &'definition Definition, attributes: Vec<&'definition (Keyword, Attribute)>) -> Result; - fn upgrade_vocabulary(&mut self, definition: &Definition, from_version: Vocabulary) -> Result; + fn install_attributes_for<'definition>( + &mut self, + definition: &'definition Definition, + attributes: Vec<&'definition (Keyword, Attribute)>, + ) -> Result; + fn upgrade_vocabulary( + &mut self, + definition: &Definition, + from_version: Vocabulary, + ) -> Result; } impl Vocabulary { // TODO: don't do linear search! - fn find(&self, entid: T) -> Option<&Attribute> where T: Into { + fn find(&self, entid: T) -> Option<&Attribute> + where + T: Into, + { let to_find = entid.into(); - self.attributes.iter().find(|&&(e, _)| e == to_find).map(|&(_, ref a)| a) + self.attributes + .iter() + .find(|&&(e, _)| e == to_find) + .map(|&(_, ref a)| a) } } @@ -678,16 +687,30 @@ impl<'a, 'c> VersionedStore for InProgress<'a, 'c> { match self.check_vocabulary(definition)? { VocabularyCheck::Present => Ok(VocabularyOutcome::Existed), VocabularyCheck::NotPresent => self.install_vocabulary(definition), - VocabularyCheck::PresentButNeedsUpdate { older_version } => self.upgrade_vocabulary(definition, older_version), - VocabularyCheck::PresentButMissingAttributes { attributes } => self.install_attributes_for(definition, attributes), - VocabularyCheck::PresentButTooNew { newer_version } => Err(MentatError::ExistingVocabularyTooNew(definition.name.to_string(), newer_version.version, definition.version).into()), + VocabularyCheck::PresentButNeedsUpdate { older_version } => { + self.upgrade_vocabulary(definition, older_version) + } + VocabularyCheck::PresentButMissingAttributes { attributes } => { + self.install_attributes_for(definition, attributes) + } + VocabularyCheck::PresentButTooNew { newer_version } => { + Err(MentatError::ExistingVocabularyTooNew( + definition.name.to_string(), + newer_version.version, + definition.version, + ) + .into()) + } } } - fn ensure_vocabularies(&mut self, vocabularies: &mut VocabularySource) -> Result> { + fn ensure_vocabularies( + &mut self, + vocabularies: &mut dyn VocabularySource, + ) -> Result> { let definitions = vocabularies.definitions(); - let mut update = Vec::new(); + let mut update = Vec::new(); let mut missing = Vec::new(); let mut out = BTreeMap::new(); @@ -697,16 +720,20 @@ impl<'a, 'c> VersionedStore for InProgress<'a, 'c> { match self.check_vocabulary(definition)? { VocabularyCheck::Present => { out.insert(definition.name.clone(), VocabularyOutcome::Existed); - }, + } VocabularyCheck::PresentButTooNew { newer_version } => { - bail!(MentatError::ExistingVocabularyTooNew(definition.name.to_string(), newer_version.version, definition.version)); - }, + bail!(MentatError::ExistingVocabularyTooNew( + definition.name.to_string(), + newer_version.version, + definition.version + )); + } - c @ VocabularyCheck::NotPresent | - c @ VocabularyCheck::PresentButNeedsUpdate { older_version: _ } | - c @ VocabularyCheck::PresentButMissingAttributes { attributes: _ } => { + c @ VocabularyCheck::NotPresent + | c @ VocabularyCheck::PresentButNeedsUpdate { older_version: _ } + | c @ VocabularyCheck::PresentButMissingAttributes { attributes: _ } => { work.add(definition, c); - }, + } } } @@ -722,17 +749,17 @@ impl<'a, 'c> VersionedStore for InProgress<'a, 'c> { VocabularyCheck::NotPresent => { // Install it directly. out.insert(name, self.install_vocabulary(definition)?); - }, + } VocabularyCheck::PresentButNeedsUpdate { older_version } => { // Save this: we'll do it later. update.push((definition, older_version)); - }, + } VocabularyCheck::PresentButMissingAttributes { attributes } => { // Save this: we'll do it later. missing.push((definition, attributes)); - }, - VocabularyCheck::Present | - VocabularyCheck::PresentButTooNew { newer_version: _ } => { + } + VocabularyCheck::Present + | VocabularyCheck::PresentButTooNew { newer_version: _ } => { unreachable!(); } } @@ -758,13 +785,17 @@ pub trait VocabularySource { /// Called before the supplied `Definition`s are transacted. Do not commit the `InProgress`. /// If this function returns `Err`, the entire vocabulary operation will fail. - fn pre(&mut self, _in_progress: &mut InProgress, _checks: &VocabularyStatus) -> Result<()> { + fn pre( + &mut self, + _in_progress: &mut InProgress<'_, '_>, + _checks: &dyn VocabularyStatus, + ) -> Result<()> { Ok(()) } /// Called after the supplied `Definition`s are transacted. Do not commit the `InProgress`. /// If this function returns `Err`, the entire vocabulary operation will fail. - fn post(&mut self, _in_progress: &mut InProgress) -> Result<()> { + fn post(&mut self, _in_progress: &mut InProgress<'_, '_>) -> Result<()> { Ok(()) } } @@ -773,14 +804,16 @@ pub trait VocabularySource { /// vocabulary `Definition`s. pub struct SimpleVocabularySource { pub definitions: Vec, - pub pre: Option Result<()>>, - pub post: Option Result<()>>, + pub pre: Option) -> Result<()>>, + pub post: Option) -> Result<()>>, } impl SimpleVocabularySource { - pub fn new(definitions: Vec, - pre: Option Result<()>>, - post: Option Result<()>>) -> SimpleVocabularySource { + pub fn new( + definitions: Vec, + pre: Option) -> Result<()>>, + post: Option) -> Result<()>>, + ) -> SimpleVocabularySource { SimpleVocabularySource { pre: pre, post: post, @@ -794,11 +827,15 @@ impl SimpleVocabularySource { } impl VocabularySource for SimpleVocabularySource { - fn pre(&mut self, in_progress: &mut InProgress, _checks: &VocabularyStatus) -> Result<()> { + fn pre( + &mut self, + in_progress: &mut InProgress<'_, '_>, + _checks: &dyn VocabularyStatus, + ) -> Result<()> { self.pre.map(|pre| (pre)(in_progress)).unwrap_or(Ok(())) } - fn post(&mut self, in_progress: &mut InProgress) -> Result<()> { + fn post(&mut self, in_progress: &mut InProgress<'_, '_>) -> Result<()> { self.post.map(|pre| (pre)(in_progress)).unwrap_or(Ok(())) } @@ -815,7 +852,11 @@ impl<'a, 'c> VocabularyMechanics for InProgress<'a, 'c> { Ok(VocabularyOutcome::Installed) } - fn install_attributes_for<'definition>(&mut self, definition: &'definition Definition, attributes: Vec<&'definition (Keyword, Attribute)>) -> Result { + fn install_attributes_for<'definition>( + &mut self, + definition: &'definition Definition, + attributes: Vec<&'definition (Keyword, Attribute)>, + ) -> Result { let (terms, _tempids) = definition.description_for_attributes(&attributes, self, None)?; self.transact_entities(terms)?; Ok(VocabularyOutcome::InstalledMissingAttributes) @@ -823,7 +864,11 @@ impl<'a, 'c> VocabularyMechanics for InProgress<'a, 'c> { /// Turn the declarative parts of the vocabulary into alterations. Run the 'pre' steps. /// Transact the changes. Run the 'post' steps. Return the result and the new `InProgress`! - fn upgrade_vocabulary(&mut self, definition: &Definition, from_version: Vocabulary) -> Result { + fn upgrade_vocabulary( + &mut self, + definition: &Definition, + from_version: Vocabulary, + ) -> Result { // It's sufficient for us to generate the datom form of each attribute and transact that. // We trust that the vocabulary will implement a 'pre' function that cleans up data for any // failable conversion (e.g., cardinality-many to cardinality-one). @@ -840,32 +885,35 @@ impl<'a, 'c> VocabularyMechanics for InProgress<'a, 'c> { } } -impl HasVocabularies for T where T: HasSchema + Queryable { +impl HasVocabularies for T +where + T: HasSchema + Queryable, +{ fn read_vocabulary_named(&self, name: &Keyword) -> Result> { if let Some(entid) = self.get_entid(name) { match self.lookup_value_for_attribute(entid, &DB_SCHEMA_VERSION)? { None => Ok(None), Some(TypedValue::Long(version)) - if version > 0 && (version < u32::max_value() as i64) => { - let version = version as u32; - let attributes = self.lookup_values_for_attribute(entid, &DB_SCHEMA_ATTRIBUTE)? - .into_iter() - .filter_map(|a| { - if let TypedValue::Ref(a) = a { - self.attribute_for_entid(a) - .cloned() - .map(|attr| (a, attr)) - } else { - None - } - }) - .collect(); - Ok(Some(Vocabulary { - entity: entid.into(), - version: version, - attributes: attributes, - })) - }, + if version > 0 && (version < u32::max_value() as i64) => + { + let version = version as u32; + let attributes = self + .lookup_values_for_attribute(entid, &DB_SCHEMA_ATTRIBUTE)? + .into_iter() + .filter_map(|a| { + if let TypedValue::Ref(a) = a { + self.attribute_for_entid(a).cloned().map(|attr| (a, attr)) + } else { + None + } + }) + .collect(); + Ok(Some(Vocabulary { + entity: entid.into(), + version: version, + attributes: attributes, + })) + } Some(_) => bail!(MentatError::InvalidVocabularyVersion), } } else { @@ -875,73 +923,90 @@ impl HasVocabularies for T where T: HasSchema + Queryable { fn read_vocabularies(&self) -> Result { // This would be way easier with pull expressions. #110. - let versions: BTreeMap = - self.q_once(r#"[:find ?vocab ?version - :where [?vocab :db.schema/version ?version]]"#, None) - .into_rel_result()? - .into_iter() - .filter_map(|v| - match (&v[0], &v[1]) { - (&Binding::Scalar(TypedValue::Ref(vocab)), - &Binding::Scalar(TypedValue::Long(version))) - if version > 0 && (version < u32::max_value() as i64) => Some((vocab, version as u32)), - (_, _) => None, - }) - .collect(); + let versions: BTreeMap = self + .q_once( + r#"[:find ?vocab ?version + :where [?vocab :db.schema/version ?version]]"#, + None, + ) + .into_rel_result()? + .into_iter() + .filter_map(|v| match (&v[0], &v[1]) { + ( + &Binding::Scalar(TypedValue::Ref(vocab)), + &Binding::Scalar(TypedValue::Long(version)), + ) if version > 0 && (version < u32::max_value() as i64) => { + Some((vocab, version as u32)) + } + (_, _) => None, + }) + .collect(); let mut attributes = BTreeMap::>::new(); - let pairs = - self.q_once("[:find ?vocab ?attr :where [?vocab :db.schema/attribute ?attr]]", None) - .into_rel_result()? - .into_iter() - .filter_map(|v| { - match (&v[0], &v[1]) { - (&Binding::Scalar(TypedValue::Ref(vocab)), - &Binding::Scalar(TypedValue::Ref(attr))) => Some((vocab, attr)), - (_, _) => None, - } - }); + let pairs = self + .q_once( + "[:find ?vocab ?attr :where [?vocab :db.schema/attribute ?attr]]", + None, + ) + .into_rel_result()? + .into_iter() + .filter_map(|v| match (&v[0], &v[1]) { + ( + &Binding::Scalar(TypedValue::Ref(vocab)), + &Binding::Scalar(TypedValue::Ref(attr)), + ) => Some((vocab, attr)), + (_, _) => None, + }); // TODO: validate that attributes.keys is a subset of versions.keys. for (vocab, attr) in pairs { if let Some(attribute) = self.attribute_for_entid(attr).cloned() { - attributes.entry(vocab).or_insert(Vec::new()).push((attr, attribute)); + attributes + .entry(vocab) + .or_insert(Vec::new()) + .push((attr, attribute)); } } // TODO: return more errors? // We walk versions first in order to support vocabularies with no attributes. - Ok(Vocabularies(versions.into_iter().filter_map(|(vocab, version)| { - // Get the name. - self.get_ident(vocab).cloned() - .map(|name| { - let attrs = attributes.remove(&vocab).unwrap_or(vec![]); - (name.clone(), Vocabulary { - entity: vocab, - version: version, - attributes: attrs, + Ok(Vocabularies( + versions + .into_iter() + .filter_map(|(vocab, version)| { + // Get the name. + self.get_ident(vocab).cloned().map(|name| { + let attrs = attributes.remove(&vocab).unwrap_or(vec![]); + ( + name.clone(), + Vocabulary { + entity: vocab, + version: version, + attributes: attrs, + }, + ) }) }) - }).collect())) + .collect(), + )) } } #[cfg(test)] mod tests { - use ::{ - Store, - }; + use crate::Store; - use super::{ - HasVocabularies, - }; + use super::HasVocabularies; #[test] fn test_read_vocabularies() { let mut store = Store::open("").expect("opened"); - let vocabularies = store.begin_read().expect("in progress") - .read_vocabularies().expect("OK"); + let vocabularies = store + .begin_read() + .expect("in progress") + .read_vocabularies() + .expect("OK"); assert_eq!(vocabularies.len(), 1); let core = vocabularies.get(&kw!(:db.schema/core)).expect("exists"); assert_eq!(core.version, 1); @@ -953,6 +1018,12 @@ mod tests { let in_progress = store.begin_transaction().expect("in progress"); let vocab = in_progress.read_vocabularies().expect("vocabulary"); assert_eq!(1, vocab.len()); - assert_eq!(1, vocab.get(&kw!(:db.schema/core)).expect("core vocab").version); + assert_eq!( + 1, + vocab + .get(&kw!(:db.schema/core)) + .expect("core vocab") + .version + ); } } diff --git a/tests/api.rs b/tests/api.rs index 6c39cd16..03dc6f36 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -10,14 +10,5 @@ // Verify that our public API can be imported. -extern crate mentat; - #[allow(unused_imports)] -use mentat::{ - Conn, - QueryResults, - TypedValue, - ValueType, - conn, - new_connection, -}; +use mentat::{conn, new_connection, Conn, QueryResults, TypedValue, ValueType}; diff --git a/tests/cache.rs b/tests/cache.rs index fe64cb36..9dc3cfe4 100644 --- a/tests/cache.rs +++ b/tests/cache.rs @@ -8,38 +8,22 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate rusqlite; - -#[macro_use] -extern crate mentat; -extern crate mentat_core; -extern crate mentat_db; +//extern crate mentat; +use mentat::{self, kw, Binding, Entid, HasSchema, Queryable, Schema, Store, TypedValue}; +use mentat_core::{self, CachedAttributes}; +use mentat_db; use std::collections::BTreeSet; -use mentat_core::{ - CachedAttributes, -}; - -use mentat::{ - Entid, - HasSchema, - Queryable, - Schema, - Store, - Binding, - TypedValue, -}; - -use mentat_db::cache::{ - SQLiteAttributeCache, -}; +use mentat_db::cache::SQLiteAttributeCache; fn populate_db() -> Store { let mut store = Store::open("").expect("opened"); { let mut write = store.begin_transaction().expect("began transaction"); - let _report = write.transact(r#"[ + let _report = write + .transact( + r#"[ {:db/ident :foo/bar :db/valueType :db.type/long :db/cardinality :db.cardinality/one }, @@ -48,8 +32,12 @@ fn populate_db() -> Store { :db/cardinality :db.cardinality/one }, {:db/ident :foo/bap :db/valueType :db.type/string - :db/cardinality :db.cardinality/many}]"#).expect("transaction expected to succeed"); - let _report = write.transact(r#"[ + :db/cardinality :db.cardinality/many}]"#, + ) + .expect("transaction expected to succeed"); + let _report = write + .transact( + r#"[ {:db/ident :item/one :foo/bar 100 :foo/baz false @@ -57,26 +45,45 @@ fn populate_db() -> Store { {:db/ident :item/two :foo/bar 200 :foo/baz true - :foo/bap ["three", "four", "knock at my door"] }]"#).expect("transaction expected to succeed"); + :foo/bap ["three", "four", "knock at my door"] }]"#, + ) + .expect("transaction expected to succeed"); write.commit().expect("committed"); } store } -fn assert_value_present_for_attribute(schema: &Schema, attribute_cache: &mut SQLiteAttributeCache, attribute: Entid, entity: Entid, value: TypedValue) { +fn assert_value_present_for_attribute( + schema: &Schema, + attribute_cache: &mut SQLiteAttributeCache, + attribute: Entid, + entity: Entid, + value: TypedValue, +) { let one = attribute_cache.get_value_for_entid(schema, attribute, entity); - assert!(attribute_cache.get_values_for_entid(schema, attribute, entity).is_none()); + assert!(attribute_cache + .get_values_for_entid(schema, attribute, entity) + .is_none()); assert_eq!(one, Some(&value)); } -fn assert_values_present_for_attribute(schema: &Schema, attribute_cache: &mut SQLiteAttributeCache, attribute: Entid, entity: Entid, values: Vec) { - assert!(attribute_cache.get_value_for_entid(schema, attribute, entity).is_none()); - let actual: BTreeSet = attribute_cache.get_values_for_entid(schema, attribute, entity) - .expect("Non-None") - .clone() - .into_iter() - .collect(); +fn assert_values_present_for_attribute( + schema: &Schema, + attribute_cache: &mut SQLiteAttributeCache, + attribute: Entid, + entity: Entid, + values: Vec, +) { + assert!(attribute_cache + .get_value_for_entid(schema, attribute, entity) + .is_none()); + let actual: BTreeSet = attribute_cache + .get_values_for_entid(schema, attribute, entity) + .expect("Non-None") + .clone() + .into_iter() + .collect(); let expected: BTreeSet = values.into_iter().collect(); assert_eq!(actual, expected); @@ -88,18 +95,26 @@ fn test_add_to_cache() { let schema = &store.conn().current_schema(); let mut attribute_cache = SQLiteAttributeCache::default(); let kw = kw!(:foo/bar); - let attr: Entid = schema.get_entid(&kw).expect("Expected entid for attribute").into(); + let attr: Entid = schema + .get_entid(&kw) + .expect("Expected entid for attribute") + .into(); { assert!(attribute_cache.value_pairs(schema, attr).is_none()); } - attribute_cache.register(&schema, &store.sqlite_mut(), attr).expect("No errors on add to cache"); + attribute_cache + .register(&schema, &store.sqlite_mut(), attr) + .expect("No errors on add to cache"); { let cached_values = attribute_cache.value_pairs(schema, attr).expect("non-None"); assert!(!cached_values.is_empty()); - let flattened: BTreeSet = cached_values.values().cloned().filter_map(|x| x).collect(); - let expected: BTreeSet = vec![TypedValue::Long(100), TypedValue::Long(200)].into_iter().collect(); + let flattened: BTreeSet = + cached_values.values().cloned().filter_map(|x| x).collect(); + let expected: BTreeSet = vec![TypedValue::Long(100), TypedValue::Long(200)] + .into_iter() + .collect(); assert_eq!(flattened, expected); } } @@ -110,15 +125,34 @@ fn test_add_attribute_already_in_cache() { let schema = store.conn().current_schema(); let kw = kw!(:foo/bar); - let attr: Entid = schema.get_entid(&kw).expect("Expected entid for attribute").into(); + let attr: Entid = schema + .get_entid(&kw) + .expect("Expected entid for attribute") + .into(); let mut attribute_cache = SQLiteAttributeCache::default(); let one = schema.get_entid(&kw!(:item/one)).expect("one"); let two = schema.get_entid(&kw!(:item/two)).expect("two"); - attribute_cache.register(&schema, &mut store.sqlite_mut(), attr).expect("No errors on add to cache"); - assert_value_present_for_attribute(&schema, &mut attribute_cache, attr.into(), one.into(), TypedValue::Long(100)); - attribute_cache.register(&schema, &mut store.sqlite_mut(), attr).expect("No errors on add to cache"); - assert_value_present_for_attribute(&schema, &mut attribute_cache, attr.into(), two.into(), TypedValue::Long(200)); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), attr) + .expect("No errors on add to cache"); + assert_value_present_for_attribute( + &schema, + &mut attribute_cache, + attr.into(), + one.into(), + TypedValue::Long(100), + ); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), attr) + .expect("No errors on add to cache"); + assert_value_present_for_attribute( + &schema, + &mut attribute_cache, + attr.into(), + two.into(), + TypedValue::Long(200), + ); } #[test] @@ -127,46 +161,119 @@ fn test_remove_from_cache() { let schema = store.conn().current_schema(); let kwr = kw!(:foo/bar); - let entidr: Entid = schema.get_entid(&kwr).expect("Expected entid for attribute").into(); + let entidr: Entid = schema + .get_entid(&kwr) + .expect("Expected entid for attribute") + .into(); let kwz = kw!(:foo/baz); - let entidz: Entid = schema.get_entid(&kwz).expect("Expected entid for attribute").into(); + let entidz: Entid = schema + .get_entid(&kwz) + .expect("Expected entid for attribute") + .into(); let kwp = kw!(:foo/bap); - let entidp: Entid = schema.get_entid(&kwp).expect("Expected entid for attribute").into(); + let entidp: Entid = schema + .get_entid(&kwp) + .expect("Expected entid for attribute") + .into(); let mut attribute_cache = SQLiteAttributeCache::default(); let one = schema.get_entid(&kw!(:item/one)).expect("one"); let two = schema.get_entid(&kw!(:item/two)).expect("two"); - assert!(attribute_cache.get_value_for_entid(&schema, entidz, one.into()).is_none()); - assert!(attribute_cache.get_values_for_entid(&schema, entidz, one.into()).is_none()); - assert!(attribute_cache.get_value_for_entid(&schema, entidz, two.into()).is_none()); - assert!(attribute_cache.get_values_for_entid(&schema, entidz, two.into()).is_none()); - assert!(attribute_cache.get_value_for_entid(&schema, entidp, one.into()).is_none()); - assert!(attribute_cache.get_values_for_entid(&schema, entidp, one.into()).is_none()); + assert!(attribute_cache + .get_value_for_entid(&schema, entidz, one.into()) + .is_none()); + assert!(attribute_cache + .get_values_for_entid(&schema, entidz, one.into()) + .is_none()); + assert!(attribute_cache + .get_value_for_entid(&schema, entidz, two.into()) + .is_none()); + assert!(attribute_cache + .get_values_for_entid(&schema, entidz, two.into()) + .is_none()); + assert!(attribute_cache + .get_value_for_entid(&schema, entidp, one.into()) + .is_none()); + assert!(attribute_cache + .get_values_for_entid(&schema, entidp, one.into()) + .is_none()); - attribute_cache.register(&schema, &mut store.sqlite_mut(), entidr).expect("No errors on add to cache"); - assert_value_present_for_attribute(&schema, &mut attribute_cache, entidr, one.into(), TypedValue::Long(100)); - assert_value_present_for_attribute(&schema, &mut attribute_cache, entidr, two.into(), TypedValue::Long(200)); - attribute_cache.register(&schema, &mut store.sqlite_mut(), entidz).expect("No errors on add to cache"); - assert_value_present_for_attribute(&schema, &mut attribute_cache, entidz, one.into(), TypedValue::Boolean(false)); - assert_value_present_for_attribute(&schema, &mut attribute_cache, entidz, one.into(), TypedValue::Boolean(false)); - attribute_cache.register(&schema, &mut store.sqlite_mut(), entidp).expect("No errors on add to cache"); - assert_values_present_for_attribute(&schema, &mut attribute_cache, entidp, one.into(), - vec![TypedValue::typed_string("buckle my shoe"), - TypedValue::typed_string("one"), - TypedValue::typed_string("two")]); - assert_values_present_for_attribute(&schema, &mut attribute_cache, entidp, two.into(), - vec![TypedValue::typed_string("knock at my door"), - TypedValue::typed_string("three"), - TypedValue::typed_string("four")]); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), entidr) + .expect("No errors on add to cache"); + assert_value_present_for_attribute( + &schema, + &mut attribute_cache, + entidr, + one.into(), + TypedValue::Long(100), + ); + assert_value_present_for_attribute( + &schema, + &mut attribute_cache, + entidr, + two.into(), + TypedValue::Long(200), + ); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), entidz) + .expect("No errors on add to cache"); + assert_value_present_for_attribute( + &schema, + &mut attribute_cache, + entidz, + one.into(), + TypedValue::Boolean(false), + ); + assert_value_present_for_attribute( + &schema, + &mut attribute_cache, + entidz, + one.into(), + TypedValue::Boolean(false), + ); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), entidp) + .expect("No errors on add to cache"); + assert_values_present_for_attribute( + &schema, + &mut attribute_cache, + entidp, + one.into(), + vec![ + TypedValue::typed_string("buckle my shoe"), + TypedValue::typed_string("one"), + TypedValue::typed_string("two"), + ], + ); + assert_values_present_for_attribute( + &schema, + &mut attribute_cache, + entidp, + two.into(), + vec![ + TypedValue::typed_string("knock at my door"), + TypedValue::typed_string("three"), + TypedValue::typed_string("four"), + ], + ); // test that we can remove an item from cache attribute_cache.unregister(entidz); assert!(!attribute_cache.is_attribute_cached_forward(entidz.into())); - assert!(attribute_cache.get_value_for_entid(&schema, entidz, one.into()).is_none()); - assert!(attribute_cache.get_values_for_entid(&schema, entidz, one.into()).is_none()); - assert!(attribute_cache.get_value_for_entid(&schema, entidz, two.into()).is_none()); - assert!(attribute_cache.get_values_for_entid(&schema, entidz, two.into()).is_none()); + assert!(attribute_cache + .get_value_for_entid(&schema, entidz, one.into()) + .is_none()); + assert!(attribute_cache + .get_values_for_entid(&schema, entidz, one.into()) + .is_none()); + assert!(attribute_cache + .get_value_for_entid(&schema, entidz, two.into()) + .is_none()); + assert!(attribute_cache + .get_values_for_entid(&schema, entidz, two.into()) + .is_none()); } #[test] @@ -176,7 +283,10 @@ fn test_remove_attribute_not_in_cache() { let schema = store.conn().current_schema(); let kw = kw!(:foo/baz); - let entid = schema.get_entid(&kw).expect("Expected entid for attribute").0; + let entid = schema + .get_entid(&kw) + .expect("Expected entid for attribute") + .0; attribute_cache.unregister(entid); assert!(!attribute_cache.is_attribute_cached_forward(entid)); } @@ -186,19 +296,30 @@ fn test_fetch_attribute_value_for_entid() { let mut store = populate_db(); let schema = store.conn().current_schema(); - let entities = store.q_once(r#"[:find ?e . :where [?e :foo/bar 100]]"#, None).expect("Expected query to work").into_scalar().expect("expected scalar results"); + let entities = store + .q_once(r#"[:find ?e . :where [?e :foo/bar 100]]"#, None) + .expect("Expected query to work") + .into_scalar() + .expect("expected scalar results"); let entid = match entities { Some(Binding::Scalar(TypedValue::Ref(entid))) => entid, x => panic!("expected Some(Ref), got {:?}", x), }; let kwr = kw!(:foo/bar); - let attr_entid = schema.get_entid(&kwr).expect("Expected entid for attribute").0; + let attr_entid = schema + .get_entid(&kwr) + .expect("Expected entid for attribute") + .0; let mut attribute_cache = SQLiteAttributeCache::default(); - attribute_cache.register(&schema, &mut store.sqlite_mut(), attr_entid).expect("No errors on add to cache"); - let val = attribute_cache.get_value_for_entid(&schema, attr_entid, entid).expect("Expected value"); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), attr_entid) + .expect("No errors on add to cache"); + let val = attribute_cache + .get_value_for_entid(&schema, attr_entid, entid) + .expect("Expected value"); assert_eq!(*val, TypedValue::Long(100)); } @@ -207,20 +328,36 @@ fn test_fetch_attribute_values_for_entid() { let mut store = populate_db(); let schema = store.conn().current_schema(); - let entities = store.q_once(r#"[:find ?e . :where [?e :foo/bar 100]]"#, None).expect("Expected query to work").into_scalar().expect("expected scalar results"); + let entities = store + .q_once(r#"[:find ?e . :where [?e :foo/bar 100]]"#, None) + .expect("Expected query to work") + .into_scalar() + .expect("expected scalar results"); let entid = match entities { Some(Binding::Scalar(TypedValue::Ref(entid))) => entid, x => panic!("expected Some(Ref), got {:?}", x), }; let kwp = kw!(:foo/bap); - let attr_entid = schema.get_entid(&kwp).expect("Expected entid for attribute").0; + let attr_entid = schema + .get_entid(&kwp) + .expect("Expected entid for attribute") + .0; let mut attribute_cache = SQLiteAttributeCache::default(); - attribute_cache.register(&schema, &mut store.sqlite_mut(), attr_entid).expect("No errors on add to cache"); - let val = attribute_cache.get_values_for_entid(&schema, attr_entid, entid).expect("Expected value"); - assert_eq!(*val, vec![TypedValue::typed_string("buckle my shoe"), - TypedValue::typed_string("one"), - TypedValue::typed_string("two")]); + attribute_cache + .register(&schema, &mut store.sqlite_mut(), attr_entid) + .expect("No errors on add to cache"); + let val = attribute_cache + .get_values_for_entid(&schema, attr_entid, entid) + .expect("Expected value"); + assert_eq!( + *val, + vec![ + TypedValue::typed_string("buckle my shoe"), + TypedValue::typed_string("one"), + TypedValue::typed_string("two") + ] + ); } diff --git a/tests/entity_builder.rs b/tests/entity_builder.rs index 62f5202b..7d4b6d4b 100644 --- a/tests/entity_builder.rs +++ b/tests/entity_builder.rs @@ -1,182 +1,211 @@ -// Copyright 2018 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -#[macro_use] -extern crate mentat; -extern crate mentat_core; -extern crate mentat_db; -extern crate mentat_transaction; -extern crate public_traits; -extern crate core_traits; -extern crate db_traits; - -use mentat::conn::{ - Conn, -}; - -use core_traits::{ - Entid, - KnownEntid, - TypedValue, -}; - -use mentat_core::{ - HasSchema, - TxReport, -}; - -use mentat_transaction::{ - TermBuilder, - Queryable, -}; - -use public_traits::errors::{ - MentatError, -}; - -use mentat::entity_builder::{ - BuildTerms, -}; - -// In reality we expect the store to hand these out safely. -fn fake_known_entid(e: Entid) -> KnownEntid { - KnownEntid(e) -} - -#[test] -fn test_entity_builder_bogus_entids() { - let mut builder = TermBuilder::new(); - let e = builder.named_tempid("x"); - let a1 = fake_known_entid(37); // :db/doc - let a2 = fake_known_entid(999); - let v = TypedValue::typed_string("Some attribute"); - let ve = fake_known_entid(12345); - - builder.add(e.clone(), a1, v).expect("add succeeded"); - builder.add(e.clone(), a2, e.clone()).expect("add succeeded, even though it's meaningless"); - builder.add(e.clone(), a2, ve).expect("add succeeded, even though it's meaningless"); - let (terms, tempids) = builder.build().expect("build succeeded"); - - assert_eq!(tempids.len(), 1); - assert_eq!(terms.len(), 3); // TODO: check the contents? - - // Now try to add them to a real store. - let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // This should fail: unrecognized entid. - match in_progress.transact_entities(terms).expect_err("expected transact to fail") { - MentatError::DbError(e) => { - assert_eq!(e.kind(), db_traits::errors::DbErrorKind::UnrecognizedEntid(999)); - }, - _ => panic!("Should have rejected the entid."), - } -} - -#[test] -fn test_in_progress_builder() { - let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); - - // Give ourselves a schema to work with! - conn.transact(&mut sqlite, r#"[ - [:db/add "o" :db/ident :foo/one] - [:db/add "o" :db/valueType :db.type/long] - [:db/add "o" :db/cardinality :db.cardinality/one] - [:db/add "m" :db/ident :foo/many] - [:db/add "m" :db/valueType :db.type/string] - [:db/add "m" :db/cardinality :db.cardinality/many] - [:db/add "r" :db/ident :foo/ref] - [:db/add "r" :db/valueType :db.type/ref] - [:db/add "r" :db/cardinality :db.cardinality/one] - ]"#).unwrap(); - - let in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // We can use this or not! - let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many"); - - let mut builder = in_progress.builder(); - let e_x = builder.named_tempid("x"); - let v_many_1 = TypedValue::typed_string("Some text"); - let v_many_2 = TypedValue::typed_string("Other text"); - builder.add(e_x.clone(), kw!(:foo/many), v_many_1).expect("add succeeded"); - builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); - builder.commit().expect("commit succeeded"); -} - -#[test] -fn test_entity_builder() { - let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); - - let foo_one = kw!(:foo/one); - let foo_many = kw!(:foo/many); - let foo_ref = kw!(:foo/ref); - let report: TxReport; - - // Give ourselves a schema to work with! - // Scoped borrow of conn. - { - conn.transact(&mut sqlite, r#"[ - [:db/add "o" :db/ident :foo/one] - [:db/add "o" :db/valueType :db.type/long] - [:db/add "o" :db/cardinality :db.cardinality/one] - [:db/add "m" :db/ident :foo/many] - [:db/add "m" :db/valueType :db.type/string] - [:db/add "m" :db/cardinality :db.cardinality/many] - [:db/add "r" :db/ident :foo/ref] - [:db/add "r" :db/valueType :db.type/ref] - [:db/add "r" :db/cardinality :db.cardinality/one] - ]"#).unwrap(); - - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // Scoped borrow of in_progress. - { - let mut builder = TermBuilder::new(); - let e_x = builder.named_tempid("x"); - let e_y = builder.named_tempid("y"); - let a_ref = in_progress.get_entid(&foo_ref).expect(":foo/ref"); - let a_one = in_progress.get_entid(&foo_one).expect(":foo/one"); - let a_many = in_progress.get_entid(&foo_many).expect(":foo/many"); - let v_many_1 = TypedValue::typed_string("Some text"); - let v_many_2 = TypedValue::typed_string("Other text"); - let v_long: TypedValue = 123.into(); - - builder.add(e_x.clone(), a_many, v_many_1).expect("add succeeded"); - builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); - builder.add(e_y.clone(), a_ref, e_x.clone()).expect("add succeeded"); - builder.add(e_x.clone(), a_one, v_long).expect("add succeeded"); - - let (terms, tempids) = builder.build().expect("build succeeded"); - - assert_eq!(tempids.len(), 2); - assert_eq!(terms.len(), 4); - - report = in_progress.transact_entities(terms).expect("add succeeded"); - let x = report.tempids.get("x").expect("our tempid has an ID"); - let y = report.tempids.get("y").expect("our tempid has an ID"); - assert_eq!(in_progress.lookup_value_for_attribute(*y, &foo_ref).expect("lookup succeeded"), - Some(TypedValue::Ref(*x))); - assert_eq!(in_progress.lookup_value_for_attribute(*x, &foo_one).expect("lookup succeeded"), - Some(TypedValue::Long(123))); - } - - in_progress.commit().expect("commit succeeded"); - } - - // It's all still there after the commit. - let x = report.tempids.get("x").expect("our tempid has an ID"); - let y = report.tempids.get("y").expect("our tempid has an ID"); - assert_eq!(conn.lookup_value_for_attribute(&mut sqlite, *y, &foo_ref).expect("lookup succeeded"), - Some(TypedValue::Ref(*x))); -} +// Copyright 2018 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#[macro_use] +extern crate mentat; + +use db_traits; + +use mentat_db; + +use mentat::conn::Conn; + +use core_traits::{Entid, KnownEntid, TypedValue}; + +use mentat_core::{HasSchema, TxReport}; + +use mentat_transaction::{Queryable, TermBuilder}; + +use public_traits::errors::MentatError; + +use mentat::entity_builder::BuildTerms; + +// In reality we expect the store to hand these out safely. +fn fake_known_entid(e: Entid) -> KnownEntid { + KnownEntid(e) +} + +#[test] +fn test_entity_builder_bogus_entids() { + let mut builder = TermBuilder::new(); + let e = builder.named_tempid("x"); + let a1 = fake_known_entid(37); // :db/doc + let a2 = fake_known_entid(999); + let v = TypedValue::typed_string("Some attribute"); + let ve = fake_known_entid(12345); + + builder.add(e.clone(), a1, v).expect("add succeeded"); + builder + .add(e.clone(), a2, e.clone()) + .expect("add succeeded, even though it's meaningless"); + builder + .add(e.clone(), a2, ve) + .expect("add succeeded, even though it's meaningless"); + let (terms, tempids) = builder.build().expect("build succeeded"); + + assert_eq!(tempids.len(), 1); + assert_eq!(terms.len(), 3); // TODO: check the contents? + + // Now try to add them to a real store. + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let mut conn = Conn::connect(&mut sqlite).unwrap(); + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); + + // This should fail: unrecognized entid. + match in_progress + .transact_entities(terms) + .expect_err("expected transact to fail") + { + MentatError::DbError(e) => { + assert_eq!( + e.kind(), + db_traits::errors::DbErrorKind::UnrecognizedEntid(999) + ); + } + _ => panic!("Should have rejected the entid."), + } +} + +#[test] +fn test_in_progress_builder() { + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let mut conn = Conn::connect(&mut sqlite).unwrap(); + + // Give ourselves a schema to work with! + conn.transact( + &mut sqlite, + r#"[ + [:db/add "o" :db/ident :foo/one] + [:db/add "o" :db/valueType :db.type/long] + [:db/add "o" :db/cardinality :db.cardinality/one] + [:db/add "m" :db/ident :foo/many] + [:db/add "m" :db/valueType :db.type/string] + [:db/add "m" :db/cardinality :db.cardinality/many] + [:db/add "r" :db/ident :foo/ref] + [:db/add "r" :db/valueType :db.type/ref] + [:db/add "r" :db/cardinality :db.cardinality/one] + ]"#, + ) + .unwrap(); + + let in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); + + // We can use this or not! + let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many"); + + let mut builder = in_progress.builder(); + let e_x = builder.named_tempid("x"); + let v_many_1 = TypedValue::typed_string("Some text"); + let v_many_2 = TypedValue::typed_string("Other text"); + builder + .add(e_x.clone(), kw!(:foo/many), v_many_1) + .expect("add succeeded"); + builder + .add(e_x.clone(), a_many, v_many_2) + .expect("add succeeded"); + builder.commit().expect("commit succeeded"); +} + +#[test] +fn test_entity_builder() { + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let mut conn = Conn::connect(&mut sqlite).unwrap(); + + let foo_one = kw!(:foo/one); + let foo_many = kw!(:foo/many); + let foo_ref = kw!(:foo/ref); + let report: TxReport; + + // Give ourselves a schema to work with! + // Scoped borrow of conn. + { + conn.transact( + &mut sqlite, + r#"[ + [:db/add "o" :db/ident :foo/one] + [:db/add "o" :db/valueType :db.type/long] + [:db/add "o" :db/cardinality :db.cardinality/one] + [:db/add "m" :db/ident :foo/many] + [:db/add "m" :db/valueType :db.type/string] + [:db/add "m" :db/cardinality :db.cardinality/many] + [:db/add "r" :db/ident :foo/ref] + [:db/add "r" :db/valueType :db.type/ref] + [:db/add "r" :db/cardinality :db.cardinality/one] + ]"#, + ) + .unwrap(); + + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); + + // Scoped borrow of in_progress. + { + let mut builder = TermBuilder::new(); + let e_x = builder.named_tempid("x"); + let e_y = builder.named_tempid("y"); + let a_ref = in_progress.get_entid(&foo_ref).expect(":foo/ref"); + let a_one = in_progress.get_entid(&foo_one).expect(":foo/one"); + let a_many = in_progress.get_entid(&foo_many).expect(":foo/many"); + let v_many_1 = TypedValue::typed_string("Some text"); + let v_many_2 = TypedValue::typed_string("Other text"); + let v_long: TypedValue = 123.into(); + + builder + .add(e_x.clone(), a_many, v_many_1) + .expect("add succeeded"); + builder + .add(e_x.clone(), a_many, v_many_2) + .expect("add succeeded"); + builder + .add(e_y.clone(), a_ref, e_x.clone()) + .expect("add succeeded"); + builder + .add(e_x.clone(), a_one, v_long) + .expect("add succeeded"); + + let (terms, tempids) = builder.build().expect("build succeeded"); + + assert_eq!(tempids.len(), 2); + assert_eq!(terms.len(), 4); + + report = in_progress.transact_entities(terms).expect("add succeeded"); + let x = report.tempids.get("x").expect("our tempid has an ID"); + let y = report.tempids.get("y").expect("our tempid has an ID"); + assert_eq!( + in_progress + .lookup_value_for_attribute(*y, &foo_ref) + .expect("lookup succeeded"), + Some(TypedValue::Ref(*x)) + ); + assert_eq!( + in_progress + .lookup_value_for_attribute(*x, &foo_one) + .expect("lookup succeeded"), + Some(TypedValue::Long(123)) + ); + } + + in_progress.commit().expect("commit succeeded"); + } + + // It's all still there after the commit. + let x = report.tempids.get("x").expect("our tempid has an ID"); + let y = report.tempids.get("y").expect("our tempid has an ID"); + assert_eq!( + conn.lookup_value_for_attribute(&mut sqlite, *y, &foo_ref) + .expect("lookup succeeded"), + Some(TypedValue::Ref(*x)) + ); +} diff --git a/tests/external_test.rs b/tests/external_test.rs index 38e30ba1..96f183d4 100644 --- a/tests/external_test.rs +++ b/tests/external_test.rs @@ -8,7 +8,7 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate mentat; +use mentat; #[test] fn can_import_sqlite() { @@ -22,25 +22,30 @@ fn can_import_sqlite() { let conn = mentat::new_connection("").expect("SQLite connected"); - conn.execute("CREATE TABLE person ( + conn.execute( + "CREATE TABLE person ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, data BLOB )", - rusqlite::params![]) - .unwrap(); + rusqlite::params![], + ) + .unwrap(); let me = Person { id: 1, name: "Steven".to_string(), data: None, }; - conn.execute("INSERT INTO person (name, data) + conn.execute( + "INSERT INTO person (name, data) VALUES (?1, ?2)", - rusqlite::params![&me.name, &me.data]) - .unwrap(); + rusqlite::params![&me.name, &me.data], + ) + .unwrap(); let mut stmt = conn.prepare("SELECT id, name, data FROM person").unwrap(); - let person_iter = stmt.query_map(rusqlite::params![], |row| { + let person_iter = stmt + .query_map(rusqlite::params![], |row| { Ok(Person { id: row.get(0).unwrap(), name: row.get(1).unwrap(), diff --git a/tests/pull.rs b/tests/pull.rs index dac53cd1..8e50dc29 100644 --- a/tests/pull.rs +++ b/tests/pull.rs @@ -10,40 +10,18 @@ #[macro_use] extern crate mentat; -extern crate mentat_core; -extern crate core_traits; -extern crate mentat_query_pull; -use std::collections::{ - BTreeMap, - BTreeSet, -}; +use std::collections::{BTreeMap, BTreeSet}; -use std::path::{ - Path, - PathBuf, -}; +use std::path::{Path, PathBuf}; -use core_traits::{ - Binding, - StructuredMap, -}; +use core_traits::{Binding, StructuredMap}; // TODO: re-export from Mentat. -use mentat_core::{ - ValueRc, -}; +use mentat_core::ValueRc; use mentat::{ - Entid, - HasSchema, - IntoResult, - Keyword, - Pullable, - Queryable, - QueryInputs, - Store, - RelResult, + Entid, HasSchema, IntoResult, Keyword, Pullable, QueryInputs, Queryable, RelResult, Store, TypedValue, }; @@ -62,21 +40,40 @@ fn test_simple_pull() { let mut store = Store::open("").expect("opened"); { let mut in_progress = store.begin_transaction().expect("began"); - in_progress.import(fixture_path("cities.schema")).expect("transacted schema"); - let report = in_progress.import(fixture_path("all_seattle.edn")).expect("transacted data"); + in_progress + .import(fixture_path("cities.schema")) + .expect("transacted schema"); + let report = in_progress + .import(fixture_path("all_seattle.edn")) + .expect("transacted data"); // These tempid strings come out of all_seattle.edn. - beacon = *report.tempids.get(&"a17592186045471".to_string()).expect("beacon"); - beacon_district = *report.tempids.get(&"a17592186045450".to_string()).expect("beacon district"); + beacon = *report + .tempids + .get(&"a17592186045471".to_string()) + .expect("beacon"); + beacon_district = *report + .tempids + .get(&"a17592186045450".to_string()) + .expect("beacon district"); - capitol = *report.tempids.get(&"a17592186045439".to_string()).expect("capitol"); - capitol_district = *report.tempids.get(&"a17592186045438".to_string()).expect("capitol district"); + capitol = *report + .tempids + .get(&"a17592186045439".to_string()) + .expect("capitol"); + capitol_district = *report + .tempids + .get(&"a17592186045438".to_string()) + .expect("capitol district"); in_progress.commit().expect("committed"); } let schema = store.conn().current_schema(); - let district = schema.get_entid(&kw!(:neighborhood/district)).expect("district").0; + let district = schema + .get_entid(&kw!(:neighborhood/district)) + .expect("district") + .0; let name = schema.get_entid(&kw!(:neighborhood/name)).expect("name").0; let attrs: BTreeSet = vec![district, name].into_iter().collect(); @@ -85,14 +82,17 @@ fn test_simple_pull() { :where (or [?hood :neighborhood/name "Beacon Hill"] [?hood :neighborhood/name "Capitol Hill"])]"#; - let hoods: BTreeSet = store.q_once(query, None) - .into_coll_result() - .expect("hoods") - .into_iter() - .map(|b| { - b.into_scalar().and_then(|tv| tv.into_entid()).expect("scalar") - }) - .collect(); + let hoods: BTreeSet = store + .q_once(query, None) + .into_coll_result() + .expect("hoods") + .into_iter() + .map(|b| { + b.into_scalar() + .and_then(|tv| tv.into_entid()) + .expect("scalar") + }) + .collect(); println!("Hoods: {:?}", hoods); @@ -100,22 +100,34 @@ fn test_simple_pull() { assert_eq!(hoods, vec![beacon, capitol].into_iter().collect()); // Pull attributes of those two neighborhoods. - let pulled = store.begin_read().expect("read") - .pull_attributes_for_entities(hoods, attrs) - .expect("pulled"); + let pulled = store + .begin_read() + .expect("read") + .pull_attributes_for_entities(hoods, attrs) + .expect("pulled"); // Here's what we expect: let c: StructuredMap = vec![ (kw!(:neighborhood/name), "Capitol Hill".into()), - (kw!(:neighborhood/district), TypedValue::Ref(capitol_district)), - ].into(); + ( + kw!(:neighborhood/district), + TypedValue::Ref(capitol_district), + ), + ] + .into(); let b: StructuredMap = vec![ (kw!(:neighborhood/name), "Beacon Hill".into()), - (kw!(:neighborhood/district), TypedValue::Ref(beacon_district)), - ].into(); + ( + kw!(:neighborhood/district), + TypedValue::Ref(beacon_district), + ), + ] + .into(); let expected: BTreeMap>; - expected = vec![(capitol, c.into()), (beacon, b.into())].into_iter().collect(); + expected = vec![(capitol, c.into()), (beacon, b.into())] + .into_iter() + .collect(); assert_eq!(pulled, expected); @@ -128,41 +140,58 @@ fn test_simple_pull() { [?hood :neighborhood/name "Capitol Hill"]) [?hood :neighborhood/district ?district] :order ?hood]"#; - let results: RelResult = store.begin_read().expect("read") - .q_once(query, None) - .into_rel_result() - .expect("results"); + let results: RelResult = store + .begin_read() + .expect("read") + .q_once(query, None) + .into_rel_result() + .expect("results"); let beacon_district_pull: Vec<(Keyword, TypedValue)> = vec![ (kw!(:db/id), TypedValue::Ref(beacon_district)), (kw!(:district/district), "Greater Duwamish".into()), - (kw!(:district/region), schema.get_entid(&Keyword::namespaced("region", "se")).unwrap().into()), + ( + kw!(:district/region), + schema + .get_entid(&Keyword::namespaced("region", "se")) + .unwrap() + .into(), + ), ]; let beacon_district_pull: StructuredMap = beacon_district_pull.into(); let capitol_district_pull: Vec<(Keyword, TypedValue)> = vec![ (kw!(:db/id), TypedValue::Ref(capitol_district)), (kw!(:district/district), "East".into()), - (kw!(:district/region), schema.get_entid(&Keyword::namespaced("region", "e")).unwrap().into()), + ( + kw!(:district/region), + schema + .get_entid(&Keyword::namespaced("region", "e")) + .unwrap() + .into(), + ), ]; let capitol_district_pull: StructuredMap = capitol_district_pull.into(); let expected = RelResult { - width: 2, - values: vec![ - TypedValue::Ref(capitol).into(), capitol_district_pull.into(), - TypedValue::Ref(beacon).into(), beacon_district_pull.into(), - ].into(), - }; + width: 2, + values: vec![ + TypedValue::Ref(capitol).into(), + capitol_district_pull.into(), + TypedValue::Ref(beacon).into(), + beacon_district_pull.into(), + ] + .into(), + }; assert_eq!(results, expected.clone()); // We can also prepare the query. let reader = store.begin_read().expect("read"); let mut prepared = reader.q_prepare(query, None).expect("prepared"); - assert_eq!(prepared.run(None) - .into_rel_result() - .expect("results"), - expected); + assert_eq!( + prepared.run(None).into_rel_result().expect("results"), + expected + ); // Execute a scalar query where the body is constant. // TODO: we shouldn't require `:where`; that makes this non-constant! @@ -170,52 +199,97 @@ fn test_simple_pull() { :neighborhood/name]) . :in ?hood :where [?hood :neighborhood/district _]]"#; - let result = reader.q_once(query, QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::Ref(beacon))])) - .into_scalar_result() - .expect("success") - .expect("result"); + let result = reader + .q_once( + query, + QueryInputs::with_value_sequence(vec![(var!(?hood), TypedValue::Ref(beacon))]), + ) + .into_scalar_result() + .expect("success") + .expect("result"); let expected: StructuredMap = vec![ (kw!(:neighborhood/name), TypedValue::from("Beacon Hill")), (kw!(:neighborhood), TypedValue::Ref(beacon)), - ].into(); + ] + .into(); assert_eq!(result, expected.into()); // Collect the names and regions of all districts. let query = r#"[:find [(pull ?district [:district/name :district/region]) ...] :where [_ :neighborhood/district ?district]]"#; - let results = reader.q_once(query, None) - .into_coll_result() - .expect("result"); + let results = reader + .q_once(query, None) + .into_coll_result() + .expect("result"); let expected: Vec = vec![ - vec![(kw!(:district/name), TypedValue::from("East")), - (kw!(:district/region), TypedValue::Ref(65556))].into(), - vec![(kw!(:district/name), TypedValue::from("Southwest")), - (kw!(:district/region), TypedValue::Ref(65559))].into(), - vec![(kw!(:district/name), TypedValue::from("Downtown")), - (kw!(:district/region), TypedValue::Ref(65560))].into(), - vec![(kw!(:district/name), TypedValue::from("Greater Duwamish")), - (kw!(:district/region), TypedValue::Ref(65557))].into(), - vec![(kw!(:district/name), TypedValue::from("Ballard")), - (kw!(:district/region), TypedValue::Ref(65561))].into(), - vec![(kw!(:district/name), TypedValue::from("Northeast")), - (kw!(:district/region), TypedValue::Ref(65555))].into(), - vec![(kw!(:district/name), TypedValue::from("Southeast")), - (kw!(:district/region), TypedValue::Ref(65557))].into(), - vec![(kw!(:district/name), TypedValue::from("Northwest")), - (kw!(:district/region), TypedValue::Ref(65561))].into(), - vec![(kw!(:district/name), TypedValue::from("Central")), - (kw!(:district/region), TypedValue::Ref(65556))].into(), - vec![(kw!(:district/name), TypedValue::from("Delridge")), - (kw!(:district/region), TypedValue::Ref(65559))].into(), - vec![(kw!(:district/name), TypedValue::from("Lake Union")), - (kw!(:district/region), TypedValue::Ref(65560))].into(), - vec![(kw!(:district/name), TypedValue::from("Magnolia/Queen Anne")), - (kw!(:district/region), TypedValue::Ref(65560))].into(), - vec![(kw!(:district/name), TypedValue::from("North")), - (kw!(:district/region), TypedValue::Ref(65555))].into(), - ]; + vec![ + (kw!(:district/name), TypedValue::from("East")), + (kw!(:district/region), TypedValue::Ref(65556)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Southwest")), + (kw!(:district/region), TypedValue::Ref(65559)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Downtown")), + (kw!(:district/region), TypedValue::Ref(65560)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Greater Duwamish")), + (kw!(:district/region), TypedValue::Ref(65557)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Ballard")), + (kw!(:district/region), TypedValue::Ref(65561)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Northeast")), + (kw!(:district/region), TypedValue::Ref(65555)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Southeast")), + (kw!(:district/region), TypedValue::Ref(65557)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Northwest")), + (kw!(:district/region), TypedValue::Ref(65561)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Central")), + (kw!(:district/region), TypedValue::Ref(65556)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Delridge")), + (kw!(:district/region), TypedValue::Ref(65559)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Lake Union")), + (kw!(:district/region), TypedValue::Ref(65560)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("Magnolia/Queen Anne")), + (kw!(:district/region), TypedValue::Ref(65560)), + ] + .into(), + vec![ + (kw!(:district/name), TypedValue::from("North")), + (kw!(:district/region), TypedValue::Ref(65555)), + ] + .into(), + ]; let expected: Vec = expected.into_iter().map(|m| m.into()).collect(); assert_eq!(results, expected); @@ -226,25 +300,65 @@ fn test_simple_pull() { [?c :community/name ?name] [?c :community/type :community.type/website] [(fulltext $ :community/category "food") [[?c ?cat]]]]"#; - let results = reader.q_once(query, None) - .into_coll_result() - .expect("result"); + let results = reader + .q_once(query, None) + .into_coll_result() + .expect("result"); let expected: Vec = vec![ - vec![(kw!(:community/name), Binding::Scalar(TypedValue::from("Community Harvest of Southwest Seattle"))), - (kw!(:community/category), vec![Binding::Scalar(TypedValue::from("sustainable food"))].into())].into(), - - vec![(kw!(:community/name), Binding::Scalar(TypedValue::from("InBallard"))), - (kw!(:community/category), vec![Binding::Scalar(TypedValue::from("shopping")), - Binding::Scalar(TypedValue::from("food")), - Binding::Scalar(TypedValue::from("nightlife")), - Binding::Scalar(TypedValue::from("services"))].into())].into(), - - vec![(kw!(:community/name), Binding::Scalar(TypedValue::from("Seattle Chinatown Guide"))), - (kw!(:community/category), vec![Binding::Scalar(TypedValue::from("shopping")), - Binding::Scalar(TypedValue::from("food"))].into())].into(), - - vec![(kw!(:community/name), Binding::Scalar(TypedValue::from("University District Food Bank"))), - (kw!(:community/category), vec![Binding::Scalar(TypedValue::from("food bank"))].into())].into(), + vec![ + ( + kw!(:community/name), + Binding::Scalar(TypedValue::from("Community Harvest of Southwest Seattle")), + ), + ( + kw!(:community/category), + vec![Binding::Scalar(TypedValue::from("sustainable food"))].into(), + ), + ] + .into(), + vec![ + ( + kw!(:community/name), + Binding::Scalar(TypedValue::from("InBallard")), + ), + ( + kw!(:community/category), + vec![ + Binding::Scalar(TypedValue::from("shopping")), + Binding::Scalar(TypedValue::from("food")), + Binding::Scalar(TypedValue::from("nightlife")), + Binding::Scalar(TypedValue::from("services")), + ] + .into(), + ), + ] + .into(), + vec![ + ( + kw!(:community/name), + Binding::Scalar(TypedValue::from("Seattle Chinatown Guide")), + ), + ( + kw!(:community/category), + vec![ + Binding::Scalar(TypedValue::from("shopping")), + Binding::Scalar(TypedValue::from("food")), + ] + .into(), + ), + ] + .into(), + vec![ + ( + kw!(:community/name), + Binding::Scalar(TypedValue::from("University District Food Bank")), + ), + ( + kw!(:community/category), + vec![Binding::Scalar(TypedValue::from("food bank"))].into(), + ), + ] + .into(), ]; let expected: Vec = expected.into_iter().map(|m| m.into()).collect(); diff --git a/tests/query.rs b/tests/query.rs index 819835ad..46e3e7c3 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -8,67 +8,36 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate chrono; -extern crate time; +use time; #[macro_use] extern crate mentat; -extern crate mentat_core; -extern crate core_traits; -extern crate public_traits; -extern crate mentat_db; -extern crate mentat_transaction; +use mentat_db; // TODO: when we switch to `failure`, make this more humane. -extern crate query_algebrizer_traits; // For errors; -extern crate query_projector_traits; // For errors. +use query_algebrizer_traits; // For errors; use std::str::FromStr; use chrono::FixedOffset; -use core_traits::{ - Entid, - KnownEntid, - ValueType, - ValueTypeSet, -}; +use core_traits::{Entid, KnownEntid, ValueType, ValueTypeSet}; -use mentat_core::{ - DateTime, - HasSchema, - Utc, - Uuid, -}; +use mentat_core::{DateTime, HasSchema, Utc, Uuid}; -use query_projector_traits::aggregates::{ - SimpleAggregationOp, -}; +use query_projector_traits::aggregates::SimpleAggregationOp; use mentat::{ - IntoResult, - Keyword, - PlainSymbol, - QueryInputs, - Queryable, - QueryResults, - RelResult, - Store, - Binding, - TxReport, - TypedValue, - Variable, - new_connection, + new_connection, Binding, IntoResult, Keyword, PlainSymbol, QueryInputs, QueryResults, + Queryable, RelResult, Store, TxReport, TypedValue, Variable, }; use mentat::query::q_uncached; use mentat::conn::Conn; -use public_traits::errors::{ - MentatError, -}; +use public_traits::errors::MentatError; #[test] fn test_rel() { @@ -76,12 +45,16 @@ fn test_rel() { let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB."); // Rel. - let start = time::PreciseTime::now(); - let results = q_uncached(&c, &db.schema, - "[:find ?x ?ident :where [?x :db/ident ?ident]]", None) - .expect("Query failed") - .results; - let end = time::PreciseTime::now(); + let start = time::Instant::now(); + let results = q_uncached( + &c, + &db.schema, + "[:find ?x ?ident :where [?x :db/ident ?ident]]", + None, + ) + .expect("Query failed") + .results; + let end = time::Instant::now(); // This will need to change each time we add a default ident. assert_eq!(40, results.len()); @@ -97,7 +70,7 @@ fn test_rel() { panic!("Expected rel."); } - println!("Rel took {}µs", start.to(end).num_microseconds().unwrap()); + println!("Rel took {}µs", (end - start).whole_microseconds()); } #[test] @@ -106,12 +79,16 @@ fn test_failing_scalar() { let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB."); // Scalar that fails. - let start = time::PreciseTime::now(); - let results = q_uncached(&c, &db.schema, - "[:find ?x . :where [?x :db/fulltext true]]", None) - .expect("Query failed") - .results; - let end = time::PreciseTime::now(); + let start = time::Instant::now(); + let results = q_uncached( + &c, + &db.schema, + "[:find ?x . :where [?x :db/fulltext true]]", + None, + ) + .expect("Query failed") + .results; + let end = time::Instant::now(); assert_eq!(0, results.len()); @@ -120,7 +97,10 @@ fn test_failing_scalar() { panic!("Expected failed scalar."); } - println!("Failing scalar took {}µs", start.to(end).num_microseconds().unwrap()); + println!( + "Failing scalar took {}µs", + (end - start).whole_microseconds() + ); } #[test] @@ -129,26 +109,29 @@ fn test_scalar() { let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB."); // Scalar that succeeds. - let start = time::PreciseTime::now(); - let results = q_uncached(&c, &db.schema, - "[:find ?ident . :where [24 :db/ident ?ident]]", None) - .expect("Query failed") - .results; - let end = time::PreciseTime::now(); + let start = time::Instant::now(); + let results = q_uncached( + &c, + &db.schema, + "[:find ?ident . :where [24 :db/ident ?ident]]", + None, + ) + .expect("Query failed") + .results; + let end = time::Instant::now(); assert_eq!(1, results.len()); if let QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Keyword(ref rc)))) = results { // Should be '24'. assert_eq!(&Keyword::namespaced("db.type", "keyword"), rc.as_ref()); - assert_eq!(KnownEntid(24), - db.schema.get_entid(rc).unwrap()); + assert_eq!(KnownEntid(24), db.schema.get_entid(rc).unwrap()); } else { panic!("Expected scalar."); } println!("{:?}", results); - println!("Scalar took {}µs", start.to(end).num_microseconds().unwrap()); + println!("Scalar took {}µs", (end - start).whole_microseconds()); } #[test] @@ -157,15 +140,18 @@ fn test_tuple() { let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB."); // Tuple. - let start = time::PreciseTime::now(); - let results = q_uncached(&c, &db.schema, - "[:find [?index ?cardinality] + let start = time::Instant::now(); + let results = q_uncached( + &c, + &db.schema, + "[:find [?index ?cardinality] :where [:db/txInstant :db/index ?index] [:db/txInstant :db/cardinality ?cardinality]]", - None) - .expect("Query failed") - .results; - let end = time::PreciseTime::now(); + None, + ) + .expect("Query failed") + .results; + let end = time::Instant::now(); assert_eq!(1, results.len()); @@ -173,13 +159,16 @@ fn test_tuple() { let cardinality_one = Keyword::namespaced("db.cardinality", "one"); assert_eq!(tuple.len(), 2); assert_eq!(tuple[0], TypedValue::Boolean(true).into()); - assert_eq!(tuple[1], db.schema.get_entid(&cardinality_one).expect("c1").into()); + assert_eq!( + tuple[1], + db.schema.get_entid(&cardinality_one).expect("c1").into() + ); } else { panic!("Expected tuple."); } println!("{:?}", results); - println!("Tuple took {}µs", start.to(end).num_microseconds().unwrap()); + println!("Tuple took {}µs", (end - start).whole_microseconds()); } #[test] @@ -188,12 +177,16 @@ fn test_coll() { let db = mentat_db::db::ensure_current_version(&mut c).expect("Couldn't open DB."); // Coll. - let start = time::PreciseTime::now(); - let results = q_uncached(&c, &db.schema, - "[:find [?e ...] :where [?e :db/ident _]]", None) - .expect("Query failed") - .results; - let end = time::PreciseTime::now(); + let start = time::Instant::now(); + let results = q_uncached( + &c, + &db.schema, + "[:find [?e ...] :where [?e :db/ident _]]", + None, + ) + .expect("Query failed") + .results; + let end = time::Instant::now(); assert_eq!(40, results.len()); @@ -204,7 +197,7 @@ fn test_coll() { } println!("{:?}", results); - println!("Coll took {}µs", start.to(end).num_microseconds().unwrap()); + println!("Coll took {}µs", (end - start).whole_microseconds()); } #[test] @@ -215,13 +208,20 @@ fn test_inputs() { // entids::DB_INSTALL_VALUE_TYPE = 5. let ee = (Variable::from_valid_name("?e"), TypedValue::Ref(5)); let inputs = QueryInputs::with_value_sequence(vec![ee]); - let results = q_uncached(&c, &db.schema, - "[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs) - .expect("query to succeed") - .results; + let results = q_uncached( + &c, + &db.schema, + "[:find ?i . :in ?e :where [?e :db/ident ?i]]", + inputs, + ) + .expect("query to succeed") + .results; if let QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Keyword(value)))) = results { - assert_eq!(value.as_ref(), &Keyword::namespaced("db.install", "valueType")); + assert_eq!( + value.as_ref(), + &Keyword::namespaced("db.install", "valueType") + ); } else { panic!("Expected scalar."); } @@ -236,13 +236,17 @@ fn test_unbound_inputs() { // Bind the wrong var by 'mistake'. let xx = (Variable::from_valid_name("?x"), TypedValue::Ref(5)); let inputs = QueryInputs::with_value_sequence(vec![xx]); - let results = q_uncached(&c, &db.schema, - "[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs); + let results = q_uncached( + &c, + &db.schema, + "[:find ?i . :in ?e :where [?e :db/ident ?i]]", + inputs, + ); match results.expect_err("expected unbound variables") { MentatError::UnboundVariables(vars) => { assert_eq!(vars, vec!["?e".to_string()].into_iter().collect()); - }, + } _ => panic!("Expected UnboundVariables variant."), } } @@ -255,35 +259,52 @@ fn test_instants_and_uuids() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "s" :db/ident :foo/uuid] [:db/add "s" :db/valueType :db.type/uuid] [:db/add "s" :db/cardinality :db.cardinality/one] - ]"#).unwrap(); - conn.transact(&mut c, r#"[ + ]"#, + ) + .unwrap(); + conn.transact( + &mut c, + r#"[ [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] - ]"#).unwrap(); - let r = conn.q_once(&mut c, - r#"[:find [?x ?u ?when] + ]"#, + ) + .unwrap(); + let r = conn + .q_once( + &mut c, + r#"[:find [?x ?u ?when] :where [?x :foo/uuid ?u ?tx] - [?tx :db/txInstant ?when]]"#, None) - .expect("results") - .into(); + [?tx :db/txInstant ?when]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Tuple(Some(vals)) => { let mut vals = vals.into_iter(); match (vals.next(), vals.next(), vals.next(), vals.next()) { - (Some(Binding::Scalar(TypedValue::Ref(e))), - Some(Binding::Scalar(TypedValue::Uuid(u))), - Some(Binding::Scalar(TypedValue::Instant(t))), - None) => { - assert!(e > 40); // There are at least this many entities in the store. - assert_eq!(Ok(u), Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4")); - assert!(t > start); - }, - _ => panic!("Unexpected results."), + ( + Some(Binding::Scalar(TypedValue::Ref(e))), + Some(Binding::Scalar(TypedValue::Uuid(u))), + Some(Binding::Scalar(TypedValue::Instant(t))), + None, + ) => { + assert!(e > 40); // There are at least this many entities in the store. + assert_eq!( + Ok(u), + Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4") + ); + assert!(t > start); + } + _ => panic!("Unexpected results."), } - }, + } _ => panic!("Expected query to work."), } } @@ -292,19 +313,32 @@ fn test_instants_and_uuids() { fn test_tx() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "s" :db/ident :foo/uuid] [:db/add "s" :db/valueType :db.type/uuid] [:db/add "s" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - let t = conn.transact(&mut c, r#"[ + let t = conn + .transact( + &mut c, + r#"[ [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "u" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let r = conn.q_once(&mut c, r#"[:find ?tx @@ -313,10 +347,8 @@ fn test_tx() { .into(); match r { QueryResults::Rel(ref v) => { - assert_eq!(*v, vec![ - vec![TypedValue::Ref(t.tx_id),] - ].into()); - }, + assert_eq!(*v, vec![vec![TypedValue::Ref(t.tx_id),]].into()); + } _ => panic!("Expected query to work."), } } @@ -325,35 +357,60 @@ fn test_tx() { fn test_tx_as_input() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "s" :db/ident :foo/uuid] [:db/add "s" :db/valueType :db.type/uuid] [:db/add "s" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction"); - conn.transact(&mut c, r#"[ + ]"#, + ) + .expect("successful transaction"); + conn.transact( + &mut c, + r#"[ [:db/add "u" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"] - ]"#).expect("successful transaction"); - let t = conn.transact(&mut c, r#"[ + ]"#, + ) + .expect("successful transaction"); + let t = conn + .transact( + &mut c, + r#"[ [:db/add "u" :foo/uuid #uuid "cf62d552-6569-4d1b-b667-04703041dfc4"] - ]"#).expect("successful transaction"); - conn.transact(&mut c, r#"[ + ]"#, + ) + .expect("successful transaction"); + conn.transact( + &mut c, + r#"[ [:db/add "u" :foo/uuid #uuid "267bab92-ee39-4ca2-b7f0-1163a85af1fb"] - ]"#).expect("successful transaction"); + ]"#, + ) + .expect("successful transaction"); let tx = (Variable::from_valid_name("?tx"), TypedValue::Ref(t.tx_id)); let inputs = QueryInputs::with_value_sequence(vec![tx]); - let r = conn.q_once(&mut c, - r#"[:find ?uuid + let r = conn + .q_once( + &mut c, + r#"[:find ?uuid :in ?tx - :where [?x :foo/uuid ?uuid ?tx]]"#, inputs) - .expect("results") - .into(); + :where [?x :foo/uuid ?uuid ?tx]]"#, + inputs, + ) + .expect("results") + .into(); match r { QueryResults::Rel(ref v) => { - assert_eq!(*v, vec![ - vec![TypedValue::Uuid(Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4").expect("Valid UUID")),] - ].into()); - }, + assert_eq!( + *v, + vec![vec![TypedValue::Uuid( + Uuid::from_str("cf62d552-6569-4d1b-b667-04703041dfc4").expect("Valid UUID") + ),]] + .into() + ); + } _ => panic!("Expected query to work."), } } @@ -363,7 +420,9 @@ fn test_fulltext() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "a" :db/ident :foo/term] [:db/add "a" :db/valueType :db.type/string] [:db/add "a" :db/fulltext false] @@ -374,41 +433,60 @@ fn test_fulltext() { [:db/add "s" :db/fulltext true] [:db/add "s" :db/index true] [:db/add "s" :db/cardinality :db.cardinality/many] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - let v = conn.transact(&mut c, r#"[ + let v = conn + .transact( + &mut c, + r#"[ [:db/add "v" :foo/fts "hello darkness my old friend"] [:db/add "v" :foo/fts "I've come to talk with you again"] - ]"#).unwrap().tempids.get("v").cloned().expect("v was mapped"); + ]"#, + ) + .unwrap() + .tempids + .get("v") + .cloned() + .expect("v was mapped"); - let r = conn.q_once(&mut c, - r#"[:find [?x ?val ?score] - :where [(fulltext $ :foo/fts "darkness") [[?x ?val _ ?score]]]]"#, None) - .expect("results") - .into(); + let r = conn + .q_once( + &mut c, + r#"[:find [?x ?val ?score] + :where [(fulltext $ :foo/fts "darkness") [[?x ?val _ ?score]]]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Tuple(Some(vals)) => { let mut vals = vals.into_iter(); match (vals.next(), vals.next(), vals.next(), vals.next()) { - (Some(Binding::Scalar(TypedValue::Ref(x))), - Some(Binding::Scalar(TypedValue::String(text))), - Some(Binding::Scalar(TypedValue::Double(score))), - None) => { - assert_eq!(x, v); - assert_eq!(text.as_str(), "hello darkness my old friend"); - assert_eq!(score, 0.0f64.into()); - }, - _ => panic!("Unexpected results."), + ( + Some(Binding::Scalar(TypedValue::Ref(x))), + Some(Binding::Scalar(TypedValue::String(text))), + Some(Binding::Scalar(TypedValue::Double(score))), + None, + ) => { + assert_eq!(x, v); + assert_eq!(text.as_str(), "hello darkness my old friend"); + assert_eq!(score, 0.0f64.into()); + } + _ => panic!("Unexpected results."), } - }, + } r => panic!("Unexpected results {:?}.", r), } - let a = conn.transact(&mut c, r#"[[:db/add "a" :foo/term "talk"]]"#) - .unwrap() - .tempids - .get("a").cloned() - .expect("a was mapped"); + let a = conn + .transact(&mut c, r#"[[:db/add "a" :foo/term "talk"]]"#) + .unwrap() + .tempids + .get("a") + .cloned() + .expect("a was mapped"); // If you use a non-constant search term, it must be bound earlier in the query. let query = r#"[:find ?x ?val @@ -418,11 +496,17 @@ fn test_fulltext() { ]"#; let r = conn.q_once(&mut c, query, None); match r.expect_err("expected query to fail") { - MentatError::AlgebrizerError(query_algebrizer_traits::errors::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { + MentatError::AlgebrizerError( + query_algebrizer_traits::errors::AlgebrizerError::InvalidArgument( + PlainSymbol(s), + ty, + i, + ), + ) => { assert_eq!(s, "fulltext"); assert_eq!(ty, "string"); assert_eq!(i, 2); - }, + } _ => panic!("Expected query to fail."), } @@ -433,11 +517,17 @@ fn test_fulltext() { [(fulltext $ :foo/fts ?a) [[?x ?val]]]]"#; let r = conn.q_once(&mut c, query, None); match r.expect_err("expected query to fail") { - MentatError::AlgebrizerError(query_algebrizer_traits::errors::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { + MentatError::AlgebrizerError( + query_algebrizer_traits::errors::AlgebrizerError::InvalidArgument( + PlainSymbol(s), + ty, + i, + ), + ) => { assert_eq!(s, "fulltext"); assert_eq!(ty, "string"); assert_eq!(i, 2); - }, + } _ => panic!("Expected query to fail."), } @@ -447,19 +537,22 @@ fn test_fulltext() { :where [?a :foo/term ?term] [(fulltext $ :foo/fts ?term) [[?x ?val]]]]"#; - let inputs = QueryInputs::with_value_sequence(vec![(Variable::from_valid_name("?a"), TypedValue::Ref(a))]); - let r = conn.q_once(&mut c, query, inputs) - .expect("results") - .into(); + let inputs = QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?a"), + TypedValue::Ref(a), + )]); + let r = conn.q_once(&mut c, query, inputs).expect("results").into(); match r { QueryResults::Rel(rels) => { let values: Vec> = rels.into_iter().collect(); - assert_eq!(values, vec![ - vec![Binding::Scalar(TypedValue::Ref(v)), - "I've come to talk with you again".into(), - ] - ]); - }, + assert_eq!( + values, + vec![vec![ + Binding::Scalar(TypedValue::Ref(v)), + "I've come to talk with you again".into(), + ]] + ); + } _ => panic!("Expected query to work."), } } @@ -469,33 +562,51 @@ fn test_instant_range_query() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "a" :db/ident :foo/date] [:db/add "a" :db/valueType :db.type/instant] [:db/add "a" :db/cardinality :db.cardinality/one] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - let ids = conn.transact(&mut c, r#"[ + let ids = conn + .transact( + &mut c, + r#"[ [:db/add "b" :foo/date #inst "2016-01-01T11:00:00.000Z"] [:db/add "c" :foo/date #inst "2016-06-01T11:00:01.000Z"] [:db/add "d" :foo/date #inst "2017-01-01T11:00:02.000Z"] [:db/add "e" :foo/date #inst "2017-06-01T11:00:03.000Z"] - ]"#).unwrap().tempids; + ]"#, + ) + .unwrap() + .tempids; - let r = conn.q_once(&mut c, - r#"[:find [?x ...] + let r = conn + .q_once( + &mut c, + r#"[:find [?x ...] :order (asc ?date) :where [?x :foo/date ?date] - [(< ?date #inst "2017-01-01T11:00:02.000Z")]]"#, None) - .expect("results") - .into(); + [(< ?date #inst "2017-01-01T11:00:02.000Z")]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Coll(vals) => { - assert_eq!(vals, - vec![Binding::Scalar(TypedValue::Ref(*ids.get("b").unwrap())), - Binding::Scalar(TypedValue::Ref(*ids.get("c").unwrap()))]); - }, + assert_eq!( + vals, + vec![ + Binding::Scalar(TypedValue::Ref(*ids.get("b").unwrap())), + Binding::Scalar(TypedValue::Ref(*ids.get("c").unwrap())) + ] + ); + } _ => panic!("Expected query to work."), } } @@ -505,49 +616,76 @@ fn test_lookup() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ [:db/add "a" :db/ident :foo/date] [:db/add "a" :db/valueType :db.type/instant] [:db/add "a" :db/cardinality :db.cardinality/one] [:db/add "b" :db/ident :foo/many] [:db/add "b" :db/valueType :db.type/long] [:db/add "b" :db/cardinality :db.cardinality/many] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - let ids = conn.transact(&mut c, r#"[ + let ids = conn + .transact( + &mut c, + r#"[ [:db/add "b" :foo/many 123] [:db/add "b" :foo/many 456] [:db/add "b" :foo/date #inst "2016-01-01T11:00:00.000Z"] [:db/add "c" :foo/date #inst "2016-06-01T11:00:01.000Z"] [:db/add "d" :foo/date #inst "2017-01-01T11:00:02.000Z"] [:db/add "e" :foo/date #inst "2017-06-01T11:00:03.000Z"] - ]"#).unwrap().tempids; + ]"#, + ) + .unwrap() + .tempids; let entid = ids.get("b").unwrap(); let foo_date = kw!(:foo/date); let foo_many = kw!(:foo/many); let db_ident = kw!(:db/ident); - let expected = TypedValue::Instant(DateTime::::from_str("2016-01-01T11:00:00.000Z").unwrap()); + let expected = + TypedValue::Instant(DateTime::::from_str("2016-01-01T11:00:00.000Z").unwrap()); // Fetch a value. - assert_eq!(expected, conn.lookup_value_for_attribute(&c, *entid, &foo_date).unwrap().unwrap()); + assert_eq!( + expected, + conn.lookup_value_for_attribute(&c, *entid, &foo_date) + .unwrap() + .unwrap() + ); // Try to fetch a missing attribute. - assert!(conn.lookup_value_for_attribute(&c, *entid, &db_ident).unwrap().is_none()); + assert!(conn + .lookup_value_for_attribute(&c, *entid, &db_ident) + .unwrap() + .is_none()); // Try to fetch from a non-existent entity. - assert!(conn.lookup_value_for_attribute(&c, 12344567, &foo_date).unwrap().is_none()); + assert!(conn + .lookup_value_for_attribute(&c, 12344567, &foo_date) + .unwrap() + .is_none()); // Fetch a multi-valued property. let two_longs = vec![TypedValue::Long(123), TypedValue::Long(456)]; - let fetched_many = conn.lookup_value_for_attribute(&c, *entid, &foo_many).unwrap().unwrap(); + let fetched_many = conn + .lookup_value_for_attribute(&c, *entid, &foo_many) + .unwrap() + .unwrap(); assert!(two_longs.contains(&fetched_many)); } #[test] fn test_aggregates_type_handling() { let mut store = Store::open("").expect("opened"); - store.transact(r#"[ + store + .transact( + r#"[ {:db/ident :test/boolean :db/valueType :db.type/boolean :db/cardinality :db.cardinality/one} {:db/ident :test/long :db/valueType :db.type/long :db/cardinality :db.cardinality/one} {:db/ident :test/double :db/valueType :db.type/double :db/cardinality :db.cardinality/one} @@ -556,9 +694,13 @@ fn test_aggregates_type_handling() { {:db/ident :test/uuid :db/valueType :db.type/uuid :db/cardinality :db.cardinality/one} {:db/ident :test/instant :db/valueType :db.type/instant :db/cardinality :db.cardinality/one} {:db/ident :test/ref :db/valueType :db.type/ref :db/cardinality :db.cardinality/one} - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - store.transact(r#"[ + store + .transact( + r#"[ {:test/boolean false :test/long 10 :test/double 2.4 @@ -583,71 +725,96 @@ fn test_aggregates_type_handling() { :test/uuid #uuid "77777234-1234-1234-1234-123412341234" :test/instant #inst "2019-01-01T11:00:00.000Z" :test/ref 3} - ]"#).unwrap(); + ]"#, + ) + .unwrap(); // No type limits => can't do it. let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v]]"#, None); let all_types = ValueTypeSet::any(); match r.expect_err("expected query to fail") { - MentatError::ProjectorError(::query_projector_traits::errors::ProjectorError::CannotApplyAggregateOperationToTypes( - SimpleAggregationOp::Sum, types)) => { - assert_eq!(types, all_types); - }, + MentatError::ProjectorError( + ::query_projector_traits::errors::ProjectorError::CannotApplyAggregateOperationToTypes( + SimpleAggregationOp::Sum, + types, + ), + ) => { + assert_eq!(types, all_types); + } e => panic!("Unexpected error type {:?}", e), } // You can't sum instants. - let r = store.q_once(r#"[:find (sum ?v) . + let r = store.q_once( + r#"[:find (sum ?v) . :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, - None); + None, + ); match r.expect_err("expected query to fail") { - MentatError::ProjectorError(::query_projector_traits::errors::ProjectorError::CannotApplyAggregateOperationToTypes( - SimpleAggregationOp::Sum, - types)) => { - assert_eq!(types, ValueTypeSet::of_one(ValueType::Instant)); - }, + MentatError::ProjectorError( + ::query_projector_traits::errors::ProjectorError::CannotApplyAggregateOperationToTypes( + SimpleAggregationOp::Sum, + types, + ), + ) => { + assert_eq!(types, ValueTypeSet::of_one(ValueType::Instant)); + } e => panic!("Unexpected error type {:?}", e), } // But you can count them. - let r = store.q_once(r#"[:find (count ?v) . + let r = store + .q_once( + r#"[:find (count ?v) . :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, - None) - .into_scalar_result() - .expect("results") - .unwrap(); + None, + ) + .into_scalar_result() + .expect("results") + .unwrap(); // Our two transactions, the bootstrap transaction, plus the three values. assert_eq!(Binding::Scalar(TypedValue::Long(6)), r); // And you can min them, which returns an instant. - let r = store.q_once(r#"[:find (min ?v) . + let r = store + .q_once( + r#"[:find (min ?v) . :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, - None) - .into_scalar_result() - .expect("results") - .unwrap(); + None, + ) + .into_scalar_result() + .expect("results") + .unwrap(); - let earliest = DateTime::parse_from_rfc3339("2017-01-01T11:00:00.000Z").unwrap().with_timezone(&Utc); + let earliest = DateTime::parse_from_rfc3339("2017-01-01T11:00:00.000Z") + .unwrap() + .with_timezone(&Utc); assert_eq!(Binding::Scalar(TypedValue::Instant(earliest)), r); - let r = store.q_once(r#"[:find (sum ?v) . + let r = store + .q_once( + r#"[:find (sum ?v) . :where [_ _ ?v] [(type ?v :db.type/long)]]"#, - None) - .into_scalar_result() - .expect("results") - .unwrap(); + None, + ) + .into_scalar_result() + .expect("results") + .unwrap(); // Yes, the current version is in the store as a Long! let total = 30i64 + 20i64 + 10i64 + ::mentat_db::db::CURRENT_VERSION as i64; assert_eq!(Binding::Scalar(TypedValue::Long(total)), r); - let r = store.q_once(r#"[:find (avg ?v) . + let r = store + .q_once( + r#"[:find (avg ?v) . :where [_ _ ?v] [(type ?v :db.type/double)]]"#, - None) - .into_scalar_result() - .expect("results") - .unwrap(); + None, + ) + .into_scalar_result() + .expect("results") + .unwrap(); let avg = (6.4f64 / 3f64) + (4.4f64 / 3f64) + (2.4f64 / 3f64); assert_eq!(Binding::Scalar(TypedValue::Double(avg.into())), r); @@ -658,7 +825,9 @@ fn test_type_reqs() { let mut c = new_connection("").expect("Couldn't open conn."); let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ {:db/ident :test/boolean :db/valueType :db.type/boolean :db/cardinality :db.cardinality/one} {:db/ident :test/long :db/valueType :db.type/long :db/cardinality :db.cardinality/one} {:db/ident :test/double :db/valueType :db.type/double :db/cardinality :db.cardinality/one} @@ -667,9 +836,13 @@ fn test_type_reqs() { {:db/ident :test/uuid :db/valueType :db.type/uuid :db/cardinality :db.cardinality/one} {:db/ident :test/instant :db/valueType :db.type/instant :db/cardinality :db.cardinality/one} {:db/ident :test/ref :db/valueType :db.type/ref :db/cardinality :db.cardinality/one} - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ {:test/boolean true :test/long 33 :test/double 1.4 @@ -678,62 +851,89 @@ fn test_type_reqs() { :test/uuid #uuid "12341234-1234-1234-1234-123412341234" :test/instant #inst "2018-01-01T11:00:00.000Z" :test/ref 1} - ]"#).unwrap(); + ]"#, + ) + .unwrap(); let eid_query = r#"[:find ?eid :where [?eid :test/string "foo"]]"#; - let res = conn.q_once(&mut c, eid_query, None) - .into_rel_result() - .expect("results"); + let res = conn + .q_once(&mut c, eid_query, None) + .into_rel_result() + .expect("results"); assert_eq!(res.row_count(), 1); assert_eq!(res.width, 1); - let entid = - match res.into_iter().next().unwrap().into_iter().next().unwrap() { - Binding::Scalar(TypedValue::Ref(eid)) => { - eid - }, - unexpected => { - panic!("Query to get the entity id returned unexpected result {:?}", unexpected); - } - }; + let entid = match res.into_iter().next().unwrap().into_iter().next().unwrap() { + Binding::Scalar(TypedValue::Ref(eid)) => eid, + unexpected => { + panic!( + "Query to get the entity id returned unexpected result {:?}", + unexpected + ); + } + }; for value_type in ValueType::all_enums().iter() { - let q = format!("[:find [?v ...] :in ?e :where [?e _ ?v] [(type ?v {})]]", value_type.into_keyword()); - let results = conn.q_once(&mut c, &q, QueryInputs::with_value_sequence(vec![ - (Variable::from_valid_name("?e"), TypedValue::Ref(entid)), - ])) - .expect("results") - .into(); + let q = format!( + "[:find [?v ...] :in ?e :where [?e _ ?v] [(type ?v {})]]", + value_type.into_keyword() + ); + let results = conn + .q_once( + &mut c, + &q, + QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?e"), + TypedValue::Ref(entid), + )]), + ) + .expect("results") + .into(); match results { QueryResults::Coll(vals) => { assert_eq!(vals.len(), 1, "Query should find exactly 1 item"); - }, + } v => { panic!("Query returned unexpected type: {:?}", v); } } } - conn.transact(&mut c, r#"[ + conn.transact( + &mut c, + r#"[ {:db/ident :test/long2 :db/valueType :db.type/long :db/cardinality :db.cardinality/one} - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - conn.transact(&mut c, format!("[[:db/add {} :test/long2 5]]", entid)).unwrap(); + conn.transact(&mut c, format!("[[:db/add {} :test/long2 5]]", entid)) + .unwrap(); let longs_query = r#"[:find [?v ...] :order (asc ?v) :in ?e :where [?e _ ?v] [(type ?v :db.type/long)]]"#; - let res = conn.q_once(&mut c, longs_query, QueryInputs::with_value_sequence(vec![ - (Variable::from_valid_name("?e"), TypedValue::Ref(entid)), - ])) - .expect("results") - .into(); + let res = conn + .q_once( + &mut c, + longs_query, + QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?e"), + TypedValue::Ref(entid), + )]), + ) + .expect("results") + .into(); match res { - QueryResults::Coll(vals) => { - assert_eq!(vals, vec![Binding::Scalar(TypedValue::Long(5)), Binding::Scalar(TypedValue::Long(33))]) - }, + QueryResults::Coll(vals) => assert_eq!( + vals, + vec![ + Binding::Scalar(TypedValue::Long(5)), + Binding::Scalar(TypedValue::Long(33)) + ] + ), v => { panic!("Query returned unexpected type: {:?}", v); } @@ -745,7 +945,9 @@ fn test_monster_head_aggregates() { let mut store = Store::open("").expect("opened"); let mut in_progress = store.begin_transaction().expect("began"); - in_progress.transact(r#"[ + in_progress + .transact( + r#"[ {:db/ident :monster/heads :db/valueType :db.type/long :db/cardinality :db.cardinality/one} @@ -757,9 +959,13 @@ fn test_monster_head_aggregates() { {:db/ident :monster/weapon :db/valueType :db.type/string :db/cardinality :db.cardinality/many} - ]"#).expect("transacted"); + ]"#, + ) + .expect("transacted"); - in_progress.transact(r#"[ + in_progress + .transact( + r#"[ {:monster/heads 1 :monster/name "Medusa" :monster/weapon "Stony gaze"} @@ -772,49 +978,62 @@ fn test_monster_head_aggregates() { {:monster/heads 3 :monster/name "Cerberus" :monster/weapon ["8-foot Kong®" "Deadly drool"]} - ]"#).expect("transacted"); + ]"#, + ) + .expect("transacted"); // Without :with, uniqueness applies prior to aggregation, so we get 1 + 3 = 4. - let res = in_progress.q_once("[:find (sum ?heads) . :where [?monster :monster/heads ?heads]]", None) - .expect("results") - .into(); + let res = in_progress + .q_once( + "[:find (sum ?heads) . :where [?monster :monster/heads ?heads]]", + None, + ) + .expect("results") + .into(); match res { QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Long(count)))) => { assert_eq!(count, 4); - }, + } r => panic!("Unexpected result {:?}", r), }; // With :with, uniqueness includes the monster, so we get 1 + 1 + 1 + 3 = 6. - let res = in_progress.q_once("[:find (sum ?heads) . :with ?monster :where [?monster :monster/heads ?heads]]", None) - .expect("results") - .into(); + let res = in_progress + .q_once( + "[:find (sum ?heads) . :with ?monster :where [?monster :monster/heads ?heads]]", + None, + ) + .expect("results") + .into(); match res { QueryResults::Scalar(Some(Binding::Scalar(TypedValue::Long(count)))) => { assert_eq!(count, 6); - }, + } r => panic!("Unexpected result {:?}", r), }; // Aggregates group. - let res = in_progress.q_once(r#"[:find ?name (count ?weapon) + let res = in_progress + .q_once( + r#"[:find ?name (count ?weapon) :with ?monster :order (asc ?name) :where [?monster :monster/name ?name] [?monster :monster/weapon ?weapon]]"#, - None) - .expect("results") - .into(); + None, + ) + .expect("results") + .into(); match res { QueryResults::Rel(vals) => { let expected = vec![ vec!["Cerberus".into(), TypedValue::Long(2)], - vec!["Chimera".into(), TypedValue::Long(1)], - vec!["Cyclops".into(), TypedValue::Long(3)], - vec!["Medusa".into(), TypedValue::Long(1)], + vec!["Chimera".into(), TypedValue::Long(1)], + vec!["Cyclops".into(), TypedValue::Long(3)], + vec!["Medusa".into(), TypedValue::Long(1)], ]; assert_eq!(vals, expected.into()); - }, + } r => panic!("Unexpected result {:?}", r), }; @@ -831,7 +1050,9 @@ fn test_basic_aggregates() { {:db/ident :foo/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} ]"#).unwrap(); - let _ids = store.transact(r#"[ + let _ids = store + .transact( + r#"[ [:db/add "a" :foo/name "Alice"] [:db/add "b" :foo/name "Beli"] [:db/add "c" :foo/name "Carlos"] @@ -844,7 +1065,10 @@ fn test_basic_aggregates() { [:db/add "b" :foo/age 22] [:db/add "c" :foo/age 42] [:db/add "d" :foo/age 28] - ]"#).unwrap().tempids; + ]"#, + ) + .unwrap() + .tempids; // Count the number of distinct bindings of `?veg` that are `true` -- namely, one. // This is not the same as `count-distinct`: note the distinction between @@ -868,16 +1092,20 @@ fn test_basic_aggregates() { AND `datoms00`.v = 1 ); */ - let r = store.q_once(r#"[:find (count ?veg) + let r = store + .q_once( + r#"[:find (count ?veg) :where [_ :foo/is-vegetarian ?veg] - [(ground true) ?veg]]"#, None) - .expect("results") - .into(); + [(ground true) ?veg]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Rel(vals) => { assert_eq!(vals, vec![vec![TypedValue::Long(1)]].into()); - }, + } _ => panic!("Expected rel."), } @@ -891,109 +1119,149 @@ fn test_basic_aggregates() { AND `datoms00`.v = 1 ); */ - let r = store.q_once(r#"[:find (count ?veg) . + let r = store + .q_once( + r#"[:find (count ?veg) . :with ?person :where [?person :foo/is-vegetarian ?veg] - [(ground true) ?veg]]"#, None) - .expect("results") - .into(); + [(ground true) ?veg]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Scalar(Some(val)) => { assert_eq!(val, Binding::Scalar(TypedValue::Long(2))); - }, + } _ => panic!("Expected scalar."), } // What are the oldest and youngest ages? - let r = store.q_once(r#"[:find [(min ?age) (max ?age)] + let r = store + .q_once( + r#"[:find [(min ?age) (max ?age)] :where - [_ :foo/age ?age]]"#, None) - .expect("results") - .into(); + [_ :foo/age ?age]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Tuple(Some(vals)) => { - assert_eq!(vals, - vec![Binding::Scalar(TypedValue::Long(14)), - Binding::Scalar(TypedValue::Long(42))]); - }, + assert_eq!( + vals, + vec![ + Binding::Scalar(TypedValue::Long(14)), + Binding::Scalar(TypedValue::Long(42)) + ] + ); + } _ => panic!("Expected tuple."), } // Who's youngest, via order? - let r = store.q_once(r#"[:find [?name ?age] + let r = store + .q_once( + r#"[:find [?name ?age] :order (asc ?age) :where [?x :foo/age ?age] - [?x :foo/name ?name]]"#, None) - .expect("results") - .into(); + [?x :foo/name ?name]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Tuple(Some(vals)) => { - assert_eq!(vals, - vec!["Alice".into(), - Binding::Scalar(TypedValue::Long(14))]); - }, + assert_eq!( + vals, + vec!["Alice".into(), Binding::Scalar(TypedValue::Long(14))] + ); + } r => panic!("Unexpected results {:?}", r), } // Who's oldest, via order? - let r = store.q_once(r#"[:find [?name ?age] + let r = store + .q_once( + r#"[:find [?name ?age] :order (desc ?age) :where [?x :foo/age ?age] - [?x :foo/name ?name]]"#, None) - .expect("results") - .into(); + [?x :foo/name ?name]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Tuple(Some(vals)) => { - assert_eq!(vals, - vec!["Carlos".into(), - Binding::Scalar(TypedValue::Long(42))]); - }, + assert_eq!( + vals, + vec!["Carlos".into(), Binding::Scalar(TypedValue::Long(42))] + ); + } _ => panic!("Expected tuple."), } // How many of each age do we have? // Add an extra person to make this interesting. - store.transact(r#"[{:foo/name "Medusa", :foo/age 28}]"#).expect("transacted"); + store + .transact(r#"[{:foo/name "Medusa", :foo/age 28}]"#) + .expect("transacted"); // If we omit the 'with', we'll get the wrong answer: - let r = store.q_once(r#"[:find ?age (count ?age) + let r = store + .q_once( + r#"[:find ?age (count ?age) :order (asc ?age) - :where [_ :foo/age ?age]]"#, None) - .expect("results") - .into(); + :where [_ :foo/age ?age]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Rel(vals) => { - assert_eq!(vals, vec![ - vec![TypedValue::Long(14), TypedValue::Long(1)], - vec![TypedValue::Long(22), TypedValue::Long(1)], - vec![TypedValue::Long(28), TypedValue::Long(1)], - vec![TypedValue::Long(42), TypedValue::Long(1)], - ].into()); - }, + assert_eq!( + vals, + vec![ + vec![TypedValue::Long(14), TypedValue::Long(1)], + vec![TypedValue::Long(22), TypedValue::Long(1)], + vec![TypedValue::Long(28), TypedValue::Long(1)], + vec![TypedValue::Long(42), TypedValue::Long(1)], + ] + .into() + ); + } _ => panic!("Expected rel."), } // If we include it, we'll get the right one: - let r = store.q_once(r#"[:find ?age (count ?age) + let r = store + .q_once( + r#"[:find ?age (count ?age) :with ?person :order (asc ?age) - :where [?person :foo/age ?age]]"#, None) - .expect("results") - .into(); + :where [?person :foo/age ?age]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Rel(vals) => { - assert_eq!(vals, vec![ - vec![TypedValue::Long(14), TypedValue::Long(1)], - vec![TypedValue::Long(22), TypedValue::Long(1)], - vec![TypedValue::Long(28), TypedValue::Long(2)], - vec![TypedValue::Long(42), TypedValue::Long(1)], - ].into()); - }, + assert_eq!( + vals, + vec![ + vec![TypedValue::Long(14), TypedValue::Long(1)], + vec![TypedValue::Long(22), TypedValue::Long(1)], + vec![TypedValue::Long(28), TypedValue::Long(2)], + vec![TypedValue::Long(42), TypedValue::Long(1)], + ] + .into() + ); + } _ => panic!("Expected rel."), } } @@ -1002,7 +1270,9 @@ fn test_basic_aggregates() { fn test_combinatorial() { let mut store = Store::open("").expect("opened"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "a" :db/ident :foo/name] [:db/add "a" :db/valueType :db.type/string] [:db/add "a" :db/cardinality :db.cardinality/one] @@ -1010,9 +1280,13 @@ fn test_combinatorial() { [:db/add "b" :db/valueType :db.type/ref] [:db/add "b" :db/cardinality :db.cardinality/many] [:db/add "b" :db/index true] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "a" :foo/name "Alice"] [:db/add "b" :foo/name "Beli"] [:db/add "c" :foo/name "Carlos"] @@ -1032,19 +1306,28 @@ fn test_combinatorial() { [:db/add "a" :foo/dance "ad"] [:db/add "d" :foo/dance "ad"] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); // How many different pairings of dancers were there? // If we just use `!=` (or `differ`), the number is doubled because of symmetry! - assert_eq!(Binding::Scalar(TypedValue::Long(6)), - store.q_once(r#"[:find (count ?right) . + assert_eq!( + Binding::Scalar(TypedValue::Long(6)), + store + .q_once( + r#"[:find (count ?right) . :with ?left :where [?left :foo/dance ?dance] [?right :foo/dance ?dance] - [(differ ?left ?right)]]"#, None) - .into_scalar_result() - .expect("scalar results").unwrap()); + [(differ ?left ?right)]]"#, + None + ) + .into_scalar_result() + .expect("scalar results") + .unwrap() + ); // SQL addresses this by using `<` instead of `!=` -- by imposing // an order on values, we can ensure that each pair only appears once, not @@ -1053,22 +1336,31 @@ fn test_combinatorial() { // will come to rely on it. Instead we expose a specific operator: `unpermute`. // When used in a query that generates permuted pairs of references, this // ensures that only one permutation is returned for a given pair. - assert_eq!(Binding::Scalar(TypedValue::Long(3)), - store.q_once(r#"[:find (count ?right) . + assert_eq!( + Binding::Scalar(TypedValue::Long(3)), + store + .q_once( + r#"[:find (count ?right) . :with ?left :where [?left :foo/dance ?dance] [?right :foo/dance ?dance] - [(unpermute ?left ?right)]]"#, None) - .into_scalar_result() - .expect("scalar results").unwrap()); + [(unpermute ?left ?right)]]"#, + None + ) + .into_scalar_result() + .expect("scalar results") + .unwrap() + ); } #[test] fn test_aggregate_the() { let mut store = Store::open("").expect("opened"); - store.transact(r#"[ + store + .transact( + r#"[ {:db/ident :visit/visitedOnDevice :db/valueType :db.type/ref :db/cardinality :db.cardinality/one} @@ -1096,16 +1388,24 @@ fn test_aggregate_the() { {:db/ident :visit/container :db/valueType :db.type/ref :db/cardinality :db.cardinality/one} - ]"#).expect("transacted schema"); + ]"#, + ) + .expect("transacted schema"); - store.transact(r#"[ + store + .transact( + r#"[ {:db/ident :container/facebook} {:db/ident :container/personal} {:db/ident :device/my-desktop} - ]"#).expect("transacted idents"); + ]"#, + ) + .expect("transacted idents"); - store.transact(r#"[ + store + .transact( + r#"[ {:visit/visitedOnDevice :device/my-desktop :visit/visitAt #inst "2018-04-06T20:46:00Z" :visit/container :container/facebook @@ -1130,31 +1430,39 @@ fn test_aggregate_the() { :visit/page "personalpage"} {:db/id "personalpage" :page/title "Facebook - Log In or Sign Up"} - ]"#).expect("transacted data"); + ]"#, + ) + .expect("transacted data"); - let per_title = - store.q_once(r#" + let per_title = store + .q_once( + r#" [:find (max ?visitDate) ?title :where [?site :site/url "https://www.facebook.com"] [?site :site/visit ?visit] [?visit :visit/container :container/facebook] [?visit :visit/visitAt ?visitDate] [?visit :visit/page ?page] - [?page :page/title ?title]]"#, None) - .into_rel_result() - .expect("two results"); + [?page :page/title ?title]]"#, + None, + ) + .into_rel_result() + .expect("two results"); - let corresponding_title = - store.q_once(r#" + let corresponding_title = store + .q_once( + r#" [:find (max ?visitDate) (the ?title) :where [?site :site/url "https://www.facebook.com"] [?site :site/visit ?visit] [?visit :visit/container :container/facebook] [?visit :visit/visitAt ?visitDate] [?visit :visit/page ?page] - [?page :page/title ?title]]"#, None) - .into_rel_result() - .expect("one result"); + [?page :page/title ?title]]"#, + None, + ) + .into_rel_result() + .expect("one result"); // This test shows the distinction between `?title` and `(the ?title`) — the former returns two // results, while the latter returns one. Without `the` we group by `?title`, getting the @@ -1165,93 +1473,115 @@ fn test_aggregate_the() { assert_eq!(2, per_title.row_count()); assert_eq!(1, corresponding_title.row_count()); - assert_eq!(corresponding_title, - vec![vec![TypedValue::Instant(DateTime::::from_str("2018-04-06T20:46:00.000Z").unwrap()), - TypedValue::typed_string("(1) Facebook")]].into()); + assert_eq!( + corresponding_title, + vec![vec![ + TypedValue::Instant(DateTime::::from_str("2018-04-06T20:46:00.000Z").unwrap()), + TypedValue::typed_string("(1) Facebook") + ]] + .into() + ); } #[test] fn test_null_aggregates() { let store = Store::open("").expect("opened"); - let rel = - store.q_once(r#" + let rel = store + .q_once( + r#" [:find (count ?tx) (max ?txInstant) :where [_ _ _ ?tx] [?tx :db/txInstant ?txInstant] [(< ?txInstant #inst "2016-01-01T11:00:00.000Z")] - ]"#, None) - .into_rel_result() - .expect("no results"); + ]"#, + None, + ) + .into_rel_result() + .expect("no results"); // (count ?tx) is 0, but (max ?txInstant) is over 0 SQL rows, yielding a NULL in the SQL rows. // We reject the entire row containing NULL aggregates. assert_eq!(0, rel.row_count()); - let rel_pull = - store.q_once(r#" + let rel_pull = store + .q_once( + r#" [:find (count ?tx) (max ?txInstant) (pull ?tx [*]) :where [_ _ _ ?tx] [?tx :db/txInstant ?txInstant] [(< ?txInstant #inst "2016-01-01T11:00:00.000Z")] - ]"#, None) - .into_rel_result() - .expect("no results"); + ]"#, + None, + ) + .into_rel_result() + .expect("no results"); // Same logic as above -- just verifying that `RelTwoStagePullProjector` handles NULL. assert_eq!(0, rel_pull.row_count()); - let coll = - store.q_once(r#" + let coll = store + .q_once( + r#" [:find [(max ?txInstant) ...] :where [_ _ _ ?tx] [?tx :db/txInstant ?txInstant] [(< ?txInstant #inst "2016-01-01T11:00:00.000Z")] - ]"#, None) - .into_coll_result() - .expect("no results"); + ]"#, + None, + ) + .into_coll_result() + .expect("no results"); // (max ?txInstant) is over 0 SQL rows, yielding a NULL in the SQL rows. We reject the entire // row containing NULL aggregates, yielding an empty vector of results. assert_eq!(coll, vec![]); - let tuple = - store.q_once(r#" + let tuple = store + .q_once( + r#" [:find [(count ?tx) (max ?txInstant)] :where [_ _ _ ?tx] [?tx :db/txInstant ?txInstant] [(< ?txInstant #inst "2016-01-01T11:00:00.000Z")] - ]"#, None) - .into_tuple_result() - .expect("no results"); + ]"#, + None, + ) + .into_tuple_result() + .expect("no results"); // (count ?tx) is 0, but (max ?txInstant) is over 0 SQL rows, yielding a NULL in the SQL rows. // We reject the entire row containing NULL aggregates, yielding no tuple result at all. assert_eq!(tuple, None); - - let tuple_pull = - store.q_once(r#" + let tuple_pull = store + .q_once( + r#" [:find [(count ?tx) (max ?txInstant) (pull ?tx [*])] :where [_ _ _ ?tx] [?tx :db/txInstant ?txInstant] [(< ?txInstant #inst "2016-01-01T11:00:00.000Z")] - ]"#, None) - .into_tuple_result() - .expect("no results"); + ]"#, + None, + ) + .into_tuple_result() + .expect("no results"); // Same logic as above -- just verifying that `CollTwoStagePullProjector` handles NULL. assert_eq!(tuple_pull, None); - let scalar = - store.q_once(r#" + let scalar = store + .q_once( + r#" [:find (max ?txInstant) . :where [_ _ _ ?tx] [?tx :db/txInstant ?txInstant] [(< ?txInstant #inst "2016-01-01T11:00:00.000Z")] - ]"#, None) - .into_scalar_result() - .expect("no results"); + ]"#, + None, + ) + .into_scalar_result() + .expect("no results"); // (max ?txInstant) is over 0 SQL rows, yielding a NULL in the SQL rows. We reject the entire // row containing NULL aggregates, yielding no scalar result at all. @@ -1262,7 +1592,9 @@ fn test_null_aggregates() { fn test_aggregation_implicit_grouping() { let mut store = Store::open("").expect("opened"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "a" :db/ident :foo/score] [:db/add "a" :db/valueType :db.type/long] [:db/add "a" :db/cardinality :db.cardinality/one] @@ -1277,9 +1609,13 @@ fn test_aggregation_implicit_grouping() { [:db/add "d" :db/cardinality :db.cardinality/many] [:db/add "d" :db/index true] [:db/add "d" :db/unique :db.unique/value] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - let ids = store.transact(r#"[ + let ids = store + .transact( + r#"[ [:db/add "a" :foo/name "Alice"] [:db/add "b" :foo/name "Beli"] [:db/add "c" :foo/name "Carlos"] @@ -1304,102 +1640,155 @@ fn test_aggregation_implicit_grouping() { [:db/add "b" :foo/play "ba"] [:db/add "b" :foo/play "bb"] [:db/add "c" :foo/play "ca"] - ]"#).unwrap().tempids; + ]"#, + ) + .unwrap() + .tempids; // How many different scores were there? - assert_eq!(Binding::Scalar(TypedValue::Long(7)), - store.q_once(r#"[:find (count ?score) . + assert_eq!( + Binding::Scalar(TypedValue::Long(7)), + store + .q_once( + r#"[:find (count ?score) . :where - [?game :foo/score ?score]]"#, None) - .into_scalar_result() - .expect("scalar results").unwrap()); + [?game :foo/score ?score]]"#, + None + ) + .into_scalar_result() + .expect("scalar results") + .unwrap() + ); // How many different games resulted in scores? // '14' appears twice. - assert_eq!(Binding::Scalar(TypedValue::Long(8)), - store.q_once(r#"[:find (count ?score) . + assert_eq!( + Binding::Scalar(TypedValue::Long(8)), + store + .q_once( + r#"[:find (count ?score) . :with ?game :where - [?game :foo/score ?score]]"#, None) - .into_scalar_result() - .expect("scalar results").unwrap()); + [?game :foo/score ?score]]"#, + None + ) + .into_scalar_result() + .expect("scalar results") + .unwrap() + ); // Who's the highest-scoring vegetarian? - assert_eq!(vec!["Alice".into(), Binding::Scalar(TypedValue::Long(99))], - store.q_once(r#"[:find [(the ?name) (max ?score)] + assert_eq!( + vec!["Alice".into(), Binding::Scalar(TypedValue::Long(99))], + store + .q_once( + r#"[:find [(the ?name) (max ?score)] :where [?game :foo/score ?score] [?person :foo/play ?game] [?person :foo/is-vegetarian true] - [?person :foo/name ?name]]"#, None) - .into_tuple_result() - .expect("tuple results").unwrap()); + [?person :foo/name ?name]]"#, + None + ) + .into_tuple_result() + .expect("tuple results") + .unwrap() + ); // We can't run an ambiguous correspondence. - let res = store.q_once(r#"[:find [(the ?name) (min ?score) (max ?score)] + let res = store.q_once( + r#"[:find [(the ?name) (min ?score) (max ?score)] :where [?game :foo/score ?score] [?person :foo/play ?game] [?person :foo/is-vegetarian true] - [?person :foo/name ?name]]"#, None); + [?person :foo/name ?name]]"#, + None, + ); match res.expect_err("expected query to fail") { - MentatError::ProjectorError(::query_projector_traits::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => { + MentatError::ProjectorError( + ::query_projector_traits::errors::ProjectorError::AmbiguousAggregates(mmc, cc), + ) => { assert_eq!(mmc, 2); assert_eq!(cc, 1); - }, + } e => { panic!("Unexpected error type {:?}.", e); - }, + } } // Max scores for vegetarians. - let expected: RelResult = - vec![vec!["Alice".into(), TypedValue::Long(99)], - vec!["Beli".into(), TypedValue::Long(22)]].into(); - assert_eq!(expected, - store.q_once(r#"[:find ?name (max ?score) + let expected: RelResult = vec![ + vec!["Alice".into(), TypedValue::Long(99)], + vec!["Beli".into(), TypedValue::Long(22)], + ] + .into(); + assert_eq!( + expected, + store + .q_once( + r#"[:find ?name (max ?score) :where [?game :foo/score ?score] [?person :foo/play ?game] [?person :foo/is-vegetarian true] - [?person :foo/name ?name]]"#, None) - .into_rel_result() - .expect("rel results")); + [?person :foo/name ?name]]"#, + None + ) + .into_rel_result() + .expect("rel results") + ); // We can combine these aggregates. - let r = store.q_once(r#"[:find ?x ?name (max ?score) (count ?score) (avg ?score) + let r = store + .q_once( + r#"[:find ?x ?name (max ?score) (count ?score) (avg ?score) :with ?game ; So we don't discard duplicate scores! :where [?x :foo/name ?name] [?x :foo/play ?game] - [?game :foo/score ?score]]"#, None) - .expect("results") - .into(); + [?game :foo/score ?score]]"#, + None, + ) + .expect("results") + .into(); match r { QueryResults::Rel(vals) => { - assert_eq!(vals, + assert_eq!( + vals, vec![ - vec![TypedValue::Ref(ids.get("a").cloned().unwrap()), - "Alice".into(), - TypedValue::Long(99), - TypedValue::Long(3), - TypedValue::Double((127f64 / 3f64).into())], - vec![TypedValue::Ref(ids.get("b").cloned().unwrap()), - "Beli".into(), - TypedValue::Long(22), - TypedValue::Long(2), - TypedValue::Double((33f64 / 2f64).into())], - vec![TypedValue::Ref(ids.get("c").cloned().unwrap()), - "Carlos".into(), - TypedValue::Long(42), - TypedValue::Long(1), - TypedValue::Double(42f64.into())], - vec![TypedValue::Ref(ids.get("d").cloned().unwrap()), - "Diana".into(), - TypedValue::Long(28), - TypedValue::Long(2), - TypedValue::Double((33f64 / 2f64).into())]].into()); - }, + vec![ + TypedValue::Ref(ids.get("a").cloned().unwrap()), + "Alice".into(), + TypedValue::Long(99), + TypedValue::Long(3), + TypedValue::Double((127f64 / 3f64).into()) + ], + vec![ + TypedValue::Ref(ids.get("b").cloned().unwrap()), + "Beli".into(), + TypedValue::Long(22), + TypedValue::Long(2), + TypedValue::Double((33f64 / 2f64).into()) + ], + vec![ + TypedValue::Ref(ids.get("c").cloned().unwrap()), + "Carlos".into(), + TypedValue::Long(42), + TypedValue::Long(1), + TypedValue::Double(42f64.into()) + ], + vec![ + TypedValue::Ref(ids.get("d").cloned().unwrap()), + "Diana".into(), + TypedValue::Long(28), + TypedValue::Long(2), + TypedValue::Double((33f64 / 2f64).into()) + ] + ] + .into() + ); + } x => panic!("Got unexpected results {:?}", x), } } @@ -1408,83 +1797,134 @@ fn test_aggregation_implicit_grouping() { fn test_tx_ids() { let mut store = Store::open("").expect("opened"); - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "a" :db/ident :foo/term] [:db/add "a" :db/valueType :db.type/string] [:db/add "a" :db/fulltext false] [:db/add "a" :db/cardinality :db.cardinality/many] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - let tx1 = store.transact(r#"[ + let tx1 = store + .transact( + r#"[ [:db/add "v" :foo/term "1"] - ]"#).expect("tx1 to apply").tx_id; + ]"#, + ) + .expect("tx1 to apply") + .tx_id; - let tx2 = store.transact(r#"[ + let tx2 = store + .transact( + r#"[ [:db/add "v" :foo/term "2"] - ]"#).expect("tx2 to apply").tx_id; + ]"#, + ) + .expect("tx2 to apply") + .tx_id; - let tx3 = store.transact(r#"[ + let tx3 = store + .transact( + r#"[ [:db/add "v" :foo/term "3"] - ]"#).expect("tx3 to apply").tx_id; + ]"#, + ) + .expect("tx3 to apply") + .tx_id; fn assert_tx_id_range(store: &Store, after: Entid, before: Entid, expected: Vec) { // TODO: after https://github.com/mozilla/mentat/issues/641, use q_prepare with inputs bound // at execution time. - let r = store.q_once(r#"[:find [?tx ...] + let r = store + .q_once( + r#"[:find [?tx ...] :in ?after ?before :where [(tx-ids $ ?after ?before) [?tx ...]] ]"#, - QueryInputs::with_value_sequence(vec![ - (Variable::from_valid_name("?after"), TypedValue::Ref(after)), - (Variable::from_valid_name("?before"), TypedValue::Ref(before)), - ])) + QueryInputs::with_value_sequence(vec![ + (Variable::from_valid_name("?after"), TypedValue::Ref(after)), + ( + Variable::from_valid_name("?before"), + TypedValue::Ref(before), + ), + ]), + ) .expect("results") .into(); match r { QueryResults::Coll(txs) => { let expected: Vec = expected.into_iter().map(|tv| tv.into()).collect(); assert_eq!(txs, expected); - }, + } x => panic!("Got unexpected results {:?}", x), } } assert_tx_id_range(&store, tx1, tx2, vec![TypedValue::Ref(tx1)]); - assert_tx_id_range(&store, tx1, tx3, vec![TypedValue::Ref(tx1), TypedValue::Ref(tx2)]); + assert_tx_id_range( + &store, + tx1, + tx3, + vec![TypedValue::Ref(tx1), TypedValue::Ref(tx2)], + ); assert_tx_id_range(&store, tx2, tx3, vec![TypedValue::Ref(tx2)]); - assert_tx_id_range(&store, tx2, tx3 + 1, vec![TypedValue::Ref(tx2), TypedValue::Ref(tx3)]); + assert_tx_id_range( + &store, + tx2, + tx3 + 1, + vec![TypedValue::Ref(tx2), TypedValue::Ref(tx3)], + ); } fn run_tx_data_test(mut store: Store) { - store.transact(r#"[ + store + .transact( + r#"[ [:db/add "a" :db/ident :foo/term] [:db/add "a" :db/valueType :db.type/string] [:db/add "a" :db/fulltext false] [:db/add "a" :db/cardinality :db.cardinality/many] - ]"#).unwrap(); + ]"#, + ) + .unwrap(); - let tx1 = store.transact(r#"[ + let tx1 = store + .transact( + r#"[ [:db/add "e" :foo/term "1"] - ]"#).expect("tx1 to apply"); + ]"#, + ) + .expect("tx1 to apply"); - let tx2 = store.transact(r#"[ + let tx2 = store + .transact( + r#"[ [:db/add "e" :foo/term "2"] - ]"#).expect("tx2 to apply"); + ]"#, + ) + .expect("tx2 to apply"); fn assert_tx_data(store: &Store, tx: &TxReport, value: TypedValue) { // TODO: after https://github.com/mozilla/mentat/issues/641, use q_prepare with inputs bound // at execution time. - let r = store.q_once(r#"[:find ?e ?a-name ?v ?tx ?added + let r = store + .q_once( + r#"[:find ?e ?a-name ?v ?tx ?added :in ?tx-in :where [(tx-data $ ?tx-in) [[?e ?a ?v ?tx ?added]]] [?a :db/ident ?a-name] :order ?e ]"#, - QueryInputs::with_value_sequence(vec![ - (Variable::from_valid_name("?tx-in"), TypedValue::Ref(tx.tx_id)), - ])) + QueryInputs::with_value_sequence(vec![( + Variable::from_valid_name("?tx-in"), + TypedValue::Ref(tx.tx_id), + )]), + ) .expect("results") .into(); @@ -1492,20 +1932,27 @@ fn run_tx_data_test(mut store: Store) { match r { QueryResults::Rel(vals) => { - assert_eq!(vals, - vec![ - vec![TypedValue::Ref(e), - TypedValue::typed_ns_keyword("foo", "term"), - value, - TypedValue::Ref(tx.tx_id), - TypedValue::Boolean(true)], - vec![TypedValue::Ref(tx.tx_id), - TypedValue::typed_ns_keyword("db", "txInstant"), - TypedValue::Instant(tx.tx_instant), - TypedValue::Ref(tx.tx_id), - TypedValue::Boolean(true)], - ].into()); - }, + assert_eq!( + vals, + vec![ + vec![ + TypedValue::Ref(e), + TypedValue::typed_ns_keyword("foo", "term"), + value, + TypedValue::Ref(tx.tx_id), + TypedValue::Boolean(true) + ], + vec![ + TypedValue::Ref(tx.tx_id), + TypedValue::typed_ns_keyword("db", "txInstant"), + TypedValue::Instant(tx.tx_instant), + TypedValue::Ref(tx.tx_id), + TypedValue::Boolean(true) + ], + ] + .into() + ); + } x => panic!("Got unexpected results {:?}", x), } }; diff --git a/tests/tolstoy.rs b/tests/tolstoy.rs index 0d958961..b48c9846 100644 --- a/tests/tolstoy.rs +++ b/tests/tolstoy.rs @@ -8,71 +8,32 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -extern crate uuid; -extern crate mentat; -extern crate edn; -extern crate core_traits; -extern crate public_traits; - -extern crate log; -#[macro_use] extern crate mentat_db; - #[cfg(feature = "syncable")] -extern crate mentat_tolstoy; - -#[cfg(feature = "syncable")] -extern crate tolstoy_traits; - // Run with 'cargo test tolstoy_tests' from top-level. #[cfg(feature = "syncable")] mod tolstoy_tests { - use std::collections::HashMap; + use std::borrow::Borrow; use std::collections::BTreeMap; + use std::collections::HashMap; use std::collections::hash_map::Entry; - use std::borrow::Borrow; - - use edn; - use uuid::Uuid; - use mentat::conn::Conn; + use mentat::{conn::Conn, new_connection}; - use mentat::new_connection; - - use mentat_db::TX0; + use mentat_db::{assert_matches, TX0}; use mentat_tolstoy::{ - Tx, - TxPart, - GlobalTransactionLog, - SyncReport, - SyncFollowup, - Syncer, + debug::parts_to_datoms, GlobalTransactionLog, SyncFollowup, SyncReport, Syncer, Tx, TxPart, }; - use mentat_tolstoy::debug::{ - parts_to_datoms, - txs_after, - }; + use mentat_tolstoy::debug::txs_after; - use mentat_tolstoy::tx_processor::{ - Processor, - TxReceiver, - }; - use core_traits::{ - Entid, - TypedValue, - ValueType, - }; - use public_traits::errors::{ - Result, - MentatError, - }; - use tolstoy_traits::errors::{ - TolstoyError, - }; + use core_traits::{Entid, TypedValue, ValueType}; + use mentat_tolstoy::tx_processor::{Processor, TxReceiver}; + use public_traits::errors::{MentatError, Result}; + use tolstoy_traits::errors::TolstoyError; struct TxCountingReceiver { tx_count: usize, @@ -80,15 +41,15 @@ mod tolstoy_tests { impl TxCountingReceiver { fn new() -> TxCountingReceiver { - TxCountingReceiver { - tx_count: 0, - } + TxCountingReceiver { tx_count: 0 } } } impl TxReceiver for TxCountingReceiver { fn tx(&mut self, _tx_id: Entid, _d: &mut T) -> Result<()> - where T: Iterator { + where + T: Iterator, + { self.tx_count = self.tx_count + 1; Ok(()) } @@ -113,7 +74,9 @@ mod tolstoy_tests { impl TxReceiver>> for TestingReceiver { fn tx(&mut self, tx_id: Entid, d: &mut T) -> Result<()> - where T: Iterator { + where + T: Iterator, + { let datoms = self.txes.entry(tx_id).or_insert(vec![]); datoms.extend(d); Ok(()) @@ -124,7 +87,11 @@ mod tolstoy_tests { } } - fn assert_tx_datoms_count(txes: &BTreeMap>, tx_num: usize, expected_datoms: usize) { + fn assert_tx_datoms_count( + txes: &BTreeMap>, + tx_num: usize, + expected_datoms: usize, + ) { let tx = txes.keys().nth(tx_num).expect("first tx"); let datoms = txes.get(tx).expect("datoms"); assert_eq!(expected_datoms, datoms.len()); @@ -162,7 +129,7 @@ mod tolstoy_tests { if tx == &Uuid::nil() { rowid_range = 0..; } else { - rowid_range = self.tx_rowid[tx] + 1 ..; + rowid_range = self.tx_rowid[tx] + 1..; } let mut txs = vec![]; @@ -186,12 +153,17 @@ mod tolstoy_tests { Entry::Vacant(entry) => { entry.insert(payload.clone()); () - }, + } } Ok(()) } - fn put_transaction(&mut self, tx: &Uuid, _parent_tx: &Uuid, chunk_txs: &Vec) -> Result<()> { + fn put_transaction( + &mut self, + tx: &Uuid, + _parent_tx: &Uuid, + chunk_txs: &Vec, + ) -> Result<()> { let mut parts = vec![]; for chunk_tx in chunk_txs { parts.push(self.chunks.get(chunk_tx).unwrap().clone()); @@ -205,7 +177,9 @@ mod tolstoy_tests { macro_rules! assert_sync { ( $report: pat, $conn: expr, $sqlite: expr, $remote: expr ) => {{ - let mut ip = $conn.begin_transaction(&mut $sqlite).expect("begun successfully"); + let mut ip = $conn + .begin_transaction(&mut $sqlite) + .expect("begun successfully"); match Syncer::sync(&mut ip, &mut $remote).expect("sync report") { $report => (), wr => panic!("Wrong sync report: {:?}", wr), @@ -213,8 +187,12 @@ mod tolstoy_tests { ip.commit().expect("committed"); }}; ( error => $error: pat, $conn: expr, $sqlite: expr, $remote: expr ) => {{ - let mut ip = $conn.begin_transaction(&mut $sqlite).expect("begun successfully"); - match Syncer::sync(&mut ip, &mut $remote).expect_err("expected sync to fail, but did not") { + let mut ip = $conn + .begin_transaction(&mut $sqlite) + .expect("begun successfully"); + match Syncer::sync(&mut ip, &mut $remote) + .expect_err("expected sync to fail, but did not") + { $error => (), we => panic!("Failed with wrong error: {:?}", we), } @@ -240,10 +218,10 @@ mod tolstoy_tests { // Schema assumed to be first transaction. assert_matches!(parts_to_datoms(&$conn.current_schema(), &txs[0].parts), $schema); - let mut index = 1; + let index = 1; $( assert_matches!(parts_to_datoms(&$conn.current_schema(), &txs[index].parts), $tx); - index = index + 1; + let index = index + 1; )* assert_eq!(index, txs.len()); @@ -257,21 +235,34 @@ mod tolstoy_tests { { let db_tx = c.transaction().expect("db tx"); // Ensure that we see a bootstrap transaction. - assert_eq!(1, Processor::process( - &db_tx, None, TxCountingReceiver::new() - ).expect("processor")); + assert_eq!( + 1, + Processor::process(&db_tx, None, TxCountingReceiver::new()).expect("processor") + ); } - let ids = conn.transact(&mut c, r#"[ + let ids = conn + .transact( + &mut c, + r#"[ [:db/add "s" :db/ident :foo/numba] [:db/add "s" :db/valueType :db.type/long] [:db/add "s" :db/cardinality :db.cardinality/one] - ]"#).expect("successful transaction").tempids; + ]"#, + ) + .expect("successful transaction") + .tempids; let numba_entity_id = ids.get("s").unwrap(); - let ids = conn.transact(&mut c, r#"[ + let ids = conn + .transact( + &mut c, + r#"[ [:db/add "b" :foo/numba 123] - ]"#).expect("successful transaction").tempids; + ]"#, + ) + .expect("successful transaction") + .tempids; let _asserted_e = ids.get("b").unwrap(); let first_tx; @@ -290,9 +281,15 @@ mod tolstoy_tests { first_tx = txes.keys().nth(1).expect("first non-bootstrap tx").clone(); } - let ids = conn.transact(&mut c, r#"[ + let ids = conn + .transact( + &mut c, + r#"[ [:db/add "b" :foo/numba 123] - ]"#).expect("successful transaction").tempids; + ]"#, + ) + .expect("successful transaction") + .tempids; let asserted_e = ids.get("b").unwrap(); { @@ -312,7 +309,10 @@ mod tolstoy_tests { // Inspect the assertion. let tx_id = txes.keys().nth(1).expect("tx"); let datoms = txes.get(tx_id).expect("datoms"); - let part = datoms.iter().find(|&part| &part.e == asserted_e).expect("to find asserted datom"); + let part = datoms + .iter() + .find(|&part| &part.e == asserted_e) + .expect("to find asserted datom"); assert_eq!(numba_entity_id, &part.a); assert!(part.v.matches_type(ValueType::Long)); @@ -330,9 +330,14 @@ mod tolstoy_tests { // Fast forward empty remote with a bootstrap transaction. assert_sync!(SyncReport::RemoteFastForward, conn, sqlite, remote_client); - let bootstrap_tx_parts = remote_client.transactions.get(&remote_client.rowid_tx[0]).unwrap(); + let bootstrap_tx_parts = remote_client + .transactions + .get(&remote_client.rowid_tx[0]) + .unwrap(); - assert_matches!(parts_to_datoms(&conn.current_schema(), &bootstrap_tx_parts), "[ + assert_matches!( + parts_to_datoms(&conn.current_schema(), &bootstrap_tx_parts), + "[ [:db.schema/core :db.schema/attribute 1 ?tx true] [:db.schema/core :db.schema/attribute 3 ?tx true] [:db.schema/core :db.schema/attribute 4 ?tx true] @@ -427,7 +432,8 @@ mod tolstoy_tests { [:db/ident :db/index true ?tx true] [:db/txInstant :db/index true ?tx true] [:db.schema/attribute :db/index true ?tx true] - [:db.schema/core :db.schema/version 1 ?tx true]]"); + [:db.schema/core :db.schema/version 1 ?tx true]]" + ); } #[test] @@ -441,10 +447,20 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Fast forward empty remote with a bootstrap transaction from 1. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge 1 and 2 bootstrap transactions. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // Assert that nothing besides a bootstrap transaction is present after a sync on 2. let synced_txs_2 = txs_after(&sqlite_2, &conn_2.current_schema(), TX0); @@ -468,16 +484,31 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions from 1. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // Assert that we end up with the same schema on 2 as we had on 1. assert_transactions!(sqlite_2, conn_2, @@ -503,21 +534,41 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both 1 and 2 define the same schema. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // Assert that 2's schema didn't change after sync. assert_transactions!(sqlite_2, conn_2, @@ -543,24 +594,46 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both 1 and 2 define the same schema. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // But 1 also has an assertion against its schema. - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); assert_transactions!(sqlite_2, conn_2, // Assert that 2's schema is the same as before the sync. @@ -590,24 +663,44 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // 1 defines a richer schema than 2. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/age :db/valueType :db.type/long - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // Assert that 2's schema has been augmented with 1's. assert_transactions!(sqlite_2, conn_2, @@ -636,30 +729,54 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both have the same schema with a unique/identity attribute. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); // Both have the same assertion against the schema. - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); assert_transactions!(sqlite_2, conn_2, // Assert that 2's schema is unchanged. @@ -691,57 +808,101 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both start off with the same schema (a single unique/identity attribute) and an assertion. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // First removes the entity. - conn_1.transact(&mut sqlite_1, r#"[ - [:db/retract (lookup-ref :person/name "Ivan") :person/name "Ivan"]]"#).expect("transacted"); + conn_1 + .transact( + &mut sqlite_1, + r#"[ + [:db/retract (lookup-ref :person/name "Ivan") :person/name "Ivan"]]"#, + ) + .expect("transacted"); // Second changes the entitiy. - conn_2.transact(&mut sqlite_2, r#"[ + conn_2 + .transact( + &mut sqlite_2, + r#"[ {:db/id (lookup-ref :person/name "Ivan") :person/name "Vanya"} - ]"#).expect("transacted"); + ]"#, + ) + .expect("transacted"); // First syncs first. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // And now, merge! - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); // We currently have a primitive conflict resolution strategy, // ending up with a new "Vanya" entity. - assert_transactions!(sqlite_2, conn_2, + assert_transactions!( + sqlite_2, + conn_2, // These hard-coded entids are brittle but deterministic. // They signify that we end up with a new entity Vanya, separate from the one // that was renamed. r#"[[65537 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65537 :person/name "Ivan" ?tx false] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65538 :person/name "Vanya" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"# ); @@ -758,52 +919,97 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both start off with the same schema (a single unique/identity attribute) and an assertion. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // First removes the entity. - conn_1.transact(&mut sqlite_1, r#"[ - [:db/retract (lookup-ref :person/name "Ivan") :person/name "Ivan"]]"#).expect("transacted"); + conn_1 + .transact( + &mut sqlite_1, + r#"[ + [:db/retract (lookup-ref :person/name "Ivan") :person/name "Ivan"]]"#, + ) + .expect("transacted"); // Second changes the entitiy. - conn_2.transact(&mut sqlite_2, r#"[ + conn_2 + .transact( + &mut sqlite_2, + r#"[ [:db/add (lookup-ref :person/name "Ivan") :person/name "Vanya"] - ]"#).expect("transacted"); + ]"#, + ) + .expect("transacted"); // Second syncs first. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // And now, merge! - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_1, + sqlite_1, + remote_client + ); // Deletion of "Ivan" will be dropped on the floor, since there's no such // entity anymore (it's "Vanya"). - assert_transactions!(sqlite_1, conn_1, + assert_transactions!( + sqlite_1, + conn_1, // These hard-coded entids are brittle but deterministic. r#"[[65537 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65537 :person/name "Ivan" ?tx false] [65537 :person/name "Vanya" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"# @@ -821,56 +1027,100 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both start off with the same schema (a single unique/identity attribute) and an assertion. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity - :db/index true}]").expect("transacted"); + :db/index true}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_2, + sqlite_2, + remote_client + ); // First renames the entity. - conn_1.transact(&mut sqlite_1, r#"[ - [:db/add (lookup-ref :person/name "Ivan") :person/name "Vanechka"]]"#).expect("transacted"); + conn_1 + .transact( + &mut sqlite_1, + r#"[ + [:db/add (lookup-ref :person/name "Ivan") :person/name "Vanechka"]]"#, + ) + .expect("transacted"); // Second also renames the entitiy. - conn_2.transact(&mut sqlite_2, r#"[ + conn_2 + .transact( + &mut sqlite_2, + r#"[ [:db/add (lookup-ref :person/name "Ivan") :person/name "Vanya"] - ]"#).expect("transacted"); + ]"#, + ) + .expect("transacted"); // Second syncs first. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // And now, merge! - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_1, + sqlite_1, + remote_client + ); // These hard-coded entids are brittle but deterministic. // They signify that we end up with a new entity Vanechka, separate from the one // that was renamed. - assert_transactions!(sqlite_1, conn_1, + assert_transactions!( + sqlite_1, + conn_1, r#"[[65537 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65537 :person/name "Ivan" ?tx false] [65537 :person/name "Vanya" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - // A new entity is created for the second rename. r#"[[65538 :person/name "Vanechka" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"# @@ -887,23 +1137,37 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/many}]").expect("transacted"); + :db/cardinality :db.cardinality/many}]", + ) + .expect("transacted"); - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); assert_sync!( error => MentatError::TolstoyError(TolstoyError::NotYetImplemented(_)), conn_2, sqlite_2, remote_client); } - #[test] fn test_schema_with_non_matching_entids() { let mut sqlite_1 = new_connection("").unwrap(); @@ -915,27 +1179,59 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // :person/name will be e=65536. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // This entity will be e=65537. - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // :person/name will be e=65536, :person/age will be e=65537 (NB conflict w/ above entity). - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/age :db/valueType :db.type/long - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); - assert_sync!(SyncReport::LocalFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); + assert_sync!( + SyncReport::LocalFastForward, + conn_1, + sqlite_1, + remote_client + ); assert_transactions!(sqlite_1, conn_1, schema => @@ -967,47 +1263,101 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both start off with the same schema (a single unique/identity attribute) and an assertion. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. // Will result in two Ivans. - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); // Upload the second Ivan. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // Get the second Ivan. - assert_sync!(SyncReport::LocalFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::LocalFastForward, + conn_1, + sqlite_1, + remote_client + ); // These entids are determenistic. We can't use lookup-refs because :person/name is // a non-unique attribute. // First removes an Ivan. - conn_1.transact(&mut sqlite_1, r#"[ - [:db/retract 65537 :person/name "Ivan"]]"#).expect("transacted"); + conn_1 + .transact( + &mut sqlite_1, + r#"[ + [:db/retract 65537 :person/name "Ivan"]]"#, + ) + .expect("transacted"); // Second renames an Ivan. - conn_2.transact(&mut sqlite_2, r#"[ - {:db/id 65537 :person/name "Vanya"}]"#).expect("transacted"); + conn_2 + .transact( + &mut sqlite_2, + r#"[ + {:db/id 65537 :person/name "Vanya"}]"#, + ) + .expect("transacted"); // First syncs first. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // And now, merge! - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); // We currently have a primitive conflict resolution strategy, // ending up with a new "Vanya" entity. @@ -1015,16 +1365,15 @@ mod tolstoy_tests { // These hard-coded entids are brittle but deterministic. // They signify that we end up with a new entity Vanya, separate from the one // that was renamed. - assert_transactions!(sqlite_2, conn_2, + assert_transactions!( + sqlite_2, + conn_2, r#"[[65537 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65538 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65537 :person/name "Ivan" ?tx false] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65538 :person/name "Ivan" ?tx false] [65538 :person/name "Vanya" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"# @@ -1042,47 +1391,101 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // Both start off with the same schema (a single unique/identity attribute) and an assertion. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. // Merge will result in two Ivans. - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); // Upload the second Ivan. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // Get the second Ivan. - assert_sync!(SyncReport::LocalFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::LocalFastForward, + conn_1, + sqlite_1, + remote_client + ); // These entids are determenistic. We can't use lookup-refs because :person/name is // a non-unique attribute. // First removes an Ivan. - conn_1.transact(&mut sqlite_1, r#"[ - [:db/retract 65537 :person/name "Ivan"]]"#).expect("transacted"); + conn_1 + .transact( + &mut sqlite_1, + r#"[ + [:db/retract 65537 :person/name "Ivan"]]"#, + ) + .expect("transacted"); // Second renames an Ivan. - conn_2.transact(&mut sqlite_2, r#"[ - [:db/add 65537 :person/name "Vanya"]]"#).expect("transacted"); + conn_2 + .transact( + &mut sqlite_2, + r#"[ + [:db/add 65537 :person/name "Vanya"]]"#, + ) + .expect("transacted"); // Second wins the sync race. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // First merges its changes with second's. - assert_sync!(SyncReport::Merge(SyncFollowup::None), conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::None), + conn_1, + sqlite_1, + remote_client + ); // We currently have a primitive conflict resolution strategy, // ending up dropping first's removal of "Ivan". @@ -1091,13 +1494,13 @@ mod tolstoy_tests { // These hard-coded entids are brittle but deterministic. // They signify that we end up with a new entity Vanya, separate from the one // that was renamed. - assert_transactions!(sqlite_1, conn_1, + assert_transactions!( + sqlite_1, + conn_1, r#"[[65537 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - r#"[[65538 :person/name "Ivan" ?tx true] [?tx :db/txInstant ?ms ?tx true]]"#, - // Just the rename left, removal is dropped on the floor. r#"[[65537 :person/name "Ivan" ?tx false] [65537 :person/name "Vanya" ?tx true] @@ -1116,27 +1519,51 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // 1 defines the same schema as 2. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :world/city :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Vancouver, BC - conn_1.transact(&mut sqlite_1, r#"[{:world/city "Vancouver"}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:world/city "Vancouver"}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :world/city :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Vancouver, WA - conn_2.transact(&mut sqlite_2, r#"[{:world/city "Vancouver"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:world/city "Vancouver"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Since :world/city is not unique, we elect not to smush these entities. - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); assert_transactions!(sqlite_2, conn_2, schema => @@ -1157,10 +1584,20 @@ mod tolstoy_tests { assert_sync!(SyncReport::NoChanges, conn_1, sqlite_1, remote_client); // Follow-up sync. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // 2 should now observe merge results from 1. - assert_sync!(SyncReport::LocalFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::LocalFastForward, + conn_1, + sqlite_1, + remote_client + ); assert_transactions!(sqlite_1, conn_1, // Assert that 1's schema is unchanged. @@ -1190,28 +1627,52 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // 1 defines a richer schema than 2. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/age :db/valueType :db.type/long - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_1.transact(&mut sqlite_1, r#"[{:person/name "Ivan" :person/age 28}]"#).expect("transacted"); + conn_1 + .transact(&mut sqlite_1, r#"[{:person/name "Ivan" :person/age 28}]"#) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#).expect("transacted"); + conn_2 + .transact(&mut sqlite_2, r#"[{:person/name "Ivan"}]"#) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); assert_transactions!(sqlite_2, conn_2, // Assert that 2's schema has been augmented with 1's. @@ -1233,10 +1694,20 @@ mod tolstoy_tests { ); // Follow-up sync after merge. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // Assert that 2's sync fast-forwarded remote. - assert_sync!(SyncReport::LocalFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::LocalFastForward, + conn_1, + sqlite_1, + remote_client + ); assert_transactions!(sqlite_1, conn_1, // Assert that 1's schema remains the same, and it sees the extra Ivan. @@ -1269,27 +1740,47 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // 1 and 2 define different schemas. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/age :db/valueType :db.type/long - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/sin :db/valueType :db.type/long - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. - assert_sync!(SyncReport::Merge(SyncFollowup::FullSync), conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::Merge(SyncFollowup::FullSync), + conn_2, + sqlite_2, + remote_client + ); assert_transactions!(sqlite_2, conn_2, schema => @@ -1308,10 +1799,20 @@ mod tolstoy_tests { ); // Follow-up sync after merge. - assert_sync!(SyncReport::RemoteFastForward, conn_2, sqlite_2, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_2, + sqlite_2, + remote_client + ); // Assert that 2's sync moved forward the remote state. - assert_sync!(SyncReport::LocalFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::LocalFastForward, + conn_1, + sqlite_1, + remote_client + ); assert_transactions!(sqlite_1, conn_1, // Assert that 1's schema is intact, and has been augmented with 2's. @@ -1342,24 +1843,39 @@ mod tolstoy_tests { let mut remote_client = TestRemoteClient::new(); // 1 and 2 define same idents but with different cardinality. - conn_1.transact(&mut sqlite_1, "[ + conn_1 + .transact( + &mut sqlite_1, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/bff :db/valueType :db.type/string - :db/cardinality :db.cardinality/one}]").expect("transacted"); + :db/cardinality :db.cardinality/one}]", + ) + .expect("transacted"); - conn_2.transact(&mut sqlite_2, "[ + conn_2 + .transact( + &mut sqlite_2, + "[ {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :person/bff :db/valueType :db.type/string - :db/cardinality :db.cardinality/many}]").expect("transacted"); + :db/cardinality :db.cardinality/many}]", + ) + .expect("transacted"); // Fast forward empty remote with a bootstrap and schema transactions. - assert_sync!(SyncReport::RemoteFastForward, conn_1, sqlite_1, remote_client); + assert_sync!( + SyncReport::RemoteFastForward, + conn_1, + sqlite_1, + remote_client + ); // Merge bootstrap+schema transactions from 1 into 2. assert_sync!( diff --git a/tests/vocabulary.rs b/tests/vocabulary.rs index 979a4a24..f09ac860 100644 --- a/tests/vocabulary.rs +++ b/tests/vocabulary.rs @@ -13,85 +13,59 @@ extern crate lazy_static; #[macro_use] extern crate mentat; -extern crate mentat_core; -extern crate core_traits; -extern crate mentat_db; -extern crate rusqlite; + +use mentat_db; +use rusqlite; use mentat::vocabulary; use mentat::vocabulary::{ - Definition, - SimpleVocabularySource, - Version, - VersionedStore, - Vocabulary, - VocabularyCheck, - VocabularyOutcome, - VocabularySource, - VocabularyStatus, + Definition, SimpleVocabularySource, Version, VersionedStore, Vocabulary, VocabularyCheck, + VocabularyOutcome, VocabularySource, VocabularyStatus, }; use mentat::query::IntoResult; -use mentat_core::{ - HasSchema, -}; +use mentat_core::HasSchema; -use core_traits::attribute::{ - Unique, -}; +use core_traits::attribute::Unique; // To check our working. use mentat_db::AttributeValidation; use mentat::{ - Conn, - InProgress, - KnownEntid, - Keyword, - QueryInputs, - Queryable, - RelResult, - Store, - Binding, - TypedValue, - ValueType, + Binding, Conn, InProgress, Keyword, KnownEntid, QueryInputs, Queryable, RelResult, Store, + TypedValue, ValueType, }; -use mentat::entity_builder::{ - BuildTerms, - TermBuilder, -}; +use mentat::entity_builder::{BuildTerms, TermBuilder}; use mentat::errors::MentatError; lazy_static! { - static ref FOO_NAME: Keyword = { - kw!(:foo/name) - }; - - static ref FOO_MOMENT: Keyword = { - kw!(:foo/moment) - }; - + static ref FOO_NAME: Keyword = { kw!(:foo/name) }; + static ref FOO_MOMENT: Keyword = { kw!(:foo/moment) }; static ref FOO_VOCAB: vocabulary::Definition = { vocabulary::Definition { name: kw!(:org.mozilla/foo), version: 1, attributes: vec![ - (FOO_NAME.clone(), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .unique(Unique::Identity) - .build()), - (FOO_MOMENT.clone(), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Instant) - .multival(false) - .index(true) - .build()), + ( + FOO_NAME.clone(), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .unique(Unique::Identity) + .build(), + ), + ( + FOO_MOMENT.clone(), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::Instant) + .multival(false) + .index(true) + .build(), + ), ], pre: Definition::no_op, post: Definition::no_op, @@ -100,19 +74,30 @@ lazy_static! { } // Do some work with the appropriate level of paranoia for a shared system. -fn be_paranoid(conn: &mut Conn, sqlite: &mut rusqlite::Connection, name: TypedValue, moment: TypedValue) { +fn be_paranoid( + conn: &mut Conn, + sqlite: &mut rusqlite::Connection, + name: TypedValue, + moment: TypedValue, +) { let mut in_progress = conn.begin_transaction(sqlite).expect("begun successfully"); assert!(in_progress.verify_core_schema().is_ok()); assert!(in_progress.ensure_vocabulary(&FOO_VOCAB).is_ok()); - let a_moment = in_progress.attribute_for_ident(&FOO_MOMENT).expect("exists").1; - let a_name = in_progress.attribute_for_ident(&FOO_NAME).expect("exists").1; + let a_moment = in_progress + .attribute_for_ident(&FOO_MOMENT) + .expect("exists") + .1; + let a_name = in_progress + .attribute_for_ident(&FOO_NAME) + .expect("exists") + .1; let builder = in_progress.builder(); let mut entity = builder.describe_tempid("s"); entity.add(a_name, name).expect("added"); entity.add(a_moment, moment).expect("added"); - assert!(entity.commit().is_ok()); // Discard the TxReport. + assert!(entity.commit().is_ok()); // Discard the TxReport. } #[test] @@ -127,60 +112,59 @@ fn test_real_world() { be_paranoid(&mut conn, &mut sqlite, alice.clone(), now.clone()); be_paranoid(&mut conn, &mut sqlite, barbara.clone(), now.clone()); - let results = conn.q_once(&mut sqlite, r#"[:find ?name ?when + let results = conn + .q_once( + &mut sqlite, + r#"[:find ?name ?when :order (asc ?name) :where [?x :foo/name ?name] [?x :foo/moment ?when] ]"#, - None) - .into_rel_result() - .expect("query succeeded"); - assert_eq!(results, - vec![vec![alice, now.clone()], vec![barbara, now.clone()]].into()); + None, + ) + .into_rel_result() + .expect("query succeeded"); + assert_eq!( + results, + vec![vec![alice, now.clone()], vec![barbara, now.clone()]].into() + ); } #[test] fn test_default_attributebuilder_complains() { // ::new is helpful. ::default is not. assert!(vocabulary::AttributeBuilder::default() - .value_type(ValueType::String) - .multival(true) - .fulltext(true) - .build() - .validate(|| "Foo".to_string()) - .is_err()); + .value_type(ValueType::String) + .multival(true) + .fulltext(true) + .build() + .validate(|| "Foo".to_string()) + .is_err()); assert!(vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(true) - .fulltext(true) - .build() - .validate(|| "Foo".to_string()) - .is_ok()); + .value_type(ValueType::String) + .multival(true) + .fulltext(true) + .build() + .validate(|| "Foo".to_string()) + .is_ok()); } #[test] fn test_add_vocab() { let bar = vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Instant) - .multival(false) - .index(true) - .build(); + .value_type(ValueType::Instant) + .multival(false) + .index(true) + .build(); let baz = vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(true) - .fulltext(true) - .build(); - let bar_only = vec![ - (kw!(:foo/bar), bar.clone()), - ]; - let baz_only = vec![ - (kw!(:foo/baz), baz.clone()), - ]; - let bar_and_baz = vec![ - (kw!(:foo/bar), bar.clone()), - (kw!(:foo/baz), baz.clone()), - ]; + .value_type(ValueType::String) + .multival(true) + .fulltext(true) + .build(); + let bar_only = vec![(kw!(:foo/bar), bar.clone())]; + let baz_only = vec![(kw!(:foo/baz), baz.clone())]; + let bar_and_baz = vec![(kw!(:foo/bar), bar.clone()), (kw!(:foo/baz), baz.clone())]; let foo_v1_a = vocabulary::Definition::new(kw!(:org.mozilla/foo), 1, bar_only.clone()); let foo_v1_b = vocabulary::Definition::new(kw!(:org.mozilla/foo), 1, bar_and_baz.clone()); @@ -200,105 +184,156 @@ fn test_add_vocab() { // Scoped borrow of `conn`. { - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); assert!(in_progress.verify_core_schema().is_ok()); - assert_eq!(VocabularyCheck::NotPresent, in_progress.check_vocabulary(&foo_v1_a).expect("check completed")); - assert_eq!(VocabularyCheck::NotPresent, in_progress.check_vocabulary(&foo_v1_b).expect("check completed")); + assert_eq!( + VocabularyCheck::NotPresent, + in_progress + .check_vocabulary(&foo_v1_a) + .expect("check completed") + ); + assert_eq!( + VocabularyCheck::NotPresent, + in_progress + .check_vocabulary(&foo_v1_b) + .expect("check completed") + ); // If we install v1.a, then it will succeed. - assert_eq!(VocabularyOutcome::Installed, in_progress.ensure_vocabulary(&foo_v1_a).expect("ensure succeeded")); + assert_eq!( + VocabularyOutcome::Installed, + in_progress + .ensure_vocabulary(&foo_v1_a) + .expect("ensure succeeded") + ); // Now we can query to get the vocab. - let ver_attr = - in_progress.q_once(foo_version_query, None) - .into_tuple_result() - .expect("query returns") - .expect("a result"); + let ver_attr = in_progress + .q_once(foo_version_query, None) + .into_tuple_result() + .expect("query returns") + .expect("a result"); assert_eq!(ver_attr[0], TypedValue::Long(1).into()); - assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar").into()); + assert_eq!( + ver_attr[1], + TypedValue::typed_ns_keyword("foo", "bar").into() + ); // If we commit, it'll stick around. in_progress.commit().expect("commit succeeded"); } // It's still there. - let ver_attr = - conn.q_once(&mut sqlite, - foo_version_query, - None) - .into_tuple_result() - .expect("query returns") - .expect("a result"); + let ver_attr = conn + .q_once(&mut sqlite, foo_version_query, None) + .into_tuple_result() + .expect("query returns") + .expect("a result"); assert_eq!(ver_attr[0], TypedValue::Long(1).into()); - assert_eq!(ver_attr[1], TypedValue::typed_ns_keyword("foo", "bar").into()); - - // Scoped borrow of `conn`. - { - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // Subsequently ensuring v1.a again will succeed with no work done. - assert_eq!(VocabularyCheck::Present, in_progress.check_vocabulary(&foo_v1_a).expect("check completed")); - - // Checking for v1.b will say that we have work to do. - assert_eq!(VocabularyCheck::PresentButMissingAttributes { - attributes: vec![&baz_only[0]], - }, in_progress.check_vocabulary(&foo_v1_b).expect("check completed")); - - // Ensuring v1.b will succeed. - assert_eq!(VocabularyOutcome::InstalledMissingAttributes, - in_progress.ensure_vocabulary(&foo_v1_b).expect("ensure succeeded")); - - // Checking v1.a or v1.b again will still succeed with no work done. - assert_eq!(VocabularyCheck::Present, in_progress.check_vocabulary(&foo_v1_a).expect("check completed")); - assert_eq!(VocabularyCheck::Present, in_progress.check_vocabulary(&foo_v1_b).expect("check completed")); - - // Ensuring again does nothing. - assert_eq!(VocabularyOutcome::Existed, in_progress.ensure_vocabulary(&foo_v1_b).expect("ensure succeeded")); - in_progress.commit().expect("commit succeeded"); - } - - // We have both attributes. - let actual_attributes = - conn.q_once(&mut sqlite, - foo_attributes_query, - None) - .into_coll_result() - .expect("query returns"); - assert_eq!(actual_attributes, - vec![ - TypedValue::typed_ns_keyword("foo", "bar").into(), - TypedValue::typed_ns_keyword("foo", "baz").into(), - ]); - - // Now let's modify our vocabulary without bumping the version. This is invalid and will result - // in an error. - let malformed_baz = vocabulary::AttributeBuilder::default() - .value_type(ValueType::Instant) - .multival(true) - .build(); - let bar_and_malformed_baz = vec![ - (kw!(:foo/bar), bar), - (kw!(:foo/baz), malformed_baz.clone()), - ]; - - let foo_v1_malformed = vocabulary::Definition::new( - kw!(:org.mozilla/foo), - 1, - bar_and_malformed_baz.clone() + assert_eq!( + ver_attr[1], + TypedValue::typed_ns_keyword("foo", "bar").into() ); // Scoped borrow of `conn`. { - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - match in_progress.ensure_vocabulary(&foo_v1_malformed).expect_err("expected vocabulary to fail") { + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); + + // Subsequently ensuring v1.a again will succeed with no work done. + assert_eq!( + VocabularyCheck::Present, + in_progress + .check_vocabulary(&foo_v1_a) + .expect("check completed") + ); + + // Checking for v1.b will say that we have work to do. + assert_eq!( + VocabularyCheck::PresentButMissingAttributes { + attributes: vec![&baz_only[0]], + }, + in_progress + .check_vocabulary(&foo_v1_b) + .expect("check completed") + ); + + // Ensuring v1.b will succeed. + assert_eq!( + VocabularyOutcome::InstalledMissingAttributes, + in_progress + .ensure_vocabulary(&foo_v1_b) + .expect("ensure succeeded") + ); + + // Checking v1.a or v1.b again will still succeed with no work done. + assert_eq!( + VocabularyCheck::Present, + in_progress + .check_vocabulary(&foo_v1_a) + .expect("check completed") + ); + assert_eq!( + VocabularyCheck::Present, + in_progress + .check_vocabulary(&foo_v1_b) + .expect("check completed") + ); + + // Ensuring again does nothing. + assert_eq!( + VocabularyOutcome::Existed, + in_progress + .ensure_vocabulary(&foo_v1_b) + .expect("ensure succeeded") + ); + in_progress.commit().expect("commit succeeded"); + } + + // We have both attributes. + let actual_attributes = conn + .q_once(&mut sqlite, foo_attributes_query, None) + .into_coll_result() + .expect("query returns"); + assert_eq!( + actual_attributes, + vec![ + TypedValue::typed_ns_keyword("foo", "bar").into(), + TypedValue::typed_ns_keyword("foo", "baz").into(), + ] + ); + + // Now let's modify our vocabulary without bumping the version. This is invalid and will result + // in an error. + let malformed_baz = vocabulary::AttributeBuilder::default() + .value_type(ValueType::Instant) + .multival(true) + .build(); + let bar_and_malformed_baz = vec![(kw!(:foo/bar), bar), (kw!(:foo/baz), malformed_baz.clone())]; + + let foo_v1_malformed = + vocabulary::Definition::new(kw!(:org.mozilla/foo), 1, bar_and_malformed_baz.clone()); + + // Scoped borrow of `conn`. + { + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); + match in_progress + .ensure_vocabulary(&foo_v1_malformed) + .expect_err("expected vocabulary to fail") + { MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours) => { assert_eq!(vocab.as_str(), ":org.mozilla/foo"); assert_eq!(attr.as_str(), ":foo/baz"); assert_eq!(version, 1); assert_eq!(&theirs, &baz); assert_eq!(&ours, &malformed_baz); - }, + } _ => panic!(), } } @@ -308,34 +343,48 @@ fn test_add_vocab() { // bump the version number. let multival_bar = vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Instant) - .multival(true) - .index(true) - .build(); - let multival_bar_and_baz = vec![ - (kw!(:foo/bar), multival_bar), - (kw!(:foo/baz), baz.clone()), - ]; + .value_type(ValueType::Instant) + .multival(true) + .index(true) + .build(); + let multival_bar_and_baz = vec![(kw!(:foo/bar), multival_bar), (kw!(:foo/baz), baz.clone())]; - let altered_vocabulary = vocabulary::Definition::new( - kw!(:org.mozilla/foo), - 2, - multival_bar_and_baz - ); + let altered_vocabulary = + vocabulary::Definition::new(kw!(:org.mozilla/foo), 2, multival_bar_and_baz); // foo/bar starts single-valued. - assert_eq!(false, conn.current_schema().attribute_for_ident(&kw!(:foo/bar)).expect("attribute").0.multival); + assert_eq!( + false, + conn.current_schema() + .attribute_for_ident(&kw!(:foo/bar)) + .expect("attribute") + .0 + .multival + ); // Scoped borrow of `conn`. { - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - assert_eq!(in_progress.ensure_vocabulary(&altered_vocabulary).expect("success"), - VocabularyOutcome::Upgraded); + let mut in_progress = conn + .begin_transaction(&mut sqlite) + .expect("begun successfully"); + assert_eq!( + in_progress + .ensure_vocabulary(&altered_vocabulary) + .expect("success"), + VocabularyOutcome::Upgraded + ); in_progress.commit().expect("commit succeeded"); } // Now it's multi-valued. - assert_eq!(true, conn.current_schema().attribute_for_ident(&kw!(:foo/bar)).expect("attribute").0.multival); + assert_eq!( + true, + conn.current_schema() + .attribute_for_ident(&kw!(:foo/bar)) + .expect("attribute") + .0 + .multival + ); } /// A helper to turn rows from `[:find ?e ?a :where [?e ?a ?v]]` into a tuple. @@ -344,7 +393,7 @@ fn ea(row: Vec) -> (KnownEntid, KnownEntid) { match (row.next(), row.next()) { (Some(Binding::Scalar(TypedValue::Ref(e))), Some(Binding::Scalar(TypedValue::Ref(a)))) => { (KnownEntid(e), KnownEntid(a)) - }, + } _ => panic!("Incorrect query shape for 'ea' helper."), } } @@ -356,7 +405,7 @@ fn av(row: Vec) -> (KnownEntid, TypedValue) { match (row.next(), row.next()) { (Some(Binding::Scalar(TypedValue::Ref(a))), Some(v)) => { (KnownEntid(a), v.into_scalar().unwrap()) - }, + } _ => panic!("Incorrect query shape for 'av' helper."), } } @@ -378,14 +427,17 @@ fn inches_to_cm(inches: Inches) -> Centimeters { (inches as f64 * 2.54f64) as Centimeters } -fn height_of_person(in_progress: &InProgress, name: &str) -> Option { - let h = in_progress.q_once(r#"[:find ?h . +fn height_of_person(in_progress: &InProgress<'_, '_>, name: &str) -> Option { + let h = in_progress + .q_once( + r#"[:find ?h . :in ?name :where [?p :person/name ?name] [?p :person/height ?h]]"#, - QueryInputs::with_value_sequence(vec![(var!(?name), TypedValue::typed_string(name))])) - .into_scalar_result() - .expect("result"); + QueryInputs::with_value_sequence(vec![(var!(?name), TypedValue::typed_string(name))]), + ) + .into_scalar_result() + .expect("result"); match h { Some(Binding::Scalar(TypedValue::Long(v))) => Some(v), _ => None, @@ -433,13 +485,13 @@ fn test_upgrade_with_functions() { let food_v1 = vocabulary::Definition { name: kw!(:org.mozilla/food), version: 1, - attributes: vec![ - (kw!(:food/name), - vocabulary::AttributeBuilder::helpful() + attributes: vec![( + kw!(:food/name), + vocabulary::AttributeBuilder::helpful() .value_type(ValueType::String) .multival(false) - .build()), - ], + .build(), + )], pre: Definition::no_op, post: Definition::no_op, }; @@ -448,18 +500,22 @@ fn test_upgrade_with_functions() { name: kw!(:org.mozilla/movies), version: 1, attributes: vec![ - (kw!(:movie/year), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Long) // No need for Instant here. - .multival(false) - .build()), - (kw!(:movie/title), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .unique(Unique::Identity) - .index(true) - .build()), + ( + kw!(:movie/year), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::Long) // No need for Instant here. + .multival(false) + .build(), + ), + ( + kw!(:movie/title), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .unique(Unique::Identity) + .index(true) + .build(), + ), ], pre: Definition::no_op, post: Definition::no_op, @@ -469,46 +525,55 @@ fn test_upgrade_with_functions() { name: kw!(:org.mozilla/people), version: 1, attributes: vec![ - (kw!(:person/name), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .unique(Unique::Identity) - .index(true) - .build()), - (kw!(:person/height), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Long) - .multival(false) - .build()), - (kw!(:person/likes), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Ref) - .multival(true) - .build()), + ( + kw!(:person/name), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .unique(Unique::Identity) + .index(true) + .build(), + ), + ( + kw!(:person/height), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::Long) + .multival(false) + .build(), + ), + ( + kw!(:person/likes), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::Ref) + .multival(true) + .build(), + ), ], pre: Definition::no_op, post: Definition::no_op, }; // Apply v1 of each. - let mut v1_provider = SimpleVocabularySource::with_definitions( - vec![ - food_v1.clone(), - movies_v1.clone(), - people_v1.clone(), - ]); + let mut v1_provider = SimpleVocabularySource::with_definitions(vec![ + food_v1.clone(), + movies_v1.clone(), + people_v1.clone(), + ]); // Mutable borrow of store. { let mut in_progress = store.begin_transaction().expect("began"); - in_progress.ensure_vocabularies(&mut v1_provider).expect("success"); + in_progress + .ensure_vocabularies(&mut v1_provider) + .expect("success"); // Also add some data. We do this in one transaction 'cos -- thanks to the modeling errors // we are about to fix! -- it's a little awkward to make references to entities without // unique attributes. - in_progress.transact(r#"[ + in_progress + .transact( + r#"[ {:movie/title "John Wick" :movie/year 2014 :db/id "mjw"} @@ -544,7 +609,9 @@ fn test_upgrade_with_functions() { :person/height 68 :person/likes ["muc", "mp", "md", "fwbw", "fS"]} - ]"#).expect("transacted"); + ]"#, + ) + .expect("transacted"); in_progress.commit().expect("commit succeeded"); } @@ -557,15 +624,15 @@ fn test_upgrade_with_functions() { let movies_v2 = vocabulary::Definition { name: kw!(:org.mozilla/movies), version: 2, - attributes: vec![ - (kw!(:movie/title), - vocabulary::AttributeBuilder::helpful() + attributes: vec![( + kw!(:movie/title), + vocabulary::AttributeBuilder::helpful() .value_type(ValueType::String) .multival(false) .non_unique() .index(true) - .build()), - ], + .build(), + )], pre: Definition::no_op, post: Definition::no_op, }; @@ -577,20 +644,33 @@ fn test_upgrade_with_functions() { // We can now add another Dune movie: Denis Villeneuve's 2019 version. // (Let's just pretend that it's been released, here in 2018!) - in_progress.transact(r#"[ + in_progress + .transact( + r#"[ {:movie/title "Dune" :movie/year 2019} - ]"#).expect("transact succeeded"); + ]"#, + ) + .expect("transact succeeded"); // And we can query both. - let years = - in_progress.q_once(r#"[:find [?year ...] + let years = in_progress + .q_once( + r#"[:find [?year ...] :where [?movie :movie/title "Dune"] [?movie :movie/year ?year] - :order (asc ?year)]"#, None) - .into_coll_result() - .expect("coll"); - assert_eq!(years, vec![Binding::Scalar(TypedValue::Long(1984)), Binding::Scalar(TypedValue::Long(2019))]); + :order (asc ?year)]"#, + None, + ) + .into_coll_result() + .expect("coll"); + assert_eq!( + years, + vec![ + Binding::Scalar(TypedValue::Long(1984)), + Binding::Scalar(TypedValue::Long(2019)) + ] + ); in_progress.commit().expect("commit succeeded"); } @@ -598,23 +678,35 @@ fn test_upgrade_with_functions() { // Migration 2: let's fix those heights! // - fn convert_heights_to_centimeters(ip: &mut InProgress, from: &Vocabulary) -> mentat::errors::Result<()> { + fn convert_heights_to_centimeters( + ip: &mut InProgress<'_, '_>, + from: &Vocabulary, + ) -> mentat::errors::Result<()> { let mut builder = TermBuilder::new(); // We keep a redundant safety check here to avoid running twice! if from.version < 2 { // Find every height and multiply it by 2.54. let person_height = ip.get_entid(&kw!(:person/height)).unwrap(); - for row in ip.q_once("[:find ?p ?h :where [?p :person/height ?h]]", None) - .into_rel_result()? - .into_iter() { + for row in ip + .q_once("[:find ?p ?h :where [?p :person/height ?h]]", None) + .into_rel_result()? + .into_iter() + { let mut row = row.into_iter(); match (row.next(), row.next()) { - (Some(Binding::Scalar(TypedValue::Ref(person))), Some(Binding::Scalar(TypedValue::Long(height)))) => { + ( + Some(Binding::Scalar(TypedValue::Ref(person))), + Some(Binding::Scalar(TypedValue::Long(height))), + ) => { // No need to explicitly retract: cardinality-one. - builder.add(KnownEntid(person), person_height, TypedValue::Long(inches_to_cm(height)))?; - }, - _ => {}, + builder.add( + KnownEntid(person), + person_height, + TypedValue::Long(inches_to_cm(height)), + )?; + } + _ => {} } } } @@ -623,17 +715,24 @@ fn test_upgrade_with_functions() { return Ok(()); } - ip.transact_builder(builder).and(Ok(())).map_err(|e| e.into()) + ip.transact_builder(builder) + .and(Ok(())) + .map_err(|e| e.into()) } - fn people_v1_to_v2(ip: &mut InProgress, from: &Vocabulary) -> mentat::errors::Result<()> { + fn people_v1_to_v2( + ip: &mut InProgress<'_, '_>, + from: &Vocabulary, + ) -> mentat::errors::Result<()> { convert_heights_to_centimeters(ip, from)?; // Let's update our documentation, too. if from.version < 2 { - ip.transact(r#"[ + ip.transact( + r#"[ [:db/add :person/height :db/doc "A person's height in centimeters."] - ]"#)?; + ]"#, + )?; } Ok(()) } @@ -655,7 +754,9 @@ fn test_upgrade_with_functions() { // Before, Sam's height is 64 (inches). assert_eq!(Some(64), height_of_person(&in_progress, "Sam")); - in_progress.ensure_vocabulary(&people_v2).expect("expected success"); + in_progress + .ensure_vocabulary(&people_v2) + .expect("expected success"); // Now, Sam's height is 162 (centimeters). assert_eq!(Some(162), height_of_person(&in_progress, "Sam")); @@ -671,24 +772,32 @@ fn test_upgrade_with_functions() { /// This is a straightforward migration -- replace the old name with the new one. /// This would be everything we need if we _knew_ there were no collisions (e.g., we were /// cleaning up UUIDs). - fn lowercase_names(ip: &mut InProgress) -> mentat::errors::Result<()> { + fn lowercase_names(ip: &mut InProgress<'_, '_>) -> mentat::errors::Result<()> { let food_name = ip.get_entid(&kw!(:food/name)).unwrap(); let mut builder = TermBuilder::new(); - for row in ip.q_once("[:find ?f ?name :where [?f :food/name ?name]]", None) - .into_rel_result()? - .into_iter() { + for row in ip + .q_once("[:find ?f ?name :where [?f :food/name ?name]]", None) + .into_rel_result()? + .into_iter() + { let mut row = row.into_iter(); match (row.next(), row.next()) { - (Some(Binding::Scalar(TypedValue::Ref(food))), Some(Binding::Scalar(TypedValue::String(name)))) => { + ( + Some(Binding::Scalar(TypedValue::Ref(food))), + Some(Binding::Scalar(TypedValue::String(name))), + ) => { if name.chars().any(|c| !c.is_lowercase()) { let lowercased = name.to_lowercase(); - println!("Need to rename {} from '{}' to '{}'", food, name, lowercased); + println!( + "Need to rename {} from '{}' to '{}'", + food, name, lowercased + ); let new_name: TypedValue = lowercased.into(); builder.add(KnownEntid(food), food_name, new_name)?; } - }, - _ => {}, + } + _ => {} } } @@ -696,23 +805,33 @@ fn test_upgrade_with_functions() { return Ok(()); } - ip.transact_builder(builder).and(Ok(())).map_err(|e| e.into()) + ip.transact_builder(builder) + .and(Ok(())) + .map_err(|e| e.into()) } /// This is the function we write to dedupe. This logic is very suitable for sharing: /// indeed, "make this attribute unique by merging entities" is something we should /// lift out for reuse. - fn merge_foods_with_same_name(ip: &mut InProgress) -> mentat::errors::Result<()> { + fn merge_foods_with_same_name(ip: &mut InProgress<'_, '_>) -> mentat::errors::Result<()> { let mut builder = TermBuilder::new(); - for row in ip.q_once("[:find ?a ?b + for row in ip + .q_once( + "[:find ?a ?b :where [?a :food/name ?name] [?b :food/name ?name] - [(unpermute ?a ?b)]]", None) - .into_rel_result()? - .into_iter() { + [(unpermute ?a ?b)]]", + None, + ) + .into_rel_result()? + .into_iter() + { let mut row = row.into_iter(); match (row.next(), row.next()) { - (Some(Binding::Scalar(TypedValue::Ref(left))), Some(Binding::Scalar(TypedValue::Ref(right)))) => { + ( + Some(Binding::Scalar(TypedValue::Ref(left))), + Some(Binding::Scalar(TypedValue::Ref(right))), + ) => { let keep = KnownEntid(left); let replace = KnownEntid(right); @@ -720,30 +839,38 @@ fn test_upgrade_with_functions() { // We should offer some support for doing this, 'cos this is long-winded and has // the unexpected side-effect of also trying to retract metadata about the entity… println!("Replacing uses of {} to {}.", replace.0, keep.0); - for (a, v) in ip.q_once("[:find ?a ?v + for (a, v) in ip + .q_once( + "[:find ?a ?v :in ?old :where [?old ?a ?v]]", - QueryInputs::with_value_sequence(vec![(var!(?old), replace.into())])) - .into_rel_result()? - .into_iter() - .map(av) { + QueryInputs::with_value_sequence(vec![(var!(?old), replace.into())]), + ) + .into_rel_result()? + .into_iter() + .map(av) + { builder.retract(replace, a, v.clone())?; builder.add(keep, a, v)?; } - for (e, a) in ip.q_once("[:find ?e ?a + for (e, a) in ip + .q_once( + "[:find ?e ?a :in ?old :where [?e ?a ?old]]", - QueryInputs::with_value_sequence(vec![(var!(?old), replace.into())])) - .into_rel_result()? - .into_iter() - .map(ea) { + QueryInputs::with_value_sequence(vec![(var!(?old), replace.into())]), + ) + .into_rel_result()? + .into_iter() + .map(ea) + { builder.retract(e, a, replace)?; builder.add(e, a, keep)?; } // TODO: `retractEntity` on `replace` (when we support that). - }, - _ => {}, + } + _ => {} } } @@ -751,7 +878,9 @@ fn test_upgrade_with_functions() { return Ok(()); } - ip.transact_builder(builder).and(Ok(())).map_err(|e| e.into()) + ip.transact_builder(builder) + .and(Ok(())) + .map_err(|e| e.into()) } // This migration is bad: it can't impose the uniqueness constraint because we end up with @@ -759,17 +888,17 @@ fn test_upgrade_with_functions() { let food_v2_bad = vocabulary::Definition { name: kw!(:org.mozilla/food), version: 2, - attributes: vec![ - (kw!(:food/name), - vocabulary::AttributeBuilder::helpful() + attributes: vec![( + kw!(:food/name), + vocabulary::AttributeBuilder::helpful() .value_type(ValueType::String) .multival(false) .unique(Unique::Identity) - .build()), - ], + .build(), + )], pre: |ip, from| { if from.version < 2 { - lowercase_names(ip) // <- no merging! + lowercase_names(ip) // <- no merging! } else { Ok(()) } @@ -781,14 +910,14 @@ fn test_upgrade_with_functions() { let food_v2_good = vocabulary::Definition { name: kw!(:org.mozilla/food), version: 2, - attributes: vec![ - (kw!(:food/name), - vocabulary::AttributeBuilder::helpful() + attributes: vec![( + kw!(:food/name), + vocabulary::AttributeBuilder::helpful() .value_type(ValueType::String) .multival(false) .unique(Unique::Identity) - .build()), - ], + .build(), + )], pre: |ip, from| { if from.version < 2 { lowercase_names(ip).and_then(|_| merge_foods_with_same_name(ip)) @@ -804,39 +933,53 @@ fn test_upgrade_with_functions() { let mut in_progress = store.begin_transaction().expect("began"); // Yep, the bad one fails! - let _err = in_progress.ensure_vocabulary(&food_v2_bad).expect_err("expected error"); + let _err = in_progress + .ensure_vocabulary(&food_v2_bad) + .expect_err("expected error"); } // Before the good migration, Sam and Beth don't like any of the same foods. - assert!(store.q_once(r#"[:find [?food ...] + assert!(store + .q_once( + r#"[:find [?food ...] :where [?sam :person/name "Sam"] [?beth :person/name "Beth"] [?sam :person/likes ?f] [?beth :person/likes ?f] - [?f :food/name ?food]]"#, None) - .into_coll_result() - .expect("success") - .is_empty()); + [?f :food/name ?food]]"#, + None + ) + .into_coll_result() + .expect("success") + .is_empty()); // Mutable borrow of store. { let mut in_progress = store.begin_transaction().expect("began"); // The good one succeeded! - in_progress.ensure_vocabulary(&food_v2_good).expect("expected success"); + in_progress + .ensure_vocabulary(&food_v2_good) + .expect("expected success"); in_progress.commit().expect("commit succeeded"); } // After, Sam and Beth both like "spice" — the same entity. - assert_eq!(store.q_once(r#"[:find [?food ...] + assert_eq!( + store + .q_once( + r#"[:find [?food ...] :where [?sam :person/name "Sam"] [?beth :person/name "Beth"] [?sam :person/likes ?f] [?beth :person/likes ?f] - [?f :food/name ?food]]"#, None) - .into_coll_result() - .expect("success"), - vec![TypedValue::typed_string("spice").into()]); + [?f :food/name ?food]]"#, + None + ) + .into_coll_result() + .expect("success"), + vec![TypedValue::typed_string("spice").into()] + ); // // Migration 4: multi-definition migration. @@ -856,20 +999,23 @@ fn test_upgrade_with_functions() { name: kw!(:org.mozilla/movies), version: 3, attributes: vec![ - (kw!(:movie/title), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .non_unique() - .index(true) - .build()), - + ( + kw!(:movie/title), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .non_unique() + .index(true) + .build(), + ), // This phrasing is backward, but this is just a test. - (kw!(:movie/likes), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Ref) - .multival(true) - .build()), + ( + kw!(:movie/likes), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::Ref) + .multival(true) + .build(), + ), ], pre: Definition::no_op, post: Definition::no_op, @@ -878,19 +1024,22 @@ fn test_upgrade_with_functions() { name: kw!(:org.mozilla/food), version: 3, attributes: vec![ - (kw!(:food/name), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::String) - .multival(false) - .unique(Unique::Identity) - .build()), - + ( + kw!(:food/name), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::String) + .multival(false) + .unique(Unique::Identity) + .build(), + ), // This phrasing is backward, but this is just a test. - (kw!(:food/likes), - vocabulary::AttributeBuilder::helpful() - .value_type(ValueType::Ref) - .multival(true) - .build()), + ( + kw!(:food/likes), + vocabulary::AttributeBuilder::helpful() + .value_type(ValueType::Ref) + .multival(true) + .build(), + ), ], pre: |ip, from| { if from.version < 2 { @@ -939,7 +1088,11 @@ fn test_upgrade_with_functions() { self.definitions.clone() } - fn pre(&mut self, _in_progress: &mut InProgress, checks: &VocabularyStatus) -> mentat::errors::Result<()> { + fn pre( + &mut self, + _in_progress: &mut InProgress<'_, '_>, + checks: &dyn VocabularyStatus, + ) -> mentat::errors::Result<()> { // Take a look at the work the vocabulary manager thinks needs to be done, and see whether we // need to migrate data. // We'll simulate that here by tracking the version. @@ -947,14 +1100,14 @@ fn test_upgrade_with_functions() { Some((_, &VocabularyCheck::PresentButNeedsUpdate { ref older_version })) => { self.pre_food_version = older_version.version; Ok(()) - }, + } _ => { panic!("Test expected a different state."); - }, + } } } - fn post(&mut self, ip: &mut InProgress) -> mentat::errors::Result<()> { + fn post(&mut self, ip: &mut InProgress<'_, '_>) -> mentat::errors::Result<()> { self.post_done = true; // We know that if this function is running, some upgrade was necessary, and we're now @@ -968,31 +1121,44 @@ fn test_upgrade_with_functions() { // Find all uses of :person/likes, splitting them into the two properties we just // introduced. - for (e, v) in ip.q_once("[:find ?e ?v + for (e, v) in ip + .q_once( + "[:find ?e ?v :where [?e :person/likes ?v] [?v :food/name _]]", - None) - .into_rel_result()? - .into_iter() - .map(ev) { + None, + ) + .into_rel_result()? + .into_iter() + .map(ev) + { builder.retract(e, person_likes, v.clone())?; builder.add(e, food_likes, v)?; } - for (e, v) in ip.q_once("[:find ?e ?v + for (e, v) in ip + .q_once( + "[:find ?e ?v :where [?e :person/likes ?v] [?v :movie/title _]]", - None) - .into_rel_result()? - .into_iter() - .map(ev) { + None, + ) + .into_rel_result()? + .into_iter() + .map(ev) + { builder.retract(e, person_likes, v.clone())?; builder.add(e, movie_likes, v)?; } - builder.add(person_likes, db_doc, - TypedValue::typed_string("Deprecated. Use :movie/likes or :food/likes instead."))?; - ip.transact_builder(builder).and(Ok(())).map_err(|e| e.into()) + builder.add( + person_likes, + db_doc, + TypedValue::typed_string("Deprecated. Use :movie/likes or :food/likes instead."), + )?; + ip.transact_builder(builder) + .and(Ok(())) + .map_err(|e| e.into()) } }; @@ -1000,7 +1166,9 @@ fn test_upgrade_with_functions() { let mut in_progress = store.begin_transaction().expect("began"); // The good one succeeded! - in_progress.ensure_vocabularies(&mut all_three).expect("expected success"); + in_progress + .ensure_vocabularies(&mut all_three) + .expect("expected success"); in_progress.commit().expect("commit succeeded"); } @@ -1010,20 +1178,32 @@ fn test_upgrade_with_functions() { // Now we can fetch just the foods or movies that Sam likes, without having to filter on // :food/name or :movie/title -- they're separate relations. - assert_eq!(store.q_once(r#"[:find [?f ...] + assert_eq!( + store + .q_once( + r#"[:find [?f ...] :where [?sam :person/name "Sam"] - [?sam :food/likes ?f]]"#, None) - .into_coll_result() - .expect("success") - .len(), - 2); - assert_eq!(store.q_once(r#"[:find [?m ...] + [?sam :food/likes ?f]]"#, + None + ) + .into_coll_result() + .expect("success") + .len(), + 2 + ); + assert_eq!( + store + .q_once( + r#"[:find [?m ...] :where [?sam :person/name "Sam"] - [?sam :movie/likes ?m]]"#, None) - .into_coll_result() - .expect("success") - .len(), - 2); + [?sam :movie/likes ?m]]"#, + None + ) + .into_coll_result() + .expect("success") + .len(), + 2 + ); // Find the height and name of anyone who likes both spice and movies released after 2012, // sorted by height. @@ -1037,9 +1217,11 @@ fn test_upgrade_with_functions() { [?p :person/height ?height] [?p :person/name ?name]]"#; let r = store.q_once(q, None).into_rel_result().unwrap(); - let expected: RelResult = - vec![vec![TypedValue::typed_string("Sam"), TypedValue::Long(162)], - vec![TypedValue::typed_string("Beth"), TypedValue::Long(172)]].into(); + let expected: RelResult = vec![ + vec![TypedValue::typed_string("Sam"), TypedValue::Long(162)], + vec![TypedValue::typed_string("Beth"), TypedValue::Long(172)], + ] + .into(); assert_eq!(expected, r); // Find foods that Upstream Color fans like. @@ -1050,8 +1232,9 @@ fn test_upgrade_with_functions() { [?p :food/likes ?f] [?f :food/name ?food]]"#; let r = store.q_once(q, None).into_coll_result().unwrap(); - let expected: Vec = - vec![TypedValue::typed_string("spice").into(), - TypedValue::typed_string("weird blue worms").into()]; + let expected: Vec = vec![ + TypedValue::typed_string("spice").into(), + TypedValue::typed_string("weird blue worms").into(), + ]; assert_eq!(expected, r); } diff --git a/tolstoy-traits/Cargo.toml b/tolstoy-traits/Cargo.toml index 8e02deb6..8ae541b6 100644 --- a/tolstoy-traits/Cargo.toml +++ b/tolstoy-traits/Cargo.toml @@ -13,13 +13,14 @@ sqlcipher = ["rusqlite/sqlcipher"] [dependencies] failure = "0.1.1" failure_derive = "0.1.1" -hyper = "0.11" +http = "0.2.0" +hyper = "0.13" serde_json = "1.0" -uuid = { version = "0.5" } +uuid = { version = "0.8" } [dependencies.db_traits] path = "../db-traits" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] diff --git a/tolstoy-traits/errors.rs b/tolstoy-traits/errors.rs index 40b4461b..82b81236 100644 --- a/tolstoy-traits/errors.rs +++ b/tolstoy-traits/errors.rs @@ -8,16 +8,14 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +use hyper; +use rusqlite; +use serde_json; use std; use std::error::Error; -use rusqlite; use uuid; -use hyper; -use serde_json; -use db_traits::errors::{ - DbError, -}; +use db_traits::errors::DbError; #[derive(Debug, Fail)] pub enum TolstoyError { @@ -58,57 +56,57 @@ pub enum TolstoyError { IoError(#[cause] std::io::Error), #[fail(display = "{}", _0)] - UuidError(#[cause] uuid::ParseError), + UuidError(#[cause] uuid::Error), #[fail(display = "{}", _0)] NetworkError(#[cause] hyper::Error), #[fail(display = "{}", _0)] - UriError(#[cause] hyper::error::UriError), + UriError(#[cause] http::uri::InvalidUri), } impl From for TolstoyError { - fn from(error: DbError) -> TolstoyError { + fn from(error: DbError) -> Self { TolstoyError::DbError(error) } } impl From for TolstoyError { - fn from(error: serde_json::Error) -> TolstoyError { + fn from(error: serde_json::Error) -> Self { TolstoyError::SerializationError(error) } } impl From for TolstoyError { - fn from(error: rusqlite::Error) -> TolstoyError { + fn from(error: rusqlite::Error) -> Self { let cause = match error.source() { Some(e) => e.to_string(), - None => "".to_string() + None => "".to_string(), }; TolstoyError::RusqliteError(error.to_string(), cause) } } impl From for TolstoyError { - fn from(error: std::io::Error) -> TolstoyError { + fn from(error: std::io::Error) -> Self { TolstoyError::IoError(error) } } -impl From for TolstoyError { - fn from(error: uuid::ParseError) -> TolstoyError { +impl From for TolstoyError { + fn from(error: uuid::Error) -> Self { TolstoyError::UuidError(error) } } impl From for TolstoyError { - fn from(error: hyper::Error) -> TolstoyError { + fn from(error: hyper::Error) -> Self { TolstoyError::NetworkError(error) } } -impl From for TolstoyError { - fn from(error: hyper::error::UriError) -> TolstoyError { +impl From for TolstoyError { + fn from(error: http::uri::InvalidUri) -> Self { TolstoyError::UriError(error) } } diff --git a/tolstoy-traits/lib.rs b/tolstoy-traits/lib.rs index 3cdd9c3c..17a90bac 100644 --- a/tolstoy-traits/lib.rs +++ b/tolstoy-traits/lib.rs @@ -14,8 +14,8 @@ extern crate failure_derive; extern crate hyper; extern crate rusqlite; -extern crate uuid; extern crate serde_json; +extern crate uuid; extern crate db_traits; diff --git a/tolstoy/Cargo.toml b/tolstoy/Cargo.toml index 7c2a7642..8e641fb7 100644 --- a/tolstoy/Cargo.toml +++ b/tolstoy/Cargo.toml @@ -1,4 +1,5 @@ [package] +edition = "2018" name = "mentat_tolstoy" version = "0.0.1" workspace = ".." @@ -8,17 +9,21 @@ authors = ["Grisha Kruglov "] sqlcipher = ["rusqlite/sqlcipher"] [dependencies] -failure = "0.1.1" -futures = "0.1" -hyper = "0.11" +failure = "0.1.6" +futures = "0.3" +hyper = "0.13" +hyper-tls = "0.4.1" +http = "0.2" log = "0.4" +mime = "0.3.16" +#tokio = { version = "0.2", features = ["full"] } tokio-core = "0.1" serde = "1.0" serde_json = "1.0" -serde_cbor = "0.8.2" +serde_cbor = "0.11.1" serde_derive = "1.0" -lazy_static = "0.2" -uuid = { version = "0.5", features = ["v4", "serde"] } +lazy_static = "1.4.0" +uuid = { version = "0.8", features = ["v4", "serde"] } [dependencies.edn] path = "../edn" @@ -46,5 +51,5 @@ path = "../public-traits" path = "../transaction" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] diff --git a/tolstoy/src/bootstrap.rs b/tolstoy/src/bootstrap.rs index 9920edbb..5a872ece 100644 --- a/tolstoy/src/bootstrap.rs +++ b/tolstoy/src/bootstrap.rs @@ -8,32 +8,20 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use mentat_core::{ - Keyword, -}; +use mentat_core::Keyword; -use mentat_db::{ - CORE_SCHEMA_VERSION, -}; +use mentat_db::CORE_SCHEMA_VERSION; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use tolstoy_traits::errors::{ - TolstoyError, -}; +use tolstoy_traits::errors::TolstoyError; -use datoms::{ - DatomsHelper, -}; +use crate::datoms::DatomsHelper; -use types::{ - Tx, -}; +use crate::types::Tx; pub struct BootstrapHelper<'a> { - parts: DatomsHelper<'a> + parts: DatomsHelper<'a>, } impl<'a> BootstrapHelper<'a> { @@ -58,10 +46,14 @@ impl<'a> BootstrapHelper<'a> { // TODO v is just a type tag and a Copy value, we shouldn't need to clone. match v.clone().into_long() { Some(v) => Ok(v), - None => bail!(TolstoyError::BadRemoteState("incorrect type for core schema version".to_string())) + None => bail!(TolstoyError::BadRemoteState( + "incorrect type for core schema version".to_string() + )), } - }, - None => bail!(TolstoyError::BadRemoteState("missing core schema version".to_string())) + } + None => bail!(TolstoyError::BadRemoteState( + "missing core schema version".to_string() + )), } } } @@ -70,11 +62,9 @@ impl<'a> BootstrapHelper<'a> { mod tests { use super::*; - use mentat_db::debug::{ - TestConn, - }; + use mentat_db::debug::TestConn; - use debug::txs_after; + use crate::debug::txs_after; #[test] fn test_bootstrap_version() { diff --git a/tolstoy/src/datoms.rs b/tolstoy/src/datoms.rs index b610d5d2..3655f72c 100644 --- a/tolstoy/src/datoms.rs +++ b/tolstoy/src/datoms.rs @@ -8,16 +8,10 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use edn::{ - Keyword, -}; +use edn::Keyword; -use core_traits::{ - Entid, - TypedValue, -}; - -use types::TxPart; +use crate::TxPart; +use core_traits::{Entid, TypedValue}; /// A primitive query interface geared toward processing bootstrap-like sets of datoms. pub struct DatomsHelper<'a> { @@ -26,9 +20,7 @@ pub struct DatomsHelper<'a> { impl<'a> DatomsHelper<'a> { pub fn new(parts: &'a Vec) -> DatomsHelper { - DatomsHelper { - parts: parts, - } + DatomsHelper { parts: parts } } // TODO these are obviously quite inefficient diff --git a/tolstoy/src/debug.rs b/tolstoy/src/debug.rs index 4306c9be..fc4b002e 100644 --- a/tolstoy/src/debug.rs +++ b/tolstoy/src/debug.rs @@ -1,105 +1,86 @@ -// Copyright 2018 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -// TODO could hide this behind #[cfg(test)], since this is only for test use. - -use rusqlite; - -use uuid::Uuid; - -use edn::entities::{ - EntidOrIdent, -}; - -use core_traits::{ - Entid, - TypedValue, -}; - -use mentat_core::{ - HasSchema, - Schema, -}; - -use mentat_db::{ - TypedSQLValue, -}; - -use mentat_db::debug::{ - Datom, - Datoms, - transactions_after, -}; - -use types::{ - Tx, - TxPart, -}; - -/// A rough equivalent of mentat_db::debug::transactions_after -/// for Tolstoy's Tx type. -pub fn txs_after(sqlite: &rusqlite::Connection, schema: &Schema, after: Entid) -> Vec { - let transactions = transactions_after( - sqlite, schema, after - ).expect("remote transactions"); - - let mut txs = vec![]; - - for transaction in transactions.0 { - let mut tx = Tx { - tx: Uuid::new_v4(), - parts: vec![], - }; - - for datom in &transaction.0 { - let e = match datom.e { - EntidOrIdent::Entid(ref e) => *e, - _ => panic!(), - }; - let a = match datom.a { - EntidOrIdent::Entid(ref a) => *a, - EntidOrIdent::Ident(ref a) => schema.get_entid(a).unwrap().0, - }; - - tx.parts.push(TxPart { - partitions: None, - e: e, - a: a, - v: TypedValue::from_edn_value(&datom.v).unwrap(), - tx: datom.tx, - added: datom.added.unwrap() - }); - } - - txs.push(tx); - } - - txs -} - -pub fn part_to_datom(schema: &Schema, part: &TxPart) -> Datom { - Datom { - e: match schema.get_ident(part.e) { - Some(ident) => EntidOrIdent::Ident(ident.clone()), - None => EntidOrIdent::Entid(part.e), - }, - a: match schema.get_ident(part.a) { - Some(ident) => EntidOrIdent::Ident(ident.clone()), - None => EntidOrIdent::Entid(part.a), - }, - v: TypedValue::to_edn_value_pair(&part.v).0, - tx: part.tx, - added: Some(part.added), - } -} - -pub fn parts_to_datoms(schema: &Schema, parts: &Vec) -> Datoms { - Datoms(parts.iter().map(|p| part_to_datom(schema, p)).collect()) -} +// Copyright 2018 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// TODO: could hide this behind #[cfg(test)], since this is only for test use. + +use rusqlite; + +use uuid::Uuid; + +use edn::entities::EntidOrIdent; + +use core_traits::{Entid, TypedValue}; + +use mentat_core::{HasSchema, Schema}; + +use mentat_db::TypedSQLValue; + +use mentat_db::debug::{transactions_after, Datom, Datoms}; + +use crate::types::{Tx, TxPart}; + +/// A rough equivalent of mentat_db::debug::transactions_after +/// for Tolstoy's Tx type. +pub fn txs_after(sqlite: &rusqlite::Connection, schema: &Schema, after: Entid) -> Vec { + let transactions = transactions_after(sqlite, schema, after).expect("remote transactions"); + + let mut txs = vec![]; + + for transaction in transactions.0 { + let mut tx = Tx { + tx: Uuid::new_v4(), + parts: vec![], + }; + + for datom in &transaction.0 { + let e = match datom.e { + EntidOrIdent::Entid(ref e) => *e, + _ => panic!(), + }; + let a = match datom.a { + EntidOrIdent::Entid(ref a) => *a, + EntidOrIdent::Ident(ref a) => schema.get_entid(a).unwrap().0, + }; + + tx.parts.push(TxPart { + partitions: None, + e: e, + a: a, + v: TypedValue::from_edn_value(&datom.v).unwrap(), + tx: datom.tx, + added: datom.added.unwrap(), + }); + } + + txs.push(tx); + } + + txs +} + +pub fn part_to_datom(schema: &Schema, part: &TxPart) -> Datom { + Datom { + e: match schema.get_ident(part.e) { + Some(ident) => EntidOrIdent::Ident(ident.clone()), + None => EntidOrIdent::Entid(part.e), + }, + a: match schema.get_ident(part.a) { + Some(ident) => EntidOrIdent::Ident(ident.clone()), + None => EntidOrIdent::Entid(part.a), + }, + v: TypedValue::to_edn_value_pair(&part.v).0, + tx: part.tx, + added: Some(part.added), + } +} + +pub fn parts_to_datoms(schema: &Schema, parts: &Vec) -> Datoms { + Datoms(parts.iter().map(|p| part_to_datom(schema, p)).collect()) +} diff --git a/tolstoy/src/lib.rs b/tolstoy/src/lib.rs index 0b78a7b4..b1cd59c5 100644 --- a/tolstoy/src/lib.rs +++ b/tolstoy/src/lib.rs @@ -21,56 +21,40 @@ extern crate edn; extern crate hyper; // TODO https://github.com/mozilla/mentat/issues/569 // extern crate hyper_tls; -extern crate tokio_core; extern crate futures; extern crate serde; extern crate serde_cbor; extern crate serde_json; +extern crate tokio_core; extern crate log; extern crate mentat_db; -extern crate mentat_core; extern crate db_traits; +extern crate mentat_core; #[macro_use] extern crate core_traits; extern crate public_traits; extern crate rusqlite; extern crate uuid; -extern crate tolstoy_traits; extern crate mentat_transaction; +extern crate tolstoy_traits; pub mod bootstrap; pub mod metadata; -pub use metadata::{ - PartitionsTable, - SyncMetadata, -}; +pub use metadata::{PartitionsTable, SyncMetadata}; mod datoms; pub mod debug; pub mod remote_client; -pub use remote_client::{ - RemoteClient, -}; +pub use remote_client::RemoteClient; pub mod schema; pub mod syncer; -pub use syncer::{ - Syncer, - SyncReport, - SyncResult, - SyncFollowup, -}; -mod tx_uploader; +pub use syncer::{SyncFollowup, SyncReport, SyncResult, Syncer}; pub mod logger; pub mod tx_mapper; -pub use tx_mapper::{ - TxMapper, -}; +mod tx_uploader; +pub use tx_mapper::TxMapper; pub mod tx_processor; pub mod types; -pub use types::{ - Tx, - TxPart, - GlobalTransactionLog, -}; +pub use types::{GlobalTransactionLog, Tx, TxPart}; diff --git a/tolstoy/src/metadata.rs b/tolstoy/src/metadata.rs index fd699480..d32035e1 100644 --- a/tolstoy/src/metadata.rs +++ b/tolstoy/src/metadata.rs @@ -13,31 +13,19 @@ use rusqlite; use uuid::Uuid; -use core_traits::{ - Entid, -}; +use core_traits::Entid; -use schema; +use crate::schema; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use tolstoy_traits::errors::{ - TolstoyError, -}; +use tolstoy_traits::errors::TolstoyError; -use mentat_db::{ - Partition, - PartitionMap, - db, -}; +use mentat_db::{db, Partition, PartitionMap}; -use types::{ - LocalGlobalTxMapping, -}; +use crate::types::LocalGlobalTxMapping; -use TxMapper; +use crate::TxMapper; // Could be Copy, but that might change pub struct SyncMetadata { @@ -64,77 +52,102 @@ impl SyncMetadata { } pub fn remote_head(tx: &rusqlite::Transaction) -> Result { - Uuid::from_bytes( - tx.query_row( - "SELECT value FROM tolstoy_metadata WHERE key = ?", - rusqlite::params![&schema::REMOTE_HEAD_KEY], |r| { - let bytes: Vec = r.get(0).unwrap(); - Ok(bytes) - } - )?.as_slice() - ).map_err(|e| e.into()) + let uuid_bytes = tx.query_row( + "SELECT value FROM tolstoy_metadata WHERE key = ?", + rusqlite::params![&schema::REMOTE_HEAD_KEY], + |r| { + let bytes: Vec = r.get(0).unwrap(); + Ok(bytes) + }, + )?; + Ok(Uuid::from_slice(uuid_bytes.as_slice())?) } pub fn set_remote_head(tx: &rusqlite::Transaction, uuid: &Uuid) -> Result<()> { let uuid_bytes = uuid.as_bytes().to_vec(); - let updated = tx.execute("UPDATE tolstoy_metadata SET value = ? WHERE key = ?", - rusqlite::params![&uuid_bytes, &schema::REMOTE_HEAD_KEY])?; + let updated = tx.execute( + "UPDATE tolstoy_metadata SET value = ? WHERE key = ?", + rusqlite::params![&uuid_bytes, &schema::REMOTE_HEAD_KEY], + )?; if updated != 1 { - bail!(TolstoyError::DuplicateMetadata(schema::REMOTE_HEAD_KEY.into())); + bail!(TolstoyError::DuplicateMetadata( + schema::REMOTE_HEAD_KEY.into() + )); } Ok(()) } - pub fn set_remote_head_and_map(tx: &mut rusqlite::Transaction, mapping: LocalGlobalTxMapping) -> Result<()> { + pub fn set_remote_head_and_map( + tx: &mut rusqlite::Transaction, + mapping: LocalGlobalTxMapping, + ) -> Result<()> { SyncMetadata::set_remote_head(tx, mapping.remote)?; TxMapper::set_lg_mapping(tx, mapping)?; Ok(()) } // TODO Functions below start to blur the line between mentat-proper and tolstoy... - pub fn get_partitions(tx: &rusqlite::Transaction, parts_table: PartitionsTable) -> Result { + pub fn get_partitions( + tx: &rusqlite::Transaction, + parts_table: PartitionsTable, + ) -> Result { match parts_table { - PartitionsTable::Core => { - db::read_partition_map(tx).map_err(|e| e.into()) - }, + PartitionsTable::Core => db::read_partition_map(tx).map_err(|e| e.into()), PartitionsTable::Tolstoy => { - let mut stmt: ::rusqlite::Statement = tx.prepare("SELECT part, start, end, idx, allow_excision FROM tolstoy_parts")?; - let m: Result = stmt.query_and_then(rusqlite::params![], |row| -> Result<(String, Partition)> { - Ok((row.get(0)?, Partition::new(row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?))) - })?.collect(); + let mut stmt: ::rusqlite::Statement = + tx.prepare("SELECT part, start, end, idx, allow_excision FROM tolstoy_parts")?; + let m: Result = stmt + .query_and_then(rusqlite::params![], |row| -> Result<(String, Partition)> { + Ok(( + row.get(0)?, + Partition::new(row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?), + )) + })? + .collect(); m } } } pub fn root_and_head_tx(tx: &rusqlite::Transaction) -> Result<(Entid, Entid)> { - let mut stmt: ::rusqlite::Statement = tx.prepare("SELECT tx FROM timelined_transactions WHERE timeline = 0 GROUP BY tx ORDER BY tx")?; - let txs: Vec<_> = stmt.query_and_then(rusqlite::params![], |row| -> Result { - Ok(row.get(0)?) - })?.collect(); + let mut stmt: ::rusqlite::Statement = tx.prepare( + "SELECT tx FROM timelined_transactions WHERE timeline = 0 GROUP BY tx ORDER BY tx", + )?; + let txs: Vec<_> = stmt + .query_and_then(rusqlite::params![], |row| -> Result { + Ok(row.get(0)?) + })? + .collect(); let mut txs = txs.into_iter(); let root_tx = match txs.nth(0) { - None => bail!(TolstoyError::UnexpectedState(format!("Could not get root tx"))), - Some(t) => t? + None => bail!(TolstoyError::UnexpectedState(format!( + "Could not get root tx" + ))), + Some(t) => t?, }; match txs.last() { None => Ok((root_tx, root_tx)), - Some(t) => Ok((root_tx, t?)) + Some(t) => Ok((root_tx, t?)), } } pub fn local_txs(db_tx: &rusqlite::Transaction, after: Option) -> Result> { let after_clause = match after { Some(t) => format!("WHERE timeline = 0 AND tx > {}", t), - None => format!("WHERE timeline = 0") + None => format!("WHERE timeline = 0"), }; - let mut stmt: ::rusqlite::Statement = db_tx.prepare(&format!("SELECT tx FROM timelined_transactions {} GROUP BY tx ORDER BY tx", after_clause))?; - let txs: Vec<_> = stmt.query_and_then(rusqlite::params![], |row| -> Result { - Ok(row.get(0)?) - })?.collect(); + let mut stmt: ::rusqlite::Statement = db_tx.prepare(&format!( + "SELECT tx FROM timelined_transactions {} GROUP BY tx ORDER BY tx", + after_clause + ))?; + let txs: Vec<_> = stmt + .query_and_then(rusqlite::params![], |row| -> Result { + Ok(row.get(0)?) + })? + .collect(); let mut all = Vec::with_capacity(txs.len()); for tx in txs { @@ -151,7 +164,11 @@ impl SyncMetadata { Ok(count == 0) } - pub fn has_entity_assertions_in_tx(db_tx: &rusqlite::Transaction, e: Entid, tx_id: Entid) -> Result { + pub fn has_entity_assertions_in_tx( + db_tx: &rusqlite::Transaction, + e: Entid, + tx_id: Entid, + ) -> Result { let count: i64 = db_tx.query_row("SELECT count(rowid) FROM timelined_transactions WHERE timeline = 0 AND tx = ? AND e = ?", rusqlite::params![&tx_id, &e], |row| { Ok(row.get(0)?) })?; @@ -169,7 +186,10 @@ mod tests { fn test_get_remote_head_default() { let mut conn = schema::tests::setup_conn_bare(); let tx = schema::tests::setup_tx(&mut conn); - assert_eq!(Uuid::nil(), SyncMetadata::remote_head(&tx).expect("fetch succeeded")); + assert_eq!( + Uuid::nil(), + SyncMetadata::remote_head(&tx).expect("fetch succeeded") + ); } #[test] @@ -178,7 +198,10 @@ mod tests { let tx = schema::tests::setup_tx(&mut conn); let uuid = Uuid::new_v4(); SyncMetadata::set_remote_head(&tx, &uuid).expect("update succeeded"); - assert_eq!(uuid, SyncMetadata::remote_head(&tx).expect("fetch succeeded")); + assert_eq!( + uuid, + SyncMetadata::remote_head(&tx).expect("fetch succeeded") + ); } #[test] @@ -205,11 +228,44 @@ mod tests { // last attribute is the timeline (0). - db_tx.execute("INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", rusqlite::params![&268435457, &3, &1529971773701734_i64, &268435457, &1, &4, &0]).expect("inserted"); - db_tx.execute("INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", rusqlite::params![&65536, &1, &":person/name", &268435457, &1, &13, &0]).expect("inserted"); - db_tx.execute("INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", rusqlite::params![&65536, &7, &27, &268435457, &1, &0, &0]).expect("inserted"); - db_tx.execute("INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", rusqlite::params![&65536, &9, &36, &268435457, &1, &0, &0]).expect("inserted"); - db_tx.execute("INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", rusqlite::params![&65536, &11, &1, &268435457, &1, &1, &0]).expect("inserted"); + db_tx + .execute( + "INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![ + &268435457, + &3, + &1529971773701734_i64, + &268435457, + &1, + &4, + &0 + ], + ) + .expect("inserted"); + db_tx + .execute( + "INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![&65536, &1, &":person/name", &268435457, &1, &13, &0], + ) + .expect("inserted"); + db_tx + .execute( + "INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![&65536, &7, &27, &268435457, &1, &0, &0], + ) + .expect("inserted"); + db_tx + .execute( + "INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![&65536, &9, &36, &268435457, &1, &0, &0], + ) + .expect("inserted"); + db_tx + .execute( + "INSERT INTO timelined_transactions VALUES (?, ?, ?, ?, ?, ?, ?)", + rusqlite::params![&65536, &11, &1, &268435457, &1, &1, &0], + ) + .expect("inserted"); let (root_tx, last_tx) = SyncMetadata::root_and_head_tx(&db_tx).expect("last tx"); assert_eq!(268435456, root_tx); diff --git a/tolstoy/src/remote_client.rs b/tolstoy/src/remote_client.rs index 6cf0bd06..da176c97 100644 --- a/tolstoy/src/remote_client.rs +++ b/tolstoy/src/remote_client.rs @@ -12,47 +12,28 @@ use std; -use futures::{future, Future, Stream}; -use hyper; -// TODO: enable TLS support; hurdle is cross-compiling openssl for Android. -// See https://github.com/mozilla/mentat/issues/569 -// use hyper_tls; -use hyper::{ - Method, - Request, - StatusCode, - Error as HyperError -}; -use hyper::header::{ - ContentType, -}; +use hyper::{body, header, Body, Client, Method, Request, StatusCode}; +use hyper_tls::HttpsConnector; // TODO: https://github.com/mozilla/mentat/issues/570 // use serde_cbor; +use futures::executor::block_on; use serde_json; -use tokio_core::reactor::Core; use uuid::Uuid; -use public_traits::errors::{ - Result, -}; +use crate::logger::d; +use public_traits::errors::Result; -use logger::d; +use crate::types::{GlobalTransactionLog, Tx, TxPart}; -use types::{ - Tx, - TxPart, - GlobalTransactionLog, -}; - -#[derive(Serialize,Deserialize)] +#[derive(Serialize, Deserialize)] struct SerializedHead { - head: Uuid + head: Uuid, } #[derive(Serialize)] struct SerializedTransaction<'a> { parent: &'a Uuid, - chunks: &'a Vec + chunks: &'a Vec, } #[derive(Deserialize)] @@ -89,170 +70,146 @@ impl RemoteClient { } // TODO what we want is a method that returns a deserialized json structure. - // It'll need a type T so that consumers can specify what downloaded json will - // map to. I ran into borrow issues doing that - probably need to restructure - // this and use PhantomData markers or somesuch. - // But for now, we get code duplication. + // It'll need a type T so that consumers can specify what downloaded json will map to. I ran + // into borrow issues doing that - probably need to restructure this and use PhantomData markers + // or somesuch. But for now, we get code duplication. fn get_uuid(&self, uri: String) -> Result { - let mut core = Core::new()?; - // TODO https://github.com/mozilla/mentat/issues/569 - // let client = hyper::Client::configure() - // .connector(hyper_tls::HttpsConnector::new(4, &core.handle()).unwrap()) - // .build(&core.handle()); - let client = hyper::Client::new(&core.handle()); + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, Body>(https); d(&format!("client")); let uri = uri.parse()?; - d(&format!("parsed uri {:?}", uri)); - let work = client.get(uri).and_then(|res| { - println!("Response: {}", res.status()); + d(&format!("GET {:?}", uri)); - res.body().concat2().and_then(move |body| { - let json: SerializedHead = serde_json::from_slice(&body).map_err(|e| { - std::io::Error::new(std::io::ErrorKind::Other, e) - })?; - Ok(json) - }) - }); + let work = async { + let res = client.get(uri).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + dbg!("response.status: {}", res.status()); - d(&format!("running...")); - - let head_json = core.run(work)?; - d(&format!("got head: {:?}", &head_json.head)); - Ok(head_json.head) + let body_bytes = body::to_bytes(res.into_body()).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + let body = + String::from_utf8(body_bytes.to_vec()).expect("response was not valid utf-8"); + let json: SerializedHead = serde_json::from_str(&body) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(json.head) + }; + block_on(work) } fn put(&self, uri: String, payload: T, expected: StatusCode) -> Result<()> - where hyper::Body: std::convert::From, { - let mut core = Core::new()?; - // TODO https://github.com/mozilla/mentat/issues/569 - // let client = hyper::Client::configure() - // .connector(hyper_tls::HttpsConnector::new(4, &core.handle()).unwrap()) - // .build(&core.handle()); - let client = hyper::Client::new(&core.handle()); - - let uri = uri.parse()?; + where + hyper::Body: std::convert::From, + { + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, Body>(https); d(&format!("PUT {:?}", uri)); - let mut req = Request::new(Method::Put, uri); - req.headers_mut().set(ContentType::json()); - req.set_body(payload); + let req = Request::builder() + .method(Method::PUT) + .uri(uri) + .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.to_string()) + .body(payload.into()) + .unwrap(); - let put = client.request(req).and_then(|res| { + let work = async { + let res = client.request(req).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; let status_code = res.status(); if status_code != expected { d(&format!("bad put response: {:?}", status_code)); - future::err(HyperError::Status) - } else { - future::ok(()) } - }); - - core.run(put)?; - Ok(()) + Ok(()) + }; + block_on(work) } fn get_transactions(&self, parent_uuid: &Uuid) -> Result> { - let mut core = Core::new()?; - // TODO https://github.com/mozilla/mentat/issues/569 - // let client = hyper::Client::configure() - // .connector(hyper_tls::HttpsConnector::new(4, &core.handle()).unwrap()) - // .build(&core.handle()); - let client = hyper::Client::new(&core.handle()); + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, Body>(https); d(&format!("client")); - let uri = format!("{}/transactions?from={}", self.bound_base_uri(), parent_uuid); + let uri = format!( + "{}/transactions?from={}", + self.bound_base_uri(), + parent_uuid + ); let uri = uri.parse()?; - d(&format!("parsed uri {:?}", uri)); + d(&format!("GET {:?}", uri)); - let work = client.get(uri).and_then(|res| { - println!("Response: {}", res.status()); + let work = async { + let res = client.get(uri).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + dbg!("response.status: {}", res.status()); - res.body().concat2().and_then(move |body| { - let json: SerializedTransactions = serde_json::from_slice(&body).map_err(|e| { - std::io::Error::new(std::io::ErrorKind::Other, e) - })?; - Ok(json) - }) - }); - - d(&format!("running...")); - - let transactions_json = core.run(work)?; - d(&format!("got transactions: {:?}", &transactions_json.transactions)); - Ok(transactions_json.transactions) + let body_bytes = body::to_bytes(res.into_body()).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + let body = + String::from_utf8(body_bytes.to_vec()).expect("response was not valid utf-8"); + let json: SerializedTransactions = serde_json::from_str(&body) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + d(&format!("got transactions: {:?}", &json.transactions)); + Ok(json.transactions) + }; + block_on(work) } fn get_chunks(&self, transaction_uuid: &Uuid) -> Result> { - let mut core = Core::new()?; - // TODO https://github.com/mozilla/mentat/issues/569 - // let client = hyper::Client::configure() - // .connector(hyper_tls::HttpsConnector::new(4, &core.handle()).unwrap()) - // .build(&core.handle()); - let client = hyper::Client::new(&core.handle()); + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, Body>(https); d(&format!("client")); - let uri = format!("{}/transactions/{}", self.bound_base_uri(), transaction_uuid); + let uri = format!( + "{}/transactions/{}", + self.bound_base_uri(), + transaction_uuid + ); let uri = uri.parse()?; - d(&format!("parsed uri {:?}", uri)); + d(&format!("GET {:?}", uri)); - let work = client.get(uri).and_then(|res| { - println!("Response: {}", res.status()); + let work = async { + let res = client.get(uri).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + dbg!("response.status: {}", res.status()); - res.body().concat2().and_then(move |body| { - let json: DeserializableTransaction = serde_json::from_slice(&body).map_err(|e| { - std::io::Error::new(std::io::ErrorKind::Other, e) - })?; - Ok(json) - }) - }); + let body_bytes = body::to_bytes(res.into_body()).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + let body = + String::from_utf8(body_bytes.to_vec()).expect("response was not valid utf-8"); + let json: DeserializableTransaction = serde_json::from_str(&body) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; - d(&format!("running...")); - - let transaction_json = core.run(work)?; - d(&format!("got transaction chunks: {:?}", &transaction_json.chunks)); - Ok(transaction_json.chunks) + d(&format!("got transaction chunks: {:?}", &json.chunks)); + Ok(json.chunks) + }; + block_on(work) } fn get_chunk(&self, chunk_uuid: &Uuid) -> Result { - let mut core = Core::new()?; - // TODO https://github.com/mozilla/mentat/issues/569 - // let client = hyper::Client::configure() - // .connector(hyper_tls::HttpsConnector::new(4, &core.handle()).unwrap()) - // .build(&core.handle()); - let client = hyper::Client::new(&core.handle()); + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, Body>(https); d(&format!("client")); let uri = format!("{}/chunks/{}", self.bound_base_uri(), chunk_uuid); let uri = uri.parse()?; - d(&format!("parsed uri {:?}", uri)); + d(&format!("GET {:?}", uri)); - let work = client.get(uri).and_then(|res| { - println!("Response: {}", res.status()); + let work = async { + let res = client.get(uri).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + dbg!("response.status: {}", res.status()); - res.body().concat2().and_then(move |body| { - let json: TxPart = serde_json::from_slice(&body).map_err(|e| { - std::io::Error::new(std::io::ErrorKind::Other, e) - })?; - Ok(json) - }) - }); - - d(&format!("running...")); - - let chunk = core.run(work)?; - d(&format!("got transaction chunk: {:?}", &chunk)); - Ok(chunk) + let body_bytes = body::to_bytes(res.into_body()).await.unwrap(); // TODO use '?' fix From hyper::Error to MentatError; + let body = + String::from_utf8(body_bytes.to_vec()).expect("response was not valid utf-8"); + let json: TxPart = serde_json::from_str(&body) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + d(&format!("got transaction chunk: {:?}", &json)); + Ok(json) + }; + block_on(work) } } @@ -264,14 +221,12 @@ impl GlobalTransactionLog for RemoteClient { fn set_head(&mut self, uuid: &Uuid) -> Result<()> { // {"head": uuid} - let head = SerializedHead { - head: uuid.clone() - }; + let head = SerializedHead { head: uuid.clone() }; let uri = format!("{}/head", self.bound_base_uri()); let json = serde_json::to_string(&head)?; d(&format!("serialized head: {:?}", json)); - self.put(uri, json, StatusCode::NoContent) + self.put(uri, json, StatusCode::NO_CONTENT) } /// Slurp transactions and datoms after `tx`, returning them as owned data. @@ -295,7 +250,7 @@ impl GlobalTransactionLog for RemoteClient { tx_list.push(Tx { tx: tx.into(), - parts: tx_parts + parts: tx_parts, }); } @@ -304,17 +259,26 @@ impl GlobalTransactionLog for RemoteClient { Ok(tx_list) } - fn put_transaction(&mut self, transaction_uuid: &Uuid, parent_uuid: &Uuid, chunks: &Vec) -> Result<()> { + fn put_transaction( + &mut self, + transaction_uuid: &Uuid, + parent_uuid: &Uuid, + chunks: &Vec, + ) -> Result<()> { // {"parent": uuid, "chunks": [chunk1, chunk2...]} let transaction = SerializedTransaction { parent: parent_uuid, - chunks: chunks + chunks: chunks, }; - let uri = format!("{}/transactions/{}", self.bound_base_uri(), transaction_uuid); + let uri = format!( + "{}/transactions/{}", + self.bound_base_uri(), + transaction_uuid + ); let json = serde_json::to_string(&transaction)?; d(&format!("serialized transaction: {:?}", json)); - self.put(uri, json, StatusCode::Created) + self.put(uri, json, StatusCode::CREATED) } fn put_chunk(&mut self, chunk_uuid: &Uuid, payload: &TxPart) -> Result<()> { @@ -322,7 +286,7 @@ impl GlobalTransactionLog for RemoteClient { let uri = format!("{}/chunks/{}", self.bound_base_uri(), chunk_uuid); d(&format!("serialized chunk: {:?}", payload)); // TODO don't want to clone every datom! - self.put(uri, payload, StatusCode::Created) + self.put(uri, payload, StatusCode::CREATED) } } @@ -336,6 +300,9 @@ mod tests { let user_uuid = Uuid::from_str(&"316ea470-ce35-4adf-9c61-e0de6e289c59").expect("uuid"); let server_uri = String::from("https://example.com/api/0.1"); let remote_client = RemoteClient::new(server_uri, user_uuid); - assert_eq!("https://example.com/api/0.1/316ea470-ce35-4adf-9c61-e0de6e289c59", remote_client.bound_base_uri()); + assert_eq!( + "https://example.com/api/0.1/316ea470-ce35-4adf-9c61-e0de6e289c59", + remote_client.bound_base_uri() + ); } } diff --git a/tolstoy/src/schema.rs b/tolstoy/src/schema.rs index f7856689..8773fb6d 100644 --- a/tolstoy/src/schema.rs +++ b/tolstoy/src/schema.rs @@ -58,7 +58,7 @@ pub mod tests { use super::*; use uuid::Uuid; - use metadata::{PartitionsTable, SyncMetadata}; + use crate::metadata::{PartitionsTable, SyncMetadata}; use mentat_db::USER0; @@ -162,7 +162,7 @@ pub mod tests { let mut values_iter = stmt .query_map(rusqlite::params![], |r| { let raw_uuid: Vec = r.get(0).unwrap(); - Ok(Uuid::from_bytes(raw_uuid.as_slice())) + Ok(Uuid::from_slice(raw_uuid.as_slice())) }) .expect("query works"); diff --git a/tolstoy/src/syncer.rs b/tolstoy/src/syncer.rs index f7577b22..080f2866 100644 --- a/tolstoy/src/syncer.rs +++ b/tolstoy/src/syncer.rs @@ -15,79 +15,34 @@ use std::collections::HashSet; use rusqlite; use uuid::Uuid; -use core_traits::{ - Entid, - KnownEntid, - TypedValue, -}; +use core_traits::{Entid, KnownEntid, TypedValue}; -use edn::{ - PlainSymbol, -}; -use edn::entities::{ - TxFunction, - EntityPlace, - LookupRef, -}; -use mentat_db::{ - CORE_SCHEMA_VERSION, - timelines, - entids, - PartitionMap, -}; -use mentat_transaction::{ - InProgress, - TermBuilder, - Queryable, -}; +use edn::entities::{EntityPlace, LookupRef, TxFunction}; +use edn::PlainSymbol; +use mentat_db::{entids, timelines, PartitionMap, CORE_SCHEMA_VERSION}; +use mentat_transaction::{InProgress, Queryable, TermBuilder}; -use mentat_transaction::entity_builder::{ - BuildTerms, -}; +use mentat_transaction::entity_builder::BuildTerms; -use mentat_transaction::query::{ - QueryInputs, - Variable, -}; +use mentat_transaction::query::{QueryInputs, Variable}; -use bootstrap::{ - BootstrapHelper, -}; +use crate::bootstrap::BootstrapHelper; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use tolstoy_traits::errors::{ - TolstoyError, -}; -use metadata::{ - PartitionsTable, - SyncMetadata, -}; -use schema::{ - ensure_current_version, -}; -use tx_uploader::TxUploader; -use tx_processor::{ - Processor, - TxReceiver, -}; -use tx_mapper::{ - TxMapper, -}; -use types::{ - LocalTx, - Tx, - TxPart, - GlobalTransactionLog, -}; +use crate::metadata::{PartitionsTable, SyncMetadata}; +use crate::schema::ensure_current_version; +use crate::tx_mapper::TxMapper; +use crate::tx_processor::{Processor, TxReceiver}; +use crate::tx_uploader::TxUploader; +use crate::types::{GlobalTransactionLog, LocalTx, Tx, TxPart}; +use tolstoy_traits::errors::TolstoyError; -use logger::d; +use crate::logger::d; pub struct Syncer {} -#[derive(Debug,PartialEq,Clone)] +#[derive(Debug, PartialEq, Clone)] pub enum SyncFollowup { None, FullSync, @@ -102,7 +57,7 @@ impl fmt::Display for SyncFollowup { } } -#[derive(Debug,PartialEq,Clone)] +#[derive(Debug, PartialEq, Clone)] pub enum SyncReport { IncompatibleRemoteBootstrap(i64, i64), BadRemoteState(String), @@ -120,24 +75,20 @@ pub enum SyncResult { impl fmt::Display for SyncReport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - SyncReport::IncompatibleRemoteBootstrap(local, remote) => { - write!(f, "Incompatible remote bootstrap transaction version. Local: {}, remote: {}.", local, remote) - }, - SyncReport::BadRemoteState(err) => { - write!(f, "Bad remote state: {}", err) - }, - SyncReport::NoChanges => { - write!(f, "Neither local nor remote have any new changes") - }, - SyncReport::RemoteFastForward => { - write!(f, "Fast-forwarded remote") - }, - SyncReport::LocalFastForward => { - write!(f, "Fast-forwarded local") - }, - SyncReport::Merge(follow_up) => { - write!(f, "Merged local and remote, requesting a follow-up: {}", follow_up) - } + SyncReport::IncompatibleRemoteBootstrap(local, remote) => write!( + f, + "Incompatible remote bootstrap transaction version. Local: {}, remote: {}.", + local, remote + ), + SyncReport::BadRemoteState(err) => write!(f, "Bad remote state: {}", err), + SyncReport::NoChanges => write!(f, "Neither local nor remote have any new changes"), + SyncReport::RemoteFastForward => write!(f, "Fast-forwarded remote"), + SyncReport::LocalFastForward => write!(f, "Fast-forwarded local"), + SyncReport::Merge(follow_up) => write!( + f, + "Merged local and remote, requesting a follow-up: {}", + follow_up + ), } } } @@ -157,7 +108,7 @@ impl fmt::Display for SyncResult { } } -#[derive(Debug,PartialEq)] +#[derive(Debug, PartialEq)] enum SyncAction { NoOp, // TODO this is the same as remote fast-forward from local root. @@ -209,7 +160,7 @@ impl From> for LocalDataState { fn from(mapped_local_head: Option) -> LocalDataState { match mapped_local_head { Some(_) => LocalDataState::Unchanged, - None => LocalDataState::Changed + None => LocalDataState::Changed, } } } @@ -221,18 +172,18 @@ pub struct LocalTxSet { impl LocalTxSet { pub fn new() -> LocalTxSet { - LocalTxSet { - txs: vec![] - } + LocalTxSet { txs: vec![] } } } impl TxReceiver> for LocalTxSet { fn tx(&mut self, tx_id: Entid, datoms: &mut T) -> Result<()> - where T: Iterator { + where + T: Iterator, + { self.txs.push(LocalTx { tx: tx_id, - parts: datoms.collect() + parts: datoms.collect(), }); Ok(()) } @@ -246,41 +197,33 @@ impl Syncer { /// Produces a SyncAction based on local and remote states. fn what_do(remote_state: RemoteDataState, local_state: LocalDataState) -> SyncAction { match remote_state { - RemoteDataState::Empty => { - SyncAction::PopulateRemote + RemoteDataState::Empty => SyncAction::PopulateRemote, + + RemoteDataState::Changed => match local_state { + LocalDataState::Changed => SyncAction::CombineChanges, + + LocalDataState::Unchanged => SyncAction::LocalFastForward, }, - RemoteDataState::Changed => { - match local_state { - LocalDataState::Changed => { - SyncAction::CombineChanges - }, + RemoteDataState::Unchanged => match local_state { + LocalDataState::Changed => SyncAction::RemoteFastForward, - LocalDataState::Unchanged => { - SyncAction::LocalFastForward - }, - } - }, - - RemoteDataState::Unchanged => { - match local_state { - LocalDataState::Changed => { - SyncAction::RemoteFastForward - }, - - LocalDataState::Unchanged => { - SyncAction::NoOp - }, - } + LocalDataState::Unchanged => SyncAction::NoOp, }, } } /// Upload local txs: (from_tx, HEAD]. Remote head is necessary here because we need to specify /// "parent" for each transaction we'll upload; remote head will be first transaction's parent. - fn fast_forward_remote(db_tx: &mut rusqlite::Transaction, from_tx: Option, remote_client: &mut R, remote_head: &Uuid) -> Result<()> - where R: GlobalTransactionLog { - + fn fast_forward_remote( + db_tx: &mut rusqlite::Transaction, + from_tx: Option, + remote_client: &mut R, + remote_head: &Uuid, + ) -> Result<()> + where + R: GlobalTransactionLog, + { // TODO consider moving head manipulations into uploader? let report; @@ -291,7 +234,7 @@ impl Syncer { let uploader = TxUploader::new( remote_client, remote_head, - SyncMetadata::get_partitions(db_tx, PartitionsTable::Tolstoy)? + SyncMetadata::get_partitions(db_tx, PartitionsTable::Tolstoy)?, ); // Walk the local transactions in the database and upload them. report = Processor::process(db_tx, from_tx, uploader)?; @@ -306,7 +249,11 @@ impl Syncer { // - update our local "remote head". TxMapper::set_lg_mappings( db_tx, - report.temp_uuids.iter().map(|v| (*v.0, v.1).into()).collect() + report + .temp_uuids + .iter() + .map(|v| (*v.0, v.1).into()) + .collect(), )?; SyncMetadata::set_remote_head(db_tx, &last_tx_uploaded)?; @@ -318,7 +265,7 @@ impl Syncer { fn local_tx_for_uuid(db_tx: &rusqlite::Transaction, uuid: &Uuid) -> Result { match TxMapper::get_tx_for_uuid(db_tx, uuid)? { Some(t) => Ok(t), - None => bail!(TolstoyError::TxIncorrectlyMapped(0)) + None => bail!(TolstoyError::TxIncorrectlyMapped(0)), } } @@ -333,7 +280,10 @@ impl Syncer { // Transactor knows how to pick out a txInstant value out of these // assertions and use that value for the generated transaction's txInstant. if part.a == entids::DB_TX_INSTANT { - e = EntityPlace::TxFunction(TxFunction { op: PlainSymbol("transaction-tx".to_string()) } ).into(); + e = EntityPlace::TxFunction(TxFunction { + op: PlainSymbol("transaction-tx".to_string()), + }) + .into(); } else { e = KnownEntid(part.e).into(); } @@ -363,11 +313,16 @@ impl Syncer { tx_part.set_next_entid(next_entid); Ok(()) } else { - bail!(TolstoyError::BadRemoteState("Missing tx partition in an incoming transaction".to_string())); + bail!(TolstoyError::BadRemoteState( + "Missing tx partition in an incoming transaction".to_string() + )); } } - fn fast_forward_local<'a, 'c>(in_progress: &mut InProgress<'a, 'c>, txs: Vec) -> Result { + fn fast_forward_local<'a, 'c>( + in_progress: &mut InProgress<'a, 'c>, + txs: Vec, + ) -> Result { let mut last_tx = None; for tx in txs { @@ -379,7 +334,11 @@ impl Syncer { // See notes in 'merge' for why we're doing this stuff. let mut partition_map = match tx.parts[0].partitions.clone() { Some(parts) => parts, - None => return Ok(SyncReport::BadRemoteState("Missing partition map in incoming transaction".to_string())) + None => { + return Ok(SyncReport::BadRemoteState( + "Missing partition map in incoming transaction".to_string(), + )) + } }; // Make space in the provided tx partition for the transaction we're about to create. @@ -396,13 +355,20 @@ impl Syncer { // We've just transacted a new tx, and generated a new tx entid. Map it to the corresponding // incoming tx uuid, advance our "locally known remote head". if let Some((entid, uuid)) = last_tx { - SyncMetadata::set_remote_head_and_map(&mut in_progress.transaction, (entid, &uuid).into())?; + SyncMetadata::set_remote_head_and_map( + &mut in_progress.transaction, + (entid, &uuid).into(), + )?; } Ok(SyncReport::LocalFastForward) } - fn merge(ip: &mut InProgress, incoming_txs: Vec, mut local_txs_to_merge: Vec) -> Result { + fn merge( + ip: &mut InProgress, + incoming_txs: Vec, + mut local_txs_to_merge: Vec, + ) -> Result { d(&format!("Rewinding local transactions.")); // 1) Rewind local to shared root. @@ -415,11 +381,11 @@ impl Syncer { local_txs_to_merge[0].tx.., // A poor man's parent reference. This might be brittle, although // excisions are prohibited in the 'tx' partition, so this should hold... - local_txs_to_merge[0].tx - 1 + local_txs_to_merge[0].tx - 1, )?; match new_schema { Some(schema) => ip.schema = schema, - None => () + None => (), }; ip.partition_map = new_partition_map; @@ -433,7 +399,11 @@ impl Syncer { let partition_map = match remote_tx.parts[0].partitions.clone() { Some(parts) => parts, - None => return Ok(SyncReport::BadRemoteState("Missing partition map in incoming transaction".to_string())) + None => { + return Ok(SyncReport::BadRemoteState( + "Missing partition map in incoming transaction".to_string(), + )) + } }; Syncer::remote_parts_to_builder(&mut builder, remote_tx.parts)?; @@ -509,10 +479,12 @@ impl Syncer { } else { might_alter_installed_attributes.insert(part.e); } - }, - _ => panic!("programming error: wrong value type for a local ident") + } + _ => panic!("programming error: wrong value type for a local ident"), } - } else if entids::is_a_schema_attribute(part.a) && !will_not_alter_installed_attribute.contains(&part.e) { + } else if entids::is_a_schema_attribute(part.a) + && !will_not_alter_installed_attribute.contains(&part.e) + { might_alter_installed_attributes.insert(part.e); } } @@ -532,7 +504,7 @@ impl Syncer { if part.added { entids_that_will_allocate.insert(part.e); } - }, + } } } @@ -607,19 +579,19 @@ impl Syncer { if attributes.unique.is_none() { continue; } - }, - None => panic!("programming error: missing attribute map for a known attribute") + } + None => panic!( + "programming error: missing attribute map for a known attribute" + ), } // TODO prepare a query and re-use it for all retractions of this type let pre_lookup = ip.q_once( "[:find ?e . :in ?a ?v :where [?e ?a ?v]]", - QueryInputs::with_value_sequence( - vec![ - (Variable::from_valid_name("?a"), a.into()), - (Variable::from_valid_name("?v"), v.clone()), - ] - ) + QueryInputs::with_value_sequence(vec![ + (Variable::from_valid_name("?a"), a.into()), + (Variable::from_valid_name("?v"), v.clone()), + ]), )?; if pre_lookup.is_empty() { @@ -629,7 +601,12 @@ impl Syncer { // TODO just use the value from the query instead of doing _another_ lookup-ref! builder.retract( - EntityPlace::LookupRef(LookupRef {a: a.into(), v: v.clone()}), a, v + EntityPlace::LookupRef(LookupRef { + a: a.into(), + v: v.clone(), + }), + a, + v, )?; } } @@ -643,7 +620,10 @@ impl Syncer { d(&format!("Savepoint before transacting a local tx...")); ip.savepoint("speculative_local")?; - d(&format!("Transacting builder filled with local txs... {:?}", builder)); + d(&format!( + "Transacting builder filled with local txs... {:?}", + builder + )); let report = ip.transact_builder(builder)?; @@ -656,11 +636,17 @@ impl Syncer { for e in might_alter_installed_attributes.iter() { match report.tempids.get(&format!("{}", e)) { Some(resolved_e) => { - if SyncMetadata::has_entity_assertions_in_tx(&ip.transaction, *resolved_e, report.tx_id)? { - bail!(TolstoyError::NotYetImplemented("Can't sync with schema alterations yet.".to_string())); + if SyncMetadata::has_entity_assertions_in_tx( + &ip.transaction, + *resolved_e, + report.tx_id, + )? { + bail!(TolstoyError::NotYetImplemented( + "Can't sync with schema alterations yet.".to_string() + )); } - }, - None => () + } + None => (), } } @@ -669,7 +655,10 @@ impl Syncer { clean_rebase = false; ip.release_savepoint("speculative_local")?; } else { - d(&format!("Applied tx {} as a no-op. Rolling back the savepoint (empty tx clean-up).", report.tx_id)); + d(&format!( + "Applied tx {} as a no-op. Rolling back the savepoint (empty tx clean-up).", + report.tx_id + )); ip.rollback_savepoint("speculative_local")?; } } @@ -692,15 +681,24 @@ impl Syncer { } } - fn first_sync_against_non_empty(ip: &mut InProgress, remote_client: &R, local_metadata: &SyncMetadata) -> Result - where R: GlobalTransactionLog { - - d(&format!("remote non-empty on first sync, adopting remote state.")); + fn first_sync_against_non_empty( + ip: &mut InProgress, + remote_client: &R, + local_metadata: &SyncMetadata, + ) -> Result + where + R: GlobalTransactionLog, + { + d(&format!( + "remote non-empty on first sync, adopting remote state." + )); // 1) Download remote transactions. let incoming_txs = remote_client.transactions_after(&Uuid::nil())?; if incoming_txs.len() == 0 { - return Ok(SyncReport::BadRemoteState("Remote specified non-root HEAD but gave no transactions".to_string())); + return Ok(SyncReport::BadRemoteState( + "Remote specified non-root HEAD but gave no transactions".to_string(), + )); } // 2) Process remote bootstrap. @@ -709,14 +707,23 @@ impl Syncer { let bootstrap_helper = BootstrapHelper::new(remote_bootstrap); if !bootstrap_helper.is_compatible()? { - return Ok(SyncReport::IncompatibleRemoteBootstrap(CORE_SCHEMA_VERSION as i64, bootstrap_helper.core_schema_version()?)); + return Ok(SyncReport::IncompatibleRemoteBootstrap( + CORE_SCHEMA_VERSION as i64, + bootstrap_helper.core_schema_version()?, + )); } - d(&format!("mapping incoming bootstrap tx uuid to local bootstrap entid: {} -> {}", remote_bootstrap.tx, local_bootstrap)); + d(&format!( + "mapping incoming bootstrap tx uuid to local bootstrap entid: {} -> {}", + remote_bootstrap.tx, local_bootstrap + )); // Map incoming bootstrap tx uuid to local bootstrap entid. // If there's more work to do, we'll move the head again. - SyncMetadata::set_remote_head_and_map(&mut ip.transaction, (local_bootstrap, &remote_bootstrap.tx).into())?; + SyncMetadata::set_remote_head_and_map( + &mut ip.transaction, + (local_bootstrap, &remote_bootstrap.tx).into(), + )?; // 3) Determine new local and remote data states, now that bootstrap has been dealt with. let remote_state = if incoming_txs.len() > 1 { @@ -766,8 +773,9 @@ impl Syncer { } pub fn sync(ip: &mut InProgress, remote_client: &mut R) -> Result - where R: GlobalTransactionLog { - + where + R: GlobalTransactionLog, + { d(&format!("sync flowing")); ensure_current_version(&mut ip.transaction)?; @@ -794,19 +802,23 @@ impl Syncer { SyncAction::NoOp => { d(&format!("local HEAD did not move. Nothing to do!")); Ok(SyncReport::NoChanges) - }, + } SyncAction::PopulateRemote => { d(&format!("empty remote!")); - Syncer::fast_forward_remote(&mut ip.transaction, None, remote_client, &remote_head)?; + Syncer::fast_forward_remote( + &mut ip.transaction, + None, + remote_client, + &remote_head, + )?; Ok(SyncReport::RemoteFastForward) - }, + } SyncAction::RemoteFastForward => { d(&format!("local HEAD moved.")); - let upload_from_tx = Syncer::local_tx_for_uuid( - &mut ip.transaction, &locally_known_remote_head - )?; + let upload_from_tx = + Syncer::local_tx_for_uuid(&mut ip.transaction, &locally_known_remote_head)?; d(&format!("Fast-forwarding the remote.")); @@ -814,30 +826,32 @@ impl Syncer { // but failed to advance our own local head. If that's the case, and we can recognize it, // our sync becomes just bumping our local head. AFAICT below would currently fail. Syncer::fast_forward_remote( - &mut ip.transaction, Some(upload_from_tx), remote_client, &remote_head + &mut ip.transaction, + Some(upload_from_tx), + remote_client, + &remote_head, )?; Ok(SyncReport::RemoteFastForward) - }, + } SyncAction::LocalFastForward => { d(&format!("fast-forwarding local store.")); Syncer::fast_forward_local( ip, - remote_client.transactions_after(&locally_known_remote_head)? + remote_client.transactions_after(&locally_known_remote_head)?, )?; Ok(SyncReport::LocalFastForward) - }, + } SyncAction::CombineChanges => { d(&format!("combining changes from local and remote stores.")); // Get the starting point for out local set of txs to merge. - let combine_local_from_tx = Syncer::local_tx_for_uuid( - &mut ip.transaction, &locally_known_remote_head - )?; + let combine_local_from_tx = + Syncer::local_tx_for_uuid(&mut ip.transaction, &locally_known_remote_head)?; let local_txs = Processor::process( &mut ip.transaction, Some(combine_local_from_tx), - LocalTxSet::new() + LocalTxSet::new(), )?; // Merge! Syncer::merge( @@ -845,9 +859,9 @@ impl Syncer { // Remote txs to merge... remote_client.transactions_after(&locally_known_remote_head)?, // ... with the local txs. - local_txs + local_txs, ) - }, + } } } } @@ -858,13 +872,31 @@ mod tests { #[test] fn test_what_do() { - assert_eq!(SyncAction::PopulateRemote, Syncer::what_do(RemoteDataState::Empty, LocalDataState::Unchanged)); - assert_eq!(SyncAction::PopulateRemote, Syncer::what_do(RemoteDataState::Empty, LocalDataState::Changed)); + assert_eq!( + SyncAction::PopulateRemote, + Syncer::what_do(RemoteDataState::Empty, LocalDataState::Unchanged) + ); + assert_eq!( + SyncAction::PopulateRemote, + Syncer::what_do(RemoteDataState::Empty, LocalDataState::Changed) + ); - assert_eq!(SyncAction::NoOp, Syncer::what_do(RemoteDataState::Unchanged, LocalDataState::Unchanged)); - assert_eq!(SyncAction::RemoteFastForward, Syncer::what_do(RemoteDataState::Unchanged, LocalDataState::Changed)); + assert_eq!( + SyncAction::NoOp, + Syncer::what_do(RemoteDataState::Unchanged, LocalDataState::Unchanged) + ); + assert_eq!( + SyncAction::RemoteFastForward, + Syncer::what_do(RemoteDataState::Unchanged, LocalDataState::Changed) + ); - assert_eq!(SyncAction::LocalFastForward, Syncer::what_do(RemoteDataState::Changed, LocalDataState::Unchanged)); - assert_eq!(SyncAction::CombineChanges, Syncer::what_do(RemoteDataState::Changed, LocalDataState::Changed)); + assert_eq!( + SyncAction::LocalFastForward, + Syncer::what_do(RemoteDataState::Changed, LocalDataState::Unchanged) + ); + assert_eq!( + SyncAction::CombineChanges, + Syncer::what_do(RemoteDataState::Changed, LocalDataState::Changed) + ); } } diff --git a/tolstoy/src/tx_mapper.rs b/tolstoy/src/tx_mapper.rs index 4788f5b0..57f69e1f 100644 --- a/tolstoy/src/tx_mapper.rs +++ b/tolstoy/src/tx_mapper.rs @@ -9,32 +9,29 @@ // specific language governing permissions and limitations under the License. use rusqlite; + +use std::convert::TryInto; + use uuid::Uuid; -use core_traits::{ - Entid, -}; +use core_traits::Entid; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use tolstoy_traits::errors::{ - TolstoyError, -}; +use tolstoy_traits::errors::TolstoyError; -use types::{ - LocalGlobalTxMapping, -}; +use crate::types::LocalGlobalTxMapping; // Exposes a tx<->uuid mapping interface. pub struct TxMapper {} impl TxMapper { - pub fn set_lg_mappings(db_tx: &mut rusqlite::Transaction, mappings: Vec) -> Result<()> { - let mut stmt = db_tx.prepare_cached( - "INSERT OR REPLACE INTO tolstoy_tu (tx, uuid) VALUES (?, ?)" - )?; + pub fn set_lg_mappings( + db_tx: &mut rusqlite::Transaction, + mappings: Vec, + ) -> Result<()> { + let mut stmt = + db_tx.prepare_cached("INSERT OR REPLACE INTO tolstoy_tu (tx, uuid) VALUES (?, ?)")?; for mapping in mappings.iter() { let uuid_bytes = mapping.remote.as_bytes().to_vec(); stmt.execute(rusqlite::params![&mapping.local, &uuid_bytes])?; @@ -42,7 +39,10 @@ impl TxMapper { Ok(()) } - pub fn set_lg_mapping(db_tx: &mut rusqlite::Transaction, mapping: LocalGlobalTxMapping) -> Result<()> { + pub fn set_lg_mapping( + db_tx: &mut rusqlite::Transaction, + mapping: LocalGlobalTxMapping, + ) -> Result<()> { TxMapper::set_lg_mappings(db_tx, vec![mapping]) } @@ -53,16 +53,17 @@ impl TxMapper { None => { let uuid = Uuid::new_v4(); let uuid_bytes = uuid.as_bytes().to_vec(); - db_tx.execute("INSERT INTO tolstoy_tu (tx, uuid) VALUES (?, ?)", rusqlite::params![&tx, &uuid_bytes])?; + db_tx.execute( + "INSERT INTO tolstoy_tu (tx, uuid) VALUES (?, ?)", + rusqlite::params![&tx, &uuid_bytes], + )?; return Ok(uuid); } } } pub fn get_tx_for_uuid(db_tx: &rusqlite::Transaction, uuid: &Uuid) -> Result> { - let mut stmt = db_tx.prepare_cached( - "SELECT tx FROM tolstoy_tu WHERE uuid = ?" - )?; + let mut stmt = db_tx.prepare_cached("SELECT tx FROM tolstoy_tu WHERE uuid = ?")?; let uuid_bytes = uuid.as_bytes().to_vec(); let results = stmt.query_map(&[&uuid_bytes], |r| r.get(0))?; @@ -78,13 +79,11 @@ impl TxMapper { } pub fn get(db_tx: &rusqlite::Transaction, tx: Entid) -> Result> { - let mut stmt = db_tx.prepare_cached( - "SELECT uuid FROM tolstoy_tu WHERE tx = ?" - )?; + let mut stmt = db_tx.prepare_cached("SELECT uuid FROM tolstoy_tu WHERE tx = ?")?; - let results = stmt.query_and_then(&[&tx], |r| -> Result{ + let results = stmt.query_and_then(&[&tx], |r| -> Result { let bytes: Vec = r.get(0).unwrap(); - Uuid::from_bytes(bytes.as_slice()).map_err(|e| e.into()) + Ok(Uuid::from_bytes(bytes.as_slice().try_into().unwrap())) })?; let mut uuids = vec![]; @@ -101,7 +100,7 @@ impl TxMapper { #[cfg(test)] pub mod tests { use super::*; - use schema; + use crate::schema; #[test] fn test_getters() { @@ -116,27 +115,22 @@ pub mod tests { fn test_bulk_setter() { let mut conn = schema::tests::setup_conn_bare(); let mut tx = schema::tests::setup_tx(&mut conn); - TxMapper::set_lg_mappings(&mut tx, vec![]).expect("empty map success"); let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); - TxMapper::set_lg_mappings( - &mut tx, - vec![(1, &uuid1).into(), (2, &uuid2).into()] - ).expect("map success"); + TxMapper::set_lg_mappings(&mut tx, vec![(1, &uuid1).into(), (2, &uuid2).into()]) + .expect("map success"); assert_eq!(Some(uuid1), TxMapper::get(&mut tx, 1).expect("success")); assert_eq!(Some(uuid2), TxMapper::get(&mut tx, 2).expect("success")); // Now let's replace one of the mappings. let new_uuid2 = Uuid::new_v4(); - TxMapper::set_lg_mappings( - &mut tx, - vec![(1, &uuid1).into(), (2, &new_uuid2).into()] - ).expect("map success"); + TxMapper::set_lg_mappings(&mut tx, vec![(1, &uuid1).into(), (2, &new_uuid2).into()]) + .expect("map success"); assert_eq!(Some(uuid1), TxMapper::get(&mut tx, 1).expect("success")); assert_eq!(Some(new_uuid2), TxMapper::get(&mut tx, 2).expect("success")); } diff --git a/tolstoy/src/tx_processor.rs b/tolstoy/src/tx_processor.rs index b66e9322..d8d8d8d7 100644 --- a/tolstoy/src/tx_processor.rs +++ b/tolstoy/src/tx_processor.rs @@ -11,28 +11,19 @@ use std::iter::Peekable; use rusqlite; -use mentat_db::{ - TypedSQLValue, -}; +use mentat_db::TypedSQLValue; -use core_traits::{ - Entid, - TypedValue, -}; +use core_traits::{Entid, TypedValue}; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use types::{ - TxPart, -}; +use crate::types::TxPart; /// Implementors must specify type of the "receiver report" which /// they will produce once processor is finished. pub trait TxReceiver { /// Called for each transaction, with an iterator over its datoms. - fn tx>(&mut self, tx_id: Entid, d: &mut T) -> Result<()>; + fn tx>(&mut self, tx_id: Entid, d: &mut T) -> Result<()>; /// Called once processor is finished, consuming this receiver and producing a report. fn done(self) -> RR; } @@ -40,7 +31,9 @@ pub trait TxReceiver { pub struct Processor {} pub struct DatomsIterator<'dbtx, 't, T> -where T: Sized + Iterator> + 't { +where + T: Sized + Iterator> + 't, +{ at_first: bool, at_last: bool, first: &'dbtx TxPart, @@ -48,9 +41,10 @@ where T: Sized + Iterator> + 't { } impl<'dbtx, 't, T> DatomsIterator<'dbtx, 't, T> -where T: Sized + Iterator> + 't { - fn new(first: &'dbtx TxPart, rows: &'t mut Peekable) -> DatomsIterator<'dbtx, 't, T> - { +where + T: Sized + Iterator> + 't, +{ + fn new(first: &'dbtx TxPart, rows: &'t mut Peekable) -> DatomsIterator<'dbtx, 't, T> { DatomsIterator { at_first: true, at_last: false, @@ -61,7 +55,9 @@ where T: Sized + Iterator> + 't { } impl<'dbtx, 't, T> Iterator for DatomsIterator<'dbtx, 't, T> -where T: Sized + Iterator> + 't { +where + T: Sized + Iterator> + 't, +{ type Item = TxPart; fn next(&mut self) -> Option { @@ -84,7 +80,7 @@ where T: Sized + Iterator> + 't { self.at_last = true; return None; } - }, + } // Empty, or error. Either way, this iterator's done. _ => { self.at_last = true; @@ -97,16 +93,14 @@ where T: Sized + Iterator> + 't { if let Some(result) = self.rows.next() { match result { Err(_) => None, - Ok(datom) => { - Some(TxPart { - partitions: None, - e: datom.e, - a: datom.a, - v: datom.v.clone(), - tx: datom.tx, - added: datom.added, - }) - }, + Ok(datom) => Some(TxPart { + partitions: None, + e: datom.e, + a: datom.a, + v: datom.v.clone(), + tx: datom.tx, + added: datom.added, + }), } } else { self.at_last = true; @@ -127,17 +121,24 @@ fn to_tx_part(row: &rusqlite::Row) -> Result { } impl Processor { - pub fn process> - (sqlite: &rusqlite::Transaction, from_tx: Option, mut receiver: R) -> Result { - + pub fn process>( + sqlite: &rusqlite::Transaction, + from_tx: Option, + mut receiver: R, + ) -> Result { let tx_filter = match from_tx { Some(tx) => format!(" WHERE timeline = 0 AND tx > {} ", tx), - None => format!("WHERE timeline = 0") + None => format!("WHERE timeline = 0"), }; - let select_query = format!("SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions {} ORDER BY tx", tx_filter); + let select_query = format!( + "SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions {} ORDER BY tx", + tx_filter + ); let mut stmt = sqlite.prepare(&select_query)?; - let mut rows = stmt.query_and_then(rusqlite::params![], to_tx_part)?.peekable(); + let mut rows = stmt + .query_and_then(rusqlite::params![], to_tx_part)? + .peekable(); // Walk the transaction table, keeping track of the current "tx". // Whenever "tx" changes, construct a datoms iterator and pass it to the receiver. @@ -150,18 +151,12 @@ impl Processor { Some(tx) => { if tx != datom.tx { current_tx = Some(datom.tx); - receiver.tx( - datom.tx, - &mut DatomsIterator::new(&datom, &mut rows) - )?; + receiver.tx(datom.tx, &mut DatomsIterator::new(&datom, &mut rows))?; } - }, + } None => { current_tx = Some(datom.tx); - receiver.tx( - datom.tx, - &mut DatomsIterator::new(&datom, &mut rows) - )?; + receiver.tx(datom.tx, &mut DatomsIterator::new(&datom, &mut rows))?; } } } diff --git a/tolstoy/src/tx_uploader.rs b/tolstoy/src/tx_uploader.rs index 0965c75c..92ea769b 100644 --- a/tolstoy/src/tx_uploader.rs +++ b/tolstoy/src/tx_uploader.rs @@ -12,29 +12,17 @@ use std::collections::HashMap; use uuid::Uuid; -use core_traits::{ - Entid, -}; +use core_traits::Entid; -use mentat_db::{ - PartitionMap, - V1_PARTS, -}; +use mentat_db::{PartitionMap, V1_PARTS}; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; -use tx_processor::{ - TxReceiver, -}; +use crate::tx_processor::TxReceiver; -use types::{ - TxPart, - GlobalTransactionLog, -}; +use crate::types::{GlobalTransactionLog, TxPart}; -use logger::d; +use crate::logger::d; pub struct UploaderReport { pub temp_uuids: HashMap, @@ -43,14 +31,18 @@ pub struct UploaderReport { pub(crate) struct TxUploader<'c> { tx_temp_uuids: HashMap, - remote_client: &'c mut GlobalTransactionLog, + remote_client: &'c mut dyn GlobalTransactionLog, remote_head: &'c Uuid, rolling_temp_head: Option, local_partitions: PartitionMap, } impl<'c> TxUploader<'c> { - pub fn new(client: &'c mut GlobalTransactionLog, remote_head: &'c Uuid, local_partitions: PartitionMap) -> TxUploader<'c> { + pub fn new( + client: &'c mut dyn GlobalTransactionLog, + remote_head: &'c Uuid, + local_partitions: PartitionMap, + ) -> TxUploader<'c> { TxUploader { tx_temp_uuids: HashMap::new(), remote_client: client, @@ -64,7 +56,9 @@ impl<'c> TxUploader<'c> { /// Given a set of entids and a partition map, returns a new PartitionMap that would result from /// expanding the partitions to fit the entids. fn allocate_partition_map_for_entids(entids: T, local_partitions: &PartitionMap) -> PartitionMap -where T: Iterator { +where + T: Iterator, +{ let mut parts = HashMap::new(); for name in V1_PARTS.iter().map(|&(ref part, ..)| part.to_string()) { // This shouldn't fail: locally-sourced partitions must be present within with V1_PARTS. @@ -90,7 +84,9 @@ where T: Iterator { impl<'c> TxReceiver for TxUploader<'c> { fn tx(&mut self, tx_id: Entid, datoms: &mut T) -> Result<()> - where T: Iterator { + where + T: Iterator, + { // Yes, we generate a new UUID for a given Tx, even if we might // already have one mapped locally. Pre-existing local mapping will // be replaced if this sync succeeds entirely. @@ -108,7 +104,10 @@ impl<'c> TxReceiver for TxUploader<'c> { // TODO this should live within a transaction, once server support is in place. // For now, we're uploading the PartitionMap in transaction's first chunk. - datoms[0].partitions = Some(allocate_partition_map_for_entids(datoms.iter().map(|d| d.e), &self.local_partitions)); + datoms[0].partitions = Some(allocate_partition_map_for_entids( + datoms.iter().map(|d| d.e), + &self.local_partitions, + )); // Upload all chunks. for datom in &datoms { @@ -131,8 +130,12 @@ impl<'c> TxReceiver for TxUploader<'c> { Some(p) => p, None => *self.remote_head, }; - d(&format!("putting transaction: {:?}, {:?}, {:?}", &tx_uuid, &tx_parent, &tx_chunks)); - self.remote_client.put_transaction(&tx_uuid, &tx_parent, &tx_chunks)?; + d(&format!( + "putting transaction: {:?}, {:?}, {:?}", + &tx_uuid, &tx_parent, &tx_chunks + )); + self.remote_client + .put_transaction(&tx_uuid, &tx_parent, &tx_chunks)?; d(&format!("updating rolling head: {:?}", tx_uuid)); self.rolling_temp_head = Some(tx_uuid.clone()); @@ -152,20 +155,19 @@ impl<'c> TxReceiver for TxUploader<'c> { pub mod tests { use super::*; - use mentat_db::{ - Partition, - V1_PARTS, - }; + use mentat_db::{Partition, V1_PARTS}; - use schema::{ - PARTITION_USER, - PARTITION_TX, - PARTITION_DB, - }; + use crate::schema::{PARTITION_DB, PARTITION_TX, PARTITION_USER}; fn bootstrap_partition_map() -> PartitionMap { - V1_PARTS.iter() - .map(|&(ref part, start, end, index, allow_excision)| (part.to_string(), Partition::new(start, end, index, allow_excision))) + V1_PARTS + .iter() + .map(|&(ref part, start, end, index, allow_excision)| { + ( + part.to_string(), + Partition::new(start, end, index, allow_excision), + ) + }) .collect() } diff --git a/tolstoy/src/types.rs b/tolstoy/src/types.rs index 40cc66cc..fab577fd 100644 --- a/tolstoy/src/types.rs +++ b/tolstoy/src/types.rs @@ -11,16 +11,11 @@ use std::cmp::Ordering; use uuid::Uuid; -use core_traits::{ - Entid, - TypedValue, -}; +use core_traits::{Entid, TypedValue}; use mentat_db::PartitionMap; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; pub struct LocalGlobalTxMapping<'a> { pub local: Entid, @@ -40,7 +35,7 @@ impl<'a> LocalGlobalTxMapping<'a> { pub fn new(local: Entid, remote: &'a Uuid) -> LocalGlobalTxMapping<'a> { LocalGlobalTxMapping { local: local, - remote: remote + remote: remote, } } } @@ -52,7 +47,6 @@ pub struct LocalTx { pub parts: Vec, } - impl PartialOrd for LocalTx { fn partial_cmp(&self, other: &LocalTx) -> Option { Some(self.cmp(other)) @@ -80,7 +74,7 @@ pub struct Tx { pub parts: Vec, } -#[derive(Debug,Clone,Serialize,Deserialize,PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TxPart { // TODO this is a temporary for development. Only first TxPart in a chunk series should have a non-None 'parts'. // 'parts' should actually live in a transaction, but we do this now to avoid changing the server until dust settles. @@ -96,6 +90,7 @@ pub trait GlobalTransactionLog { fn head(&self) -> Result; fn transactions_after(&self, tx: &Uuid) -> Result>; fn set_head(&mut self, tx: &Uuid) -> Result<()>; - fn put_transaction(&mut self, tx: &Uuid, parent_tx: &Uuid, chunk_txs: &Vec) -> Result<()>; + fn put_transaction(&mut self, tx: &Uuid, parent_tx: &Uuid, chunk_txs: &Vec) + -> Result<()>; fn put_chunk(&mut self, tx: &Uuid, payload: &TxPart) -> Result<()>; } diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml index 5abe174e..ef13ceb4 100644 --- a/tools/cli/Cargo.toml +++ b/tools/cli/Cargo.toml @@ -19,22 +19,22 @@ doc = false test = false [dependencies] -combine = "2.2.2" -dirs = "1.0.3" -env_logger = "0.5" -failure = "0.1.1" -failure_derive = "0.1.1" +combine = "4.0.0-beta.2" +dirs = "2.0.2" +env_logger = "0.7.1" +failure = "0.1.6" +failure_derive = "0.1.6" getopts = "0.2" -lazy_static = "0.2" -linefeed = "0.5" +lazy_static = "1.4.0" +linefeed = "0.6.0" log = "0.4" tabwriter = "1" -tempfile = "1.1" +tempfile = "3.1.0" termion = "1" -time = "0.1" +time = "0.2.2" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] [dependencies.mentat] diff --git a/tools/cli/src/mentat_cli/command_parser.rs b/tools/cli/src/mentat_cli/command_parser.rs index 40bba552..02949fcc 100644 --- a/tools/cli/src/mentat_cli/command_parser.rs +++ b/tools/cli/src/mentat_cli/command_parser.rs @@ -8,40 +8,18 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use combine::{ - Parser, - any, - eof, - look_ahead, - many1, - satisfy, - sep_end_by, - token, -}; - -use combine::char::{ - space, - spaces, - string, -}; - -use combine::combinator::{ - choice, - try, -}; +use combine::parser::char::{space, spaces, string}; +use combine::parser::combinator::attempt; +use combine::{any, choice, eof, look_ahead, many1, satisfy, sep_end_by, token, Parser}; use CliError; use edn; -use failure::{ - Compat, - Error, -}; +use failure::Error; -use mentat::{ - CacheDirection, -}; +use combine::error::StringStreamError; +use mentat::CacheDirection; pub static COMMAND_CACHE: &'static str = &"cache"; pub static COMMAND_CLOSE: &'static str = &"close"; @@ -88,46 +66,40 @@ impl Command { /// TODO: for query and transact commands, they will be considered complete if a parsable EDN has been entered as an argument pub fn is_complete(&self) -> bool { match self { - &Command::Query(ref args) | - &Command::QueryExplain(ref args) | - &Command::QueryPrepared(ref args) | - &Command::Transact(ref args) - => { - edn::parse::value(&args).is_ok() - }, - &Command::Cache(_, _) | - &Command::Close | - &Command::Exit | - &Command::Help(_) | - &Command::Import(_) | - &Command::Open(_) | - &Command::OpenEncrypted(_, _) | - &Command::Timer(_) | - &Command::Schema | - &Command::Sync(_) - => true, + &Command::Query(ref args) + | &Command::QueryExplain(ref args) + | &Command::QueryPrepared(ref args) + | &Command::Transact(ref args) => edn::parse::value(&args).is_ok(), + &Command::Cache(_, _) + | &Command::Close + | &Command::Exit + | &Command::Help(_) + | &Command::Import(_) + | &Command::Open(_) + | &Command::OpenEncrypted(_, _) + | &Command::Timer(_) + | &Command::Schema + | &Command::Sync(_) => true, } } pub fn is_timed(&self) -> bool { match self { - &Command::Import(_) | - &Command::Query(_) | - &Command::QueryPrepared(_) | - &Command::Transact(_) - => true, + &Command::Import(_) + | &Command::Query(_) + | &Command::QueryPrepared(_) + | &Command::Transact(_) => true, - &Command::Cache(_, _) | - &Command::Close | - &Command::Exit | - &Command::Help(_) | - &Command::Open(_) | - &Command::OpenEncrypted(_, _) | - &Command::QueryExplain(_) | - &Command::Timer(_) | - &Command::Schema | - &Command::Sync(_) - => false, + &Command::Cache(_, _) + | &Command::Close + | &Command::Exit + | &Command::Help(_) + | &Command::Open(_) + | &Command::OpenEncrypted(_, _) + | &Command::QueryExplain(_) + | &Command::Timer(_) + | &Command::Schema + | &Command::Sync(_) => false, } } @@ -135,72 +107,58 @@ impl Command { match self { &Command::Cache(ref attr, ref direction) => { format!(".{} {} {:?}", COMMAND_CACHE, attr, direction) - }, - &Command::Close => { - format!(".{}", COMMAND_CLOSE) - }, - &Command::Exit => { - format!(".{}", COMMAND_EXIT_LONG) - }, - &Command::Help(ref args) => { - format!(".{} {:?}", COMMAND_HELP, args) - }, - &Command::Import(ref args) => { - format!(".{} {}", COMMAND_IMPORT_LONG, args) - }, - &Command::Open(ref args) => { - format!(".{} {}", COMMAND_OPEN, args) - }, + } + &Command::Close => format!(".{}", COMMAND_CLOSE), + &Command::Exit => format!(".{}", COMMAND_EXIT_LONG), + &Command::Help(ref args) => format!(".{} {:?}", COMMAND_HELP, args), + &Command::Import(ref args) => format!(".{} {}", COMMAND_IMPORT_LONG, args), + &Command::Open(ref args) => format!(".{} {}", COMMAND_OPEN, args), &Command::OpenEncrypted(ref db, ref key) => { format!(".{} {} {}", COMMAND_OPEN_ENCRYPTED, db, key) - }, - &Command::Query(ref args) => { - format!(".{} {}", COMMAND_QUERY_LONG, args) - }, - &Command::QueryExplain(ref args) => { - format!(".{} {}", COMMAND_QUERY_EXPLAIN_LONG, args) - }, + } + &Command::Query(ref args) => format!(".{} {}", COMMAND_QUERY_LONG, args), + &Command::QueryExplain(ref args) => format!(".{} {}", COMMAND_QUERY_EXPLAIN_LONG, args), &Command::QueryPrepared(ref args) => { format!(".{} {}", COMMAND_QUERY_PREPARED_LONG, args) - }, - &Command::Schema => { - format!(".{}", COMMAND_SCHEMA) - }, - &Command::Sync(ref args) => { - format!(".{} {:?}", COMMAND_SYNC, args) - }, - &Command::Timer(on) => { - format!(".{} {}", COMMAND_TIMER_LONG, on) - }, - &Command::Transact(ref args) => { - format!(".{} {}", COMMAND_TRANSACT_LONG, args) - }, + } + &Command::Schema => format!(".{}", COMMAND_SCHEMA), + &Command::Sync(ref args) => format!(".{} {:?}", COMMAND_SYNC, args), + &Command::Timer(on) => format!(".{} {}", COMMAND_TIMER_LONG, on), + &Command::Transact(ref args) => format!(".{} {}", COMMAND_TRANSACT_LONG, args), } } } pub fn command(s: &str) -> Result { - let path = || many1::(satisfy(|c: char| !c.is_whitespace())); - let argument = || many1::(satisfy(|c: char| !c.is_whitespace())); - let arguments = || sep_end_by::, _, _>(many1(satisfy(|c: char| !c.is_whitespace())), many1::, _>(space())).expected("arguments"); + let path = || many1::(satisfy(|c: char| !c.is_whitespace())); + let argument = || many1::(satisfy(|c: char| !c.is_whitespace())); + let arguments = || { + sep_end_by::, _, _, _>( + many1(satisfy(|c: char| !c.is_whitespace())), + many1::, _, _>(space()), + ) + .expected("arguments") + }; // Helpers. - let direction_parser = || string("forward") - .map(|_| CacheDirection::Forward) - .or(string("reverse").map(|_| CacheDirection::Reverse)) - .or(string("both").map(|_| CacheDirection::Both)); + let direction_parser = || { + string("forward") + .map(|_| CacheDirection::Forward) + .or(string("reverse").map(|_| CacheDirection::Reverse)) + .or(string("both").map(|_| CacheDirection::Both)) + }; - let edn_arg_parser = || spaces() - .with(look_ahead(string("[").or(string("{"))) - .with(many1::, _>(try(any()))) - .and_then(|args| -> Result> { - Ok(args.iter().collect()) - }) - ); + let edn_arg_parser = || { + spaces().with( + look_ahead(string("[").or(string("{"))) + .with(many1::, _, _>(attempt(any()))) + .and_then(|args| -> Result { + Ok(args.iter().collect()) + }), + ) + }; - let no_arg_parser = || arguments() - .skip(spaces()) - .skip(eof()); + let no_arg_parser = || arguments().skip(spaces()).skip(eof()); let opener = |command, num_args| { string(command) @@ -208,135 +166,146 @@ pub fn command(s: &str) -> Result { .with(arguments()) .map(move |args| { if args.len() < num_args { - bail!(CliError::CommandParse("Missing required argument".to_string())); + bail!(CliError::CommandParse( + "Missing required argument".to_string() + )); } if args.len() > num_args { - bail!(CliError::CommandParse(format!("Unrecognized argument {:?}", args[num_args]))); + bail!(CliError::CommandParse(format!( + "Unrecognized argument {:?}", + args[num_args] + ))); } Ok(args) }) }; // Commands. - let cache_parser = string(COMMAND_CACHE) - .with(spaces()) - .with(argument().skip(spaces()).and(direction_parser()) - .map(|(arg, direction)| { - Ok(Command::Cache(arg, direction)) - })); + let cache_parser = string(COMMAND_CACHE).with(spaces()).with( + argument() + .skip(spaces()) + .and(direction_parser()) + .map(|(arg, direction)| Ok(Command::Cache(arg, direction))), + ); + let close_parser = string(COMMAND_CLOSE).with(no_arg_parser()).map(|args| { + if !args.is_empty() { + bail!(CliError::CommandParse(format!( + "Unrecognized argument {:?}", + args[0] + ))); + } + Ok(Command::Close) + }); - let close_parser = string(COMMAND_CLOSE) - .with(no_arg_parser()) - .map(|args| { - if !args.is_empty() { - bail!(CliError::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); - } - Ok(Command::Close) - }); + let exit_parser = attempt(string(COMMAND_EXIT_LONG)) + .or(attempt(string(COMMAND_EXIT_SHORT))) + .with(no_arg_parser()) + .map(|args| { + if !args.is_empty() { + bail!(CliError::CommandParse(format!( + "Unrecognized argument {:?}", + args[0] + ))); + } + Ok(Command::Exit) + }); - let exit_parser = try(string(COMMAND_EXIT_LONG)).or(try(string(COMMAND_EXIT_SHORT))) - .with(no_arg_parser()) - .map(|args| { - if !args.is_empty() { - bail!(CliError::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); - } - Ok(Command::Exit) - }); - - let explain_query_parser = try(string(COMMAND_QUERY_EXPLAIN_LONG)) - .or(try(string(COMMAND_QUERY_EXPLAIN_SHORT))) - .with(edn_arg_parser()) - .map(|x| { - Ok(Command::QueryExplain(x)) - }); + let explain_query_parser = attempt(string(COMMAND_QUERY_EXPLAIN_LONG)) + .or(attempt(string(COMMAND_QUERY_EXPLAIN_SHORT))) + .with(edn_arg_parser()) + .map(|x| Ok(Command::QueryExplain(x))); let help_parser = string(COMMAND_HELP) - .with(spaces()) - .with(arguments()) - .map(|args| { - Ok(Command::Help(args.clone())) - }); + .with(spaces()) + .with(arguments()) + .map(|args| Ok(Command::Help(args.clone()))); - let import_parser = try(string(COMMAND_IMPORT_LONG)).or(try(string(COMMAND_IMPORT_SHORT))) - .with(spaces()) - .with(path()) - .map(|x| { - Ok(Command::Import(x)) - }); + let import_parser = attempt(string(COMMAND_IMPORT_LONG)) + .or(attempt(string(COMMAND_IMPORT_SHORT))) + .with(spaces()) + .with(path()) + .map(|x| Ok(Command::Import(x))); - let open_parser = opener(COMMAND_OPEN, 1).map(|args_res| - args_res.map(|args| Command::Open(args[0].clone()))); + let open_parser = + opener(COMMAND_OPEN, 1).map(|args_res| args_res.map(|args| Command::Open(args[0].clone()))); - let open_encrypted_parser = opener(COMMAND_OPEN_ENCRYPTED, 2).map(|args_res| - args_res.map(|args| Command::OpenEncrypted(args[0].clone(), args[1].clone()))); + let open_encrypted_parser = opener(COMMAND_OPEN_ENCRYPTED, 2).map(|args_res| { + args_res.map(|args| Command::OpenEncrypted(args[0].clone(), args[1].clone())) + }); - let query_parser = try(string(COMMAND_QUERY_LONG)).or(try(string(COMMAND_QUERY_SHORT))) - .with(edn_arg_parser()) - .map(|x| { - Ok(Command::Query(x)) - }); + let query_parser = attempt(string(COMMAND_QUERY_LONG)) + .or(attempt(string(COMMAND_QUERY_SHORT))) + .with(edn_arg_parser()) + .map(|x| Ok(Command::Query(x))); let query_prepared_parser = string(COMMAND_QUERY_PREPARED_LONG) - .with(edn_arg_parser()) - .map(|x| { - Ok(Command::QueryPrepared(x)) - }); + .with(edn_arg_parser()) + .map(|x| Ok(Command::QueryPrepared(x))); - let schema_parser = string(COMMAND_SCHEMA) - .with(no_arg_parser()) - .map(|args| { - if !args.is_empty() { - bail!(CliError::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); - } - Ok(Command::Schema) - }); + let schema_parser = string(COMMAND_SCHEMA).with(no_arg_parser()).map(|args| { + if !args.is_empty() { + bail!(CliError::CommandParse(format!( + "Unrecognized argument {:?}", + args[0] + ))); + } + Ok(Command::Schema) + }); let sync_parser = string(COMMAND_SYNC) - .with(spaces()) - .with(arguments()) - .map(|args| { - if args.len() < 1 { - bail!(CliError::CommandParse("Missing required argument".to_string())); - } - if args.len() > 2 { - bail!(CliError::CommandParse(format!("Unrecognized argument {:?}", args[2]))); - } - Ok(Command::Sync(args.clone())) - }); + .with(spaces()) + .with(arguments()) + .map(|args| { + if args.len() < 1 { + bail!(CliError::CommandParse( + "Missing required argument".to_string() + )); + } + if args.len() > 2 { + bail!(CliError::CommandParse(format!( + "Unrecognized argument {:?}", + args[2] + ))); + } + Ok(Command::Sync(args.clone())) + }); let timer_parser = string(COMMAND_TIMER_LONG) - .with(spaces()) - .with(string("on").map(|_| true).or(string("off").map(|_| false))) - .map(|args| { - Ok(Command::Timer(args)) - }); + .with(spaces()) + .with(string("on").map(|_| true).or(string("off").map(|_| false))) + .map(|args| Ok(Command::Timer(args))); - let transact_parser = try(string(COMMAND_TRANSACT_LONG)).or(try(string(COMMAND_TRANSACT_SHORT))) - .with(edn_arg_parser()) - .map(|x| { - Ok(Command::Transact(x)) - }); + let transact_parser = attempt(string(COMMAND_TRANSACT_LONG)) + .or(attempt(string(COMMAND_TRANSACT_SHORT))) + .with(edn_arg_parser()) + .map(|x| Ok(Command::Transact(x))); + let parsers = choice(( + attempt(help_parser), + attempt(import_parser), + attempt(timer_parser), + attempt(cache_parser), + attempt(open_encrypted_parser), + attempt(open_parser), + attempt(close_parser), + attempt(explain_query_parser), + attempt(exit_parser), + attempt(query_prepared_parser), + attempt(query_parser), + attempt(schema_parser), + attempt(sync_parser), + attempt(transact_parser), + )); spaces() - .skip(token('.')) - .with(choice::<[&mut Parser>; 14], _> - ([&mut try(help_parser), - &mut try(import_parser), - &mut try(timer_parser), - &mut try(cache_parser), - &mut try(open_encrypted_parser), - &mut try(open_parser), - &mut try(close_parser), - &mut try(explain_query_parser), - &mut try(exit_parser), - &mut try(query_prepared_parser), - &mut try(query_parser), - &mut try(schema_parser), - &mut try(sync_parser), - &mut try(transact_parser)])) + .skip(token('.')) + .with(parsers) .parse(s) - .unwrap_or((Err(CliError::CommandParse(format!("Invalid command {:?}", s)).into()), "")).0 + .unwrap_or(( + Err(CliError::CommandParse(format!("Invalid command {:?}", s)).into()), + "", + )) + .0 } #[cfg(test)] @@ -350,8 +319,8 @@ mod tests { match cmd { Command::Help(args) => { assert_eq!(args, vec!["command1", "command2"]); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -362,8 +331,8 @@ mod tests { match cmd { Command::Help(args) => { assert_eq!(args, vec![".command1"]); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -375,8 +344,8 @@ mod tests { Command::Help(args) => { let empty: Vec = vec![]; assert_eq!(args, empty); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -388,8 +357,8 @@ mod tests { Command::Help(args) => { let empty: Vec = vec![]; assert_eq!(args, empty); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -407,8 +376,8 @@ mod tests { match cmd { Command::Open(arg) => { assert_eq!(arg, "database1".to_string()); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -419,8 +388,8 @@ mod tests { match cmd { Command::Open(arg) => { assert_eq!(arg, "/path/to/my.db".to_string()); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -432,8 +401,8 @@ mod tests { Command::OpenEncrypted(path, key) => { assert_eq!(path, "/path/to/my.db".to_string()); assert_eq!(key, "hunter2".to_string()); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -452,8 +421,8 @@ mod tests { Command::Sync(args) => { assert_eq!(args[0], "https://example.com/api/".to_string()); assert_eq!(args[1], "316ea470-ce35-4adf-9c61-e0de6e289c59".to_string()); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -464,8 +433,8 @@ mod tests { match cmd { Command::Open(arg) => { assert_eq!(arg, "my.db".to_string()); - }, - _ => assert!(false) + } + _ => assert!(false), } } @@ -496,7 +465,7 @@ mod tests { let cmd = command(&input).expect("Expected close command"); match cmd { Command::Close => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -506,7 +475,7 @@ mod tests { let cmd = command(&input).expect("Expected close command"); match cmd { Command::Close => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -523,7 +492,7 @@ mod tests { let cmd = command(&input).expect("Expected exit command"); match cmd { Command::Exit => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -533,7 +502,7 @@ mod tests { let cmd = command(&input).expect("Expected exit command"); match cmd { Command::Exit => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -543,7 +512,7 @@ mod tests { let cmd = command(&input).expect("Expected exit command"); match cmd { Command::Exit => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -560,7 +529,7 @@ mod tests { let cmd = command(&input).expect("Expected schema command"); match cmd { Command::Schema => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -570,7 +539,7 @@ mod tests { let cmd = command(&input).expect("Expected schema command"); match cmd { Command::Schema => assert!(true), - _ => assert!(false) + _ => assert!(false), } } @@ -580,7 +549,7 @@ mod tests { let cmd = command(&input).expect("Expected query command"); match cmd { Command::Query(edn) => assert_eq!(edn, "[:find ?x :where [?x foo/bar ?y]]"), - _ => assert!(false) + _ => assert!(false), } } @@ -590,7 +559,7 @@ mod tests { let cmd = command(&input).expect("Expected query command"); match cmd { Command::Query(edn) => assert_eq!(edn, "[:find ?x :where [?x foo/bar ?y]]"), - _ => assert!(false) + _ => assert!(false), } } @@ -600,7 +569,7 @@ mod tests { let cmd = command(&input).expect("Expected query command"); match cmd { Command::Query(edn) => assert_eq!(edn, "[:find ?x\r\n"), - _ => assert!(false) + _ => assert!(false), } } @@ -610,7 +579,7 @@ mod tests { let cmd = command(&input).expect("Expected query command"); match cmd { Command::Query(edn) => assert_eq!(edn, "{}"), - _ => assert!(false) + _ => assert!(false), } } @@ -634,7 +603,7 @@ mod tests { let cmd = command(&input).expect("Expected import command"); match cmd { Command::Import(path) => assert_eq!(path, "/foo/bar/"), - _ => panic!("Wrong command!") + _ => panic!("Wrong command!"), } } @@ -643,18 +612,25 @@ mod tests { let input = ".t [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; let cmd = command(&input).expect("Expected transact command"); match cmd { - Command::Transact(edn) => assert_eq!(edn, "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"), - _ => assert!(false) + Command::Transact(edn) => assert_eq!( + edn, + "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]" + ), + _ => assert!(false), } } #[test] fn test_transact_parser_alt_command() { - let input = ".transact [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; + let input = + ".transact [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; let cmd = command(&input).expect("Expected transact command"); match cmd { - Command::Transact(edn) => assert_eq!(edn, "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"), - _ => assert!(false) + Command::Transact(edn) => assert_eq!( + edn, + "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]" + ), + _ => assert!(false), } } @@ -664,7 +640,7 @@ mod tests { let cmd = command(&input).expect("Expected transact command"); match cmd { Command::Transact(edn) => assert_eq!(edn, "{\r\n"), - _ => assert!(false) + _ => assert!(false), } } @@ -674,7 +650,7 @@ mod tests { let cmd = command(&input).expect("Expected transact command"); match cmd { Command::Transact(edn) => assert_eq!(edn, "{}"), - _ => assert!(false) + _ => assert!(false), } } @@ -698,7 +674,7 @@ mod tests { let cmd = command(&input).expect("Expected close command"); match cmd { Command::Close => assert!(true), - _ => assert!(false) + _ => assert!(false), } } diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs index 27139af8..ee70d73d 100644 --- a/tools/cli/src/mentat_cli/input.rs +++ b/tools/cli/src/mentat_cli/input.rs @@ -8,29 +8,15 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::io::{ - stdin, - stdout, - Write, -}; +use std::io::{stdin, stdout, Write}; -use linefeed::{ - DefaultTerminal, - Interface, - ReadResult, - Signal, -}; +use linefeed::{DefaultTerminal, Interface, ReadResult, Signal}; -use termion::{ - color, -}; +use termion::color; use self::InputResult::*; -use command_parser::{ - Command, - command, -}; +use command_parser::{command, Command}; use failure::Error; @@ -88,7 +74,7 @@ impl InputReader { r.set_word_break_chars(" \t\n!\"#$%&'(){}*+,-./:;<=>?@[\\]^`"); } - InputReader{ + InputReader { buffer: String::new(), interface, in_process_cmd: None, @@ -104,11 +90,17 @@ impl InputReader { /// Returns `More` if further input is required for a complete result. /// In this case, the input received so far is buffered internally. pub fn read_input(&mut self) -> Result { - let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT }; - let prompt = format!("{blue}{prompt}{reset}", - blue = color::Fg(::BLUE), - prompt = prompt, - reset = color::Fg(color::Reset)); + let prompt = if self.in_process_cmd.is_some() { + MORE_PROMPT + } else { + DEFAULT_PROMPT + }; + let prompt = format!( + "{blue}{prompt}{reset}", + blue = color::Fg(::BLUE), + prompt = prompt, + reset = color::Fg(color::Reset) + ); let line = match self.read_line(prompt.as_str()) { UserAction::TextInput(s) => s, UserAction::Interrupt if self.in_process_cmd.is_some() => { @@ -118,7 +110,7 @@ impl InputReader { // of the previous. println!(); String::new() - }, + } _ => return Ok(Eof), }; @@ -140,31 +132,29 @@ impl InputReader { let cmd = match &self.in_process_cmd { &Some(Command::QueryPrepared(ref args)) => { Ok(Command::QueryPrepared(args.clone() + "\n" + &line)) - }, - &Some(Command::Query(ref args)) => { - Ok(Command::Query(args.clone() + "\n" + &line)) - }, + } + &Some(Command::Query(ref args)) => Ok(Command::Query(args.clone() + "\n" + &line)), &Some(Command::Transact(ref args)) => { Ok(Command::Transact(args.clone() + "\n" + &line)) - }, - _ => { - command(&self.buffer) - }, + } + _ => command(&self.buffer), }; match cmd { Ok(cmd) => { match cmd { - Command::Query(_) | - Command::QueryPrepared(_) | - Command::Transact(_) | - Command::QueryExplain(_) if !cmd.is_complete() => { + Command::Query(_) + | Command::QueryPrepared(_) + | Command::Transact(_) + | Command::QueryExplain(_) + if !cmd.is_complete() => + { // A query or transact is complete if it contains a valid EDN. // if the command is not complete, ask for more from the REPL and remember // which type of command we've found here. self.in_process_cmd = Some(cmd); Ok(More) - }, + } _ => { let entry = self.buffer.clone(); self.buffer.clear(); @@ -173,37 +163,36 @@ impl InputReader { Ok(InputResult::MetaCommand(cmd)) } } - }, + } Err(e) => { let entry = self.buffer.clone(); self.buffer.clear(); self.add_history(entry); self.in_process_cmd = None; Err(e) - }, + } } } fn read_line(&mut self, prompt: &str) -> UserAction { match self.interface { Some(ref mut r) => { - r.set_prompt(prompt); - r.read_line().ok().map_or(UserAction::Quit, |line| - match line { + r.set_prompt(prompt).unwrap(); + r.read_line() + .ok() + .map_or(UserAction::Quit, |line| match line { ReadResult::Input(s) => UserAction::TextInput(s), - ReadResult::Signal(Signal::Interrupt) => - UserAction::Interrupt, + ReadResult::Signal(Signal::Interrupt) => UserAction::Interrupt, _ => UserAction::Quit, }) - - }, + } None => { print!("{}", prompt); if stdout().flush().is_err() { return UserAction::Quit; } self.read_stdin() - }, + } } } @@ -218,7 +207,7 @@ impl InputReader { s.truncate(len); } UserAction::TextInput(s) - }, + } } } diff --git a/tools/cli/src/mentat_cli/lib.rs b/tools/cli/src/mentat_cli/lib.rs index e7744759..ebb1153b 100644 --- a/tools/cli/src/mentat_cli/lib.rs +++ b/tools/cli/src/mentat_cli/lib.rs @@ -10,17 +10,18 @@ #![crate_name = "mentat_cli"] -use std::path::{ - PathBuf, -}; +use std::path::PathBuf; -#[macro_use] extern crate failure_derive; -#[macro_use] extern crate log; -#[macro_use] extern crate lazy_static; +#[macro_use] +extern crate failure_derive; +#[macro_use] +extern crate log; +#[macro_use] +extern crate lazy_static; extern crate combine; -extern crate env_logger; extern crate dirs; +extern crate env_logger; extern crate failure; extern crate getopts; extern crate linefeed; @@ -29,17 +30,15 @@ extern crate tabwriter; extern crate termion; extern crate time; -extern crate mentat; extern crate edn; +extern crate mentat; #[macro_use] extern crate core_traits; extern crate mentat_db; use getopts::Options; -use termion::{ - color, -}; +use termion::color; static HISTORY_FILE_PATH: &str = ".mentat_history"; @@ -73,14 +72,38 @@ pub fn run() -> i32 { opts.optopt("d", "", "The path to a database to open", "DATABASE"); if cfg!(feature = "sqlcipher") { - opts.optopt("k", "key", "The key to use to open the database (only available when using sqlcipher)", "KEY"); + opts.optopt( + "k", + "key", + "The key to use to open the database (only available when using sqlcipher)", + "KEY", + ); } opts.optflag("h", "help", "Print this help message and exit"); - opts.optmulti("q", "query", "Execute a query on startup. Queries are executed after any transacts.", "QUERY"); - opts.optmulti("t", "transact", "Execute a transact on startup. Transacts are executed before queries.", "TRANSACT"); - opts.optmulti("i", "import", "Execute an import on startup. Imports are executed before queries.", "PATH"); + opts.optmulti( + "q", + "query", + "Execute a query on startup. Queries are executed after any transacts.", + "QUERY", + ); + opts.optmulti( + "t", + "transact", + "Execute a transact on startup. Transacts are executed before queries.", + "TRANSACT", + ); + opts.optmulti( + "i", + "import", + "Execute an import on startup. Imports are executed before queries.", + "PATH", + ); opts.optflag("v", "version", "Print version and exit"); - opts.optflag("", "no-tty", "Don't try to use a TTY for readline-like input processing"); + opts.optflag( + "", + "no-tty", + "Don't try to use a TTY for readline-like input processing", + ); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -108,41 +131,44 @@ pub fn run() -> i32 { let mut last_arg: Option<&str> = None; - let cmds:Vec = args.iter().filter_map(|arg| { - match last_arg { + let cmds: Vec = args + .iter() + .filter_map(|arg| match last_arg { Some("-d") => { last_arg = None; if let &Some(ref k) = &key { - Some(command_parser::Command::OpenEncrypted(arg.clone(), k.clone())) + Some(command_parser::Command::OpenEncrypted( + arg.clone(), + k.clone(), + )) } else { Some(command_parser::Command::Open(arg.clone())) } - }, + } Some("-q") => { last_arg = None; Some(command_parser::Command::Query(arg.clone())) - }, + } Some("-i") => { last_arg = None; Some(command_parser::Command::Import(arg.clone())) - }, + } Some("-t") => { last_arg = None; Some(command_parser::Command::Transact(arg.clone())) - }, - Some(_) | - None => { + } + Some(_) | None => { last_arg = Some(&arg); None - }, - } - }).collect(); + } + }) + .collect(); let mut repl = match repl::Repl::new(!matches.opt_present("no-tty")) { Ok(repl) => repl, Err(e) => { println!("{}", e); - return 1 + return 1; } }; @@ -157,18 +183,18 @@ pub fn version() -> &'static str { } fn print_usage(arg0: &str, opts: &Options) { - print!("{}", opts.usage(&format!( - "Usage: {} [OPTIONS] [FILE]", arg0))); + print!( + "{}", + opts.usage(&format!("Usage: {} [OPTIONS] [FILE]", arg0)) + ); } fn print_version() { println!("mentat {}", version()); } - #[cfg(test)] mod tests { #[test] - fn it_works() { - } + fn it_works() {} } diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index 9965ac07..ba31b210 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -10,63 +10,30 @@ use std::io::Write; -use failure::{ - Error, -}; +use failure::Error; -use linefeed::{ - Interface, -}; +use linefeed::Interface; use tabwriter::TabWriter; -use termion::{ - color, - style, -}; +use termion::{color, style}; -use time::{ - Duration, - PreciseTime, -}; +use time::{Duration, Instant}; -use core_traits::{ - StructuredMap, -}; +use core_traits::StructuredMap; use mentat::{ - Binding, - CacheDirection, - Keyword, - QueryExplanation, - QueryOutput, - QueryResults, - Queryable, - Store, - TxReport, - TypedValue, + Binding, CacheDirection, Keyword, QueryExplanation, QueryOutput, QueryResults, Queryable, + Store, TxReport, TypedValue, }; -use command_parser::{ - Command, -}; +use command_parser::Command; use command_parser::{ - COMMAND_CACHE, - COMMAND_EXIT_LONG, - COMMAND_EXIT_SHORT, - COMMAND_HELP, - COMMAND_IMPORT_LONG, - COMMAND_OPEN, - COMMAND_QUERY_LONG, - COMMAND_QUERY_SHORT, - COMMAND_QUERY_EXPLAIN_LONG, - COMMAND_QUERY_EXPLAIN_SHORT, - COMMAND_QUERY_PREPARED_LONG, - COMMAND_SCHEMA, - COMMAND_TIMER_LONG, - COMMAND_TRANSACT_LONG, - COMMAND_TRANSACT_SHORT, + COMMAND_CACHE, COMMAND_EXIT_LONG, COMMAND_EXIT_SHORT, COMMAND_HELP, COMMAND_IMPORT_LONG, + COMMAND_OPEN, COMMAND_QUERY_EXPLAIN_LONG, COMMAND_QUERY_EXPLAIN_SHORT, COMMAND_QUERY_LONG, + COMMAND_QUERY_PREPARED_LONG, COMMAND_QUERY_SHORT, COMMAND_SCHEMA, COMMAND_TIMER_LONG, + COMMAND_TRANSACT_LONG, COMMAND_TRANSACT_SHORT, }; // These are still defined when this feature is disabled (so that we can @@ -74,22 +41,13 @@ use command_parser::{ // we weren't compiled with sqlcipher), but they're unused, since we // omit them from help message (since they wouldn't work). #[cfg(feature = "sqlcipher")] -use command_parser::{ - COMMAND_OPEN_ENCRYPTED, -}; +use command_parser::COMMAND_OPEN_ENCRYPTED; #[cfg(feature = "syncable")] -use command_parser::{ - COMMAND_SYNC, -}; +use command_parser::COMMAND_SYNC; use input::InputReader; -use input::InputResult::{ - Empty, - Eof, - MetaCommand, - More, -}; +use input::InputResult::{Empty, Eof, MetaCommand, More}; lazy_static! { static ref HELP_COMMANDS: Vec<(&'static str, &'static str)> = { @@ -130,59 +88,70 @@ lazy_static! { } fn eprint_out(s: &str) { - eprint!("{green}{s}{reset}", green = color::Fg(::GREEN), s = s, reset = color::Fg(color::Reset)); + eprint!( + "{green}{s}{reset}", + green = color::Fg(::GREEN), + s = s, + reset = color::Fg(color::Reset) + ); } fn parse_namespaced_keyword(input: &str) -> Option { let splits = [':', '/']; let mut i = input.split(&splits[..]); match (i.next(), i.next(), i.next(), i.next()) { - (Some(""), Some(namespace), Some(name), None) => { - Some(Keyword::namespaced(namespace, name)) - }, + (Some(""), Some(namespace), Some(name), None) => Some(Keyword::namespaced(namespace, name)), _ => None, } } fn format_time(duration: Duration) { - let m_nanos = duration.num_nanoseconds(); - if let Some(nanos) = m_nanos { + let m_nanos = duration.whole_nanoseconds(); + if let Some(nanos) = Some(m_nanos) { if nanos < 1_000 { - eprintln!("{bold}{nanos}{reset}ns", - bold = style::Bold, - nanos = nanos, - reset = style::Reset); + eprintln!( + "{bold}{nanos}{reset}ns", + bold = style::Bold, + nanos = nanos, + reset = style::Reset + ); return; } } - let m_micros = duration.num_microseconds(); - if let Some(micros) = m_micros { + let m_micros = duration.whole_microseconds(); + if let Some(micros) = Some(m_micros) { if micros < 1_000 { - eprintln!("{bold}{micros}{reset}µs", - bold = style::Bold, - micros = micros, - reset = style::Reset); + eprintln!( + "{bold}{micros}{reset}µs", + bold = style::Bold, + micros = micros, + reset = style::Reset + ); return; } if micros < 1_000_000 { // Print as millis. let millis = (micros as f64) / 1000f64; - eprintln!("{bold}{millis}{reset}ms", - bold = style::Bold, - millis = millis, - reset = style::Reset); + eprintln!( + "{bold}{millis}{reset}ms", + bold = style::Bold, + millis = millis, + reset = style::Reset + ); return; } } - let millis = duration.num_milliseconds(); + let millis = duration.whole_milliseconds(); let seconds = (millis as f64) / 1000f64; - eprintln!("{bold}{seconds}{reset}s", - bold = style::Bold, - seconds = seconds, - reset = style::Reset); + eprintln!( + "{bold}{seconds}{reset}s", + bold = style::Bold, + seconds = seconds, + reset = style::Reset + ); } /// Executes input and maintains state of persistent items. @@ -205,7 +174,10 @@ impl Repl { /// Constructs a new `Repl`. pub fn new(tty: bool) -> Result { let interface = if tty { - Some(Interface::new("mentat").map_err(|_| "failed to create tty interface; try --no-tty")?) + Some( + Interface::new("mentat") + .map_err(|_| "failed to create tty interface; try --no-tty")?, + ) } else { None }; @@ -239,15 +211,14 @@ impl Repl { if !self.handle_command(cmd) { break; } - }, - Ok(Empty) | - Ok(More) => (), + } + Ok(Empty) | Ok(More) => (), Ok(Eof) => { if self.input_reader.is_tty() { println!(); } break; - }, + } Err(e) => eprintln!("{}", e.to_string()), } } @@ -270,68 +241,72 @@ impl Repl { fn handle_command(&mut self, cmd: Command) -> bool { let should_print_times = self.timer_on && cmd.is_timed(); - let mut start = PreciseTime::now(); - let mut end: Option = None; + let mut start = Instant::now(); + let mut end: Option = None; match cmd { Command::Cache(attr, direction) => { self.cache(attr, direction); - }, + } Command::Close => { self.close(); - }, + } Command::Exit => { eprintln!("Exiting…"); return false; - }, + } Command::Help(args) => { self.help_command(args); - }, + } Command::Import(path) => { self.execute_import(path); - }, + } Command::Open(db) => { match self.open(db) { Ok(_) => println!("Database {:?} opened", self.db_name()), Err(e) => eprintln!("{}", e.to_string()), }; - }, + } Command::OpenEncrypted(db, encryption_key) => { match self.open_with_key(db, &encryption_key) { - Ok(_) => println!("Database {:?} opened with key {:?}", self.db_name(), encryption_key), + Ok(_) => println!( + "Database {:?} opened with key {:?}", + self.db_name(), + encryption_key + ), Err(e) => eprintln!("{}", e.to_string()), } - }, + } Command::Query(query) => { self.store .q_once(query.as_str(), None) .map_err(|e| e.into()) .and_then(|o| { - end = Some(PreciseTime::now()); + end = Some(Instant::now()); self.print_results(o) }) .map_err(|err| { eprintln!("{:?}.", err); }) .ok(); - }, + } Command::QueryExplain(query) => { self.explain_query(query); - }, + } Command::QueryPrepared(query) => { self.store .q_prepare(query.as_str(), None) .and_then(|mut p| { - let prepare_end = PreciseTime::now(); + let prepare_end = Instant::now(); if should_print_times { eprint_out("Prepare time"); eprint!(": "); - format_time(start.to(prepare_end)); + format_time(prepare_end - start); } - // This is a hack. - start = PreciseTime::now(); + // TODO: This is a hack. + start = Instant::now(); let r = p.run(None); - end = Some(PreciseTime::now()); + end = Some(Instant::now()); return r; }) .map(|o| self.print_results(o)) @@ -339,73 +314,76 @@ impl Repl { eprintln!("{:?}.", err); }) .ok(); - }, + } Command::Schema => { let edn = self.store.conn().current_schema().to_edn_value(); match edn.to_pretty(120) { Ok(s) => println!("{}", s), - Err(e) => eprintln!("{}", e) + Err(e) => eprintln!("{}", e), }; - }, + } #[cfg(feature = "syncable")] Command::Sync(args) => { match self.store.sync(&args[0], &args[1]) { Ok(report) => println!("Sync report: {}", report), - Err(e) => eprintln!("{:?}", e) + Err(e) => eprintln!("{:?}", e), }; - }, + } #[cfg(not(feature = "syncable"))] Command::Sync(_) => { eprintln!(".sync requires the syncable Mentat feature"); - }, + } Command::Timer(on) => { self.toggle_timer(on); - }, + } Command::Transact(transaction) => { self.execute_transact(transaction); - }, + } } - let end = end.unwrap_or_else(PreciseTime::now); + let end = end.unwrap_or_else(Instant::now); if should_print_times { eprint_out("Run time"); eprint!(": "); - format_time(start.to(end)); + format_time(end - start); } return true; } fn execute_import(&mut self, path: T) - where T: Into { - use ::std::io::Read; + where + T: Into, + { + use std::io::Read; let path = path.into(); let mut content: String = "".to_string(); match ::std::fs::File::open(path.clone()).and_then(|mut f| f.read_to_string(&mut content)) { Ok(_) => self.execute_transact(content), - Err(e) => eprintln!("Error reading file {}: {}", path, e) + Err(e) => eprintln!("Error reading file {}: {}", path, e), } } fn open_common( &mut self, path: String, - encryption_key: Option<&str> + encryption_key: Option<&str>, ) -> ::mentat::errors::Result<()> { if self.path.is_empty() || path != self.path { let next = match encryption_key { #[cfg(not(feature = "sqlcipher"))] - Some(_) => return Err(::mentat::MentatError::RusqliteError(".open_encrypted requires the sqlcipher Mentat feature".into(), "".into())), - #[cfg(feature = "sqlcipher")] - Some(k) => { - Store::open_with_key(path.as_str(), k)? - }, - _ => { - Store::open(path.as_str())? + Some(_) => { + return Err(::mentat::MentatError::RusqliteError( + ".open_encrypted requires the sqlcipher Mentat feature".into(), + "".into(), + )) } + #[cfg(feature = "sqlcipher")] + Some(k) => Store::open_with_key(path.as_str(), k)?, + _ => Store::open(path.as_str())?, }; self.path = path; self.store = next; @@ -414,12 +392,18 @@ impl Repl { Ok(()) } - fn open(&mut self, path: T) -> ::mentat::errors::Result<()> where T: Into { + fn open(&mut self, path: T) -> ::mentat::errors::Result<()> + where + T: Into, + { self.open_common(path.into(), None) } - fn open_with_key(&mut self, path: T, encryption_key: U) - -> ::mentat::errors::Result<()> where T: Into, U: AsRef { + fn open_with_key(&mut self, path: T, encryption_key: U) -> ::mentat::errors::Result<()> + where + T: Into, + U: AsRef, + { self.open_common(path.into(), Some(encryption_key.as_ref())) } @@ -449,9 +433,11 @@ impl Repl { if arg.chars().nth(0).unwrap() == '.' { arg.remove(0); } - if let Some(&(cmd, msg)) = HELP_COMMANDS.iter() - .filter(|&&(c, _)| c == arg.as_str()) - .next() { + if let Some(&(cmd, msg)) = HELP_COMMANDS + .iter() + .filter(|&&(c, _)| c == arg.as_str()) + .next() + { write!(output, ".{}\t", cmd).unwrap(); writeln!(output, "{}", msg).unwrap(); } else { @@ -483,7 +469,7 @@ impl Repl { if let Some(val) = v { writeln!(output, "| {}\t |", &self.binding_as_string(&val))?; } - }, + } QueryResults::Tuple(vv) => { if let Some(vals) = vv { @@ -492,13 +478,13 @@ impl Repl { } writeln!(output, "|")?; } - }, + } QueryResults::Coll(vv) => { for val in vv { writeln!(output, "| {}\t|", self.binding_as_string(&val))?; } - }, + } QueryResults::Rel(vvv) => { for vv in vvv { @@ -507,7 +493,7 @@ impl Repl { } writeln!(output, "|")?; } - }, + } } for _ in 0..query_output.spec.expected_column_count() { write!(output, "---\t")?; @@ -519,12 +505,11 @@ impl Repl { pub fn explain_query(&self, query: String) { match self.store.q_explain(query.as_str(), None) { - Result::Err(err) => - println!("{:?}.", err), - Result::Ok(QueryExplanation::KnownConstant) => - println!("Query is known constant!"), - Result::Ok(QueryExplanation::KnownEmpty(empty_because)) => - println!("Query is known empty: {:?}", empty_because), + Result::Err(err) => println!("{:?}.", err), + Result::Ok(QueryExplanation::KnownConstant) => println!("Query is known constant!"), + Result::Ok(QueryExplanation::KnownEmpty(empty_because)) => { + println!("Query is known empty: {:?}", empty_because) + } Result::Ok(QueryExplanation::ExecutionPlan { query, steps }) => { println!("SQL: {}", query.sql); if !query.args.is_empty() { @@ -537,8 +522,14 @@ impl Repl { println!("Plan: select id | order | from | detail"); // Compute the number of columns we need for order, select id, and from, // so that longer query plans don't become misaligned. - let (max_select_id, max_order, max_from) = steps.iter().fold((0, 0, 0), |acc, step| - (acc.0.max(step.select_id), acc.1.max(step.order), acc.2.max(step.from))); + let (max_select_id, max_order, max_from) = + steps.iter().fold((0, 0, 0), |acc, step| { + ( + acc.0.max(step.select_id), + acc.1.max(step.order), + acc.2.max(step.from), + ) + }); // This is less efficient than computing it via the logarithm base 10, // but it's clearer and doesn't have require special casing "0" let max_select_digits = max_select_id.to_string().len(); @@ -546,11 +537,16 @@ impl Repl { let max_from_digits = max_from.to_string().len(); for step in steps { // Note: > is right align. - println!(" {:>sel_cols$}|{:>ord_cols$}|{:>from_cols$}|{}", - step.select_id, step.order, step.from, step.detail, - sel_cols = max_select_digits, - ord_cols = max_order_digits, - from_cols = max_from_digits); + println!( + " {:>sel_cols$}|{:>ord_cols$}|{:>from_cols$}|{}", + step.select_id, + step.order, + step.from, + step.detail, + sel_cols = max_select_digits, + ord_cols = max_order_digits, + from_cols = max_from_digits + ); } } }; @@ -581,9 +577,7 @@ impl Repl { fn vec_as_string(&self, value: &Vec) -> String { let mut out: String = "[".to_string(); - let vals: Vec = value.iter() - .map(|v| self.binding_as_string(v)) - .collect(); + let vals: Vec = value.iter().map(|v| self.binding_as_string(v)).collect(); out.push_str(vals.join(", ").as_str()); out.push_str("]"); @@ -609,7 +603,13 @@ impl Repl { fn value_as_string(&self, value: &TypedValue) -> String { use self::TypedValue::*; match value { - &Boolean(b) => if b { "true".to_string() } else { "false".to_string() }, + &Boolean(b) => { + if b { + "true".to_string() + } else { + "false".to_string() + } + } &Double(d) => format!("{}", d), &Instant(ref i) => format!("{}", i), &Keyword(ref k) => format!("{}", k), diff --git a/transaction/Cargo.toml b/transaction/Cargo.toml index acd950e3..f8ee32ff 100644 --- a/transaction/Cargo.toml +++ b/transaction/Cargo.toml @@ -43,5 +43,5 @@ path = "../query-pull" path = "../query-sql" [dependencies.rusqlite] -version = "0.19" +version = "0.21" features = ["limits"] diff --git a/transaction/src/entity_builder.rs b/transaction/src/entity_builder.rs index 05ce8bb9..9604017b 100644 --- a/transaction/src/entity_builder.rs +++ b/transaction/src/entity_builder.rs @@ -52,35 +52,18 @@ // // We probably need both, but this file provides the latter. -use edn::{ - InternSet, - PlainSymbol, - ValueRc, -}; use edn::entities::{ - AttributePlace, - Entity, - EntityPlace, - LookupRef, - OpType, - TempId, - TxFunction, - ValuePlace, + AttributePlace, Entity, EntityPlace, LookupRef, OpType, TempId, TxFunction, ValuePlace, }; +use edn::{InternSet, PlainSymbol, ValueRc}; -use core_traits::{ - TypedValue, -}; +use core_traits::TypedValue; -use mentat_core::{ - TxReport, -}; +use mentat_core::TxReport; -use ::InProgress; +use InProgress; -use public_traits::errors::{ - Result, -}; +use public_traits::errors::Result; pub type Terms = (Vec>, InternSet); @@ -95,22 +78,34 @@ pub struct EntityBuilder { entity: EntityPlace, } -pub trait BuildTerms where Self: Sized { - fn named_tempid(&mut self, name: I) -> ValueRc where I: Into; +pub trait BuildTerms +where + Self: Sized, +{ + fn named_tempid(&mut self, name: I) -> ValueRc + where + I: Into; fn describe_tempid(self, name: &str) -> EntityBuilder; - fn describe(self, entity: E) -> EntityBuilder where E: Into>; + fn describe(self, entity: E) -> EntityBuilder + where + E: Into>; fn add(&mut self, e: E, a: A, v: V) -> Result<()> - where E: Into>, - A: Into, - V: Into>; + where + E: Into>, + A: Into, + V: Into>; fn retract(&mut self, e: E, a: A, v: V) -> Result<()> - where E: Into>, - A: Into, - V: Into>; + where + E: Into>, + A: Into, + V: Into>; } impl BuildTerms for TermBuilder { - fn named_tempid(&mut self, name: I) -> ValueRc where I: Into { + fn named_tempid(&mut self, name: I) -> ValueRc + where + I: Into, + { self.tempids.intern(TempId::External(name.into())) } @@ -119,7 +114,10 @@ impl BuildTerms for TermBuilder { self.describe(e) } - fn describe(self, entity: E) -> EntityBuilder where E: Into> { + fn describe(self, entity: E) -> EntityBuilder + where + E: Into>, + { EntityBuilder { builder: self, entity: entity.into(), @@ -127,18 +125,32 @@ impl BuildTerms for TermBuilder { } fn add(&mut self, e: E, a: A, v: V) -> Result<()> - where E: Into>, - A: Into, - V: Into> { - self.terms.push(Entity::AddOrRetract { op: OpType::Add, e: e.into(), a: a.into(), v: v.into() }); + where + E: Into>, + A: Into, + V: Into>, + { + self.terms.push(Entity::AddOrRetract { + op: OpType::Add, + e: e.into(), + a: a.into(), + v: v.into(), + }); Ok(()) } fn retract(&mut self, e: E, a: A, v: V) -> Result<()> - where E: Into>, - A: Into, - V: Into> { - self.terms.push(Entity::AddOrRetract { op: OpType::Retract, e: e.into(), a: a.into(), v: v.into() }); + where + E: Into>, + A: Into, + V: Into>, + { + self.terms.push(Entity::AddOrRetract { + op: OpType::Retract, + e: e.into(), + a: a.into(), + v: v.into(), + }); Ok(()) } } @@ -165,30 +177,44 @@ impl TermBuilder { } pub fn lookup_ref(a: A, v: V) -> LookupRef - where A: Into, - V: Into { - LookupRef { a: a.into(), v: v.into() } + where + A: Into, + V: Into, + { + LookupRef { + a: a.into(), + v: v.into(), + } } pub fn tx_function(op: &str) -> TxFunction { - TxFunction { op: PlainSymbol::plain(op) } + TxFunction { + op: PlainSymbol::plain(op), + } } } -impl EntityBuilder where T: BuildTerms { +impl EntityBuilder +where + T: BuildTerms, +{ pub fn finish(self) -> (T, EntityPlace) { (self.builder, self.entity) } pub fn add(&mut self, a: A, v: V) -> Result<()> - where A: Into, - V: Into> { + where + A: Into, + V: Into>, + { self.builder.add(self.entity.clone(), a, v) } pub fn retract(&mut self, a: A, v: V) -> Result<()> - where A: Into, - V: Into> { + where + A: Into, + V: Into>, + { self.builder.retract(self.entity.clone(), a, v) } } @@ -209,13 +235,12 @@ impl<'a, 'c> InProgressBuilder<'a, 'c> { /// Build the terms from this builder and transact them against the current /// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't /// imply an automatic rollback. - pub fn transact(self) -> (InProgress<'a, 'c>, Result) { + pub fn transact(self) -> (InProgress<'a, 'c>, Result) { let mut in_progress = self.in_progress; - let result = self.builder - .build() - .and_then(|(terms, _tempid_set)| { - in_progress.transact_entities(terms) - }); + let result = self + .builder + .build() + .and_then(|(terms, _tempid_set)| in_progress.transact_entities(terms)); (in_progress, result) } @@ -223,16 +248,20 @@ impl<'a, 'c> InProgressBuilder<'a, 'c> { /// step fails, roll back. Return the `TxReport`. pub fn commit(self) -> Result { let mut in_progress = self.in_progress; - in_progress.transact_builder(self.builder) - .and_then(|report| { - in_progress.commit()?; - Ok(report) - }) + in_progress + .transact_builder(self.builder) + .and_then(|report| { + in_progress.commit()?; + Ok(report) + }) } } impl<'a, 'c> BuildTerms for InProgressBuilder<'a, 'c> { - fn named_tempid(&mut self, name: I) -> ValueRc where I: Into { + fn named_tempid(&mut self, name: I) -> ValueRc + where + I: Into, + { self.builder.named_tempid(name) } @@ -241,7 +270,10 @@ impl<'a, 'c> BuildTerms for InProgressBuilder<'a, 'c> { self.describe(e) } - fn describe(self, entity: E) -> EntityBuilder> where E: Into> { + fn describe(self, entity: E) -> EntityBuilder> + where + E: Into>, + { EntityBuilder { builder: self, entity: entity.into(), @@ -249,16 +281,20 @@ impl<'a, 'c> BuildTerms for InProgressBuilder<'a, 'c> { } fn add(&mut self, e: E, a: A, v: V) -> Result<()> - where E: Into>, - A: Into, - V: Into> { + where + E: Into>, + A: Into, + V: Into>, + { self.builder.add(e, a, v) } fn retract(&mut self, e: E, a: A, v: V) -> Result<()> - where E: Into>, - A: Into, - V: Into> { + where + E: Into>, + A: Into, + V: Into>, + { self.builder.retract(e, a, v) } } @@ -267,7 +303,7 @@ impl<'a, 'c> EntityBuilder> { /// Build the terms from this builder and transact them against the current /// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't /// imply an automatic rollback. - pub fn transact(self) -> (InProgress<'a, 'c>, Result) { + pub fn transact(self) -> (InProgress<'a, 'c>, Result) { self.finish().0.transact() } diff --git a/transaction/src/lib.rs b/transaction/src/lib.rs index c5b3bf37..42adc919 100644 --- a/transaction/src/lib.rs +++ b/transaction/src/lib.rs @@ -23,10 +23,7 @@ extern crate mentat_query_projector; extern crate mentat_query_pull; extern crate mentat_sql; -use std::sync::{ - Arc, - Mutex, -}; +use std::sync::{Arc, Mutex}; use std::io::Read; @@ -34,94 +31,43 @@ use std::borrow::Borrow; use std::collections::BTreeMap; -use std::fs::{ - File, -}; +use std::fs::File; -use std::path::{ - Path, -}; +use std::path::Path; -use edn::{ - InternSet, - Keyword, -}; -use edn::entities::{ - TempId, - OpType, -}; +use edn::entities::{OpType, TempId}; +use edn::{InternSet, Keyword}; -use core_traits::{ - Attribute, - Entid, - KnownEntid, - StructuredMap, - TypedValue, - ValueType, -}; +use core_traits::{Attribute, Entid, KnownEntid, StructuredMap, TypedValue, ValueType}; -use public_traits::errors::{ - Result, - MentatError, -}; +use public_traits::errors::{MentatError, Result}; -use mentat_core::{ - HasSchema, - Schema, - TxReport, - ValueRc, -}; +use mentat_core::{HasSchema, Schema, TxReport, ValueRc}; -use mentat_query_pull::{ - pull_attributes_for_entities, - pull_attributes_for_entity, -}; +use mentat_query_pull::{pull_attributes_for_entities, pull_attributes_for_entity}; use mentat_db::{ - transact, - transact_terms, - InProgressObserverTransactWatcher, - PartitionMap, - TransactableValue, - TransactWatcher, - TxObservationService, + transact, transact_terms, InProgressObserverTransactWatcher, PartitionMap, TransactWatcher, + TransactableValue, TxObservationService, }; use mentat_db::internal_types::TermWithTempIds; -use mentat_db::cache::{ - InProgressCacheTransactWatcher, - InProgressSQLiteAttributeCache, -}; +use mentat_db::cache::{InProgressCacheTransactWatcher, InProgressSQLiteAttributeCache}; pub mod entity_builder; pub mod metadata; pub mod query; -pub use entity_builder::{ - InProgressBuilder, - TermBuilder, -}; +pub use entity_builder::{InProgressBuilder, TermBuilder}; -pub use metadata::{ - Metadata, -}; +pub use metadata::Metadata; use query::{ - Known, - PreparedResult, - QueryExplanation, - QueryInputs, - QueryOutput, - lookup_value_for_attribute, - lookup_values_for_attribute, - q_explain, - q_once, - q_prepare, - q_uncached, + lookup_value_for_attribute, lookup_values_for_attribute, q_explain, q_once, q_prepare, + q_uncached, Known, PreparedResult, QueryExplanation, QueryInputs, QueryOutput, }; - #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CacheDirection { Forward, @@ -159,23 +105,42 @@ pub struct InProgressRead<'a, 'c> { pub trait Queryable { fn q_explain(&self, query: &str, inputs: T) -> Result - where T: Into>; + where + T: Into>; fn q_once(&self, query: &str, inputs: T) -> Result - where T: Into>; + where + T: Into>; fn q_prepare(&self, query: &str, inputs: T) -> PreparedResult - where T: Into>; - fn lookup_values_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into; - fn lookup_value_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into; + where + T: Into>; + fn lookup_values_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into; + fn lookup_value_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into; } pub trait Pullable { - fn pull_attributes_for_entities(&self, entities: E, attributes: A) -> Result>> - where E: IntoIterator, - A: IntoIterator; + fn pull_attributes_for_entities( + &self, + entities: E, + attributes: A, + ) -> Result>> + where + E: IntoIterator, + A: IntoIterator; fn pull_attributes_for_entity(&self, entity: Entid, attributes: A) -> Result - where A: IntoIterator; + where + A: IntoIterator; } impl<'a, 'c> InProgress<'a, 'c> { @@ -191,24 +156,28 @@ impl<'a, 'c> InProgress<'a, 'c> { /// If you only have a reference to an `InProgress`, you can't use the easy builder. /// This exists so you can make your own. pub fn transact_builder(&mut self, builder: TermBuilder) -> Result { - builder.build() - .and_then(|(terms, _tempid_set)| { - self.transact_entities(terms) - }) + builder + .build() + .and_then(|(terms, _tempid_set)| self.transact_entities(terms)) } - pub fn transact_terms(&mut self, terms: I, tempid_set: InternSet) -> Result where I: IntoIterator { + pub fn transact_terms(&mut self, terms: I, tempid_set: InternSet) -> Result + where + I: IntoIterator, + { let w = InProgressTransactWatcher::new( - &mut self.tx_observer_watcher, - self.cache.transact_watcher()); - let (report, next_partition_map, next_schema, _watcher) = - transact_terms(&self.transaction, - self.partition_map.clone(), - &self.schema, - &self.schema, - w, - terms, - tempid_set)?; + &mut self.tx_observer_watcher, + self.cache.transact_watcher(), + ); + let (report, next_partition_map, next_schema, _watcher) = transact_terms( + &self.transaction, + self.partition_map.clone(), + &self.schema, + &self.schema, + w, + terms, + tempid_set, + )?; self.partition_map = next_partition_map; if let Some(schema) = next_schema { self.schema = schema; @@ -216,7 +185,10 @@ impl<'a, 'c> InProgress<'a, 'c> { Ok(report) } - pub fn transact_entities(&mut self, entities: I) -> Result where I: IntoIterator> { + pub fn transact_entities(&mut self, entities: I) -> Result + where + I: IntoIterator>, + { // We clone the partition map here, rather than trying to use a Cell or using a mutable // reference, for two reasons: // 1. `transact` allocates new IDs in partitions before and while doing work that might @@ -226,15 +198,17 @@ impl<'a, 'c> InProgress<'a, 'c> { // `Default::default` in those situations to extract the partition map, and so there // would still be some cost. let w = InProgressTransactWatcher::new( - &mut self.tx_observer_watcher, - self.cache.transact_watcher()); - let (report, next_partition_map, next_schema, _watcher) = - transact(&self.transaction, - self.partition_map.clone(), - &self.schema, - &self.schema, - w, - entities)?; + &mut self.tx_observer_watcher, + self.cache.transact_watcher(), + ); + let (report, next_partition_map, next_schema, _watcher) = transact( + &self.transaction, + self.partition_map.clone(), + &self.schema, + &self.schema, + w, + entities, + )?; self.partition_map = next_partition_map; if let Some(schema) = next_schema { self.schema = schema; @@ -242,13 +216,18 @@ impl<'a, 'c> InProgress<'a, 'c> { Ok(report) } - pub fn transact(&mut self, transaction: B) -> Result where B: Borrow { + pub fn transact(&mut self, transaction: B) -> Result + where + B: Borrow, + { let entities = edn::parse::entities(transaction.borrow())?; self.transact_entities(entities) } pub fn import

(&mut self, path: P) -> Result - where P: AsRef { + where + P: AsRef, + { let mut file = File::open(path)?; let mut text: String = String::new(); file.read_to_string(&mut text)?; @@ -289,31 +268,47 @@ impl<'a, 'c> InProgress<'a, 'c> { } let txes = self.tx_observer_watcher.txes; - self.tx_observer.lock().unwrap().in_progress_did_commit(txes); + self.tx_observer + .lock() + .unwrap() + .in_progress_did_commit(txes); Ok(()) } - pub fn cache(&mut self, - attribute: &Keyword, - cache_direction: CacheDirection, - cache_action: CacheAction) -> Result<()> { - let attribute_entid: Entid = self.schema - .attribute_for_ident(&attribute) - .ok_or_else(|| MentatError::UnknownAttribute(attribute.to_string()))?.1.into(); + pub fn cache( + &mut self, + attribute: &Keyword, + cache_direction: CacheDirection, + cache_action: CacheAction, + ) -> Result<()> { + let attribute_entid: Entid = self + .schema + .attribute_for_ident(&attribute) + .ok_or_else(|| MentatError::UnknownAttribute(attribute.to_string()))? + .1 + .into(); match cache_action { - CacheAction::Register => { - match cache_direction { - CacheDirection::Both => self.cache.register(&self.schema, &self.transaction, attribute_entid), - CacheDirection::Forward => self.cache.register_forward(&self.schema, &self.transaction, attribute_entid), - CacheDirection::Reverse => self.cache.register_reverse(&self.schema, &self.transaction, attribute_entid), - }.map_err(|e| e.into()) - }, + CacheAction::Register => match cache_direction { + CacheDirection::Both => { + self.cache + .register(&self.schema, &self.transaction, attribute_entid) + } + CacheDirection::Forward => { + self.cache + .register_forward(&self.schema, &self.transaction, attribute_entid) + } + CacheDirection::Reverse => { + self.cache + .register_reverse(&self.schema, &self.transaction, attribute_entid) + } + } + .map_err(|e| e.into()), CacheAction::Deregister => { self.cache.unregister(attribute_entid); Ok(()) - }, + } } } @@ -322,17 +317,20 @@ impl<'a, 'c> InProgress<'a, 'c> { } pub fn savepoint(&self, name: &str) -> Result<()> { - self.transaction.execute(&format!("SAVEPOINT {}", name), rusqlite::params![])?; + self.transaction + .execute(&format!("SAVEPOINT {}", name), rusqlite::params![])?; Ok(()) } pub fn rollback_savepoint(&self, name: &str) -> Result<()> { - self.transaction.execute(&format!("ROLLBACK TO {}", name), rusqlite::params![])?; + self.transaction + .execute(&format!("ROLLBACK TO {}", name), rusqlite::params![])?; Ok(()) } pub fn release_savepoint(&self, name: &str) -> Result<()> { - self.transaction.execute(&format!("RELEASE {}", name), rusqlite::params![])?; + self.transaction + .execute(&format!("RELEASE {}", name), rusqlite::params![])?; Ok(()) } } @@ -343,108 +341,148 @@ impl<'a, 'c> InProgressRead<'a, 'c> { } } - impl<'a, 'c> Queryable for InProgressRead<'a, 'c> { fn q_once(&self, query: &str, inputs: T) -> Result - where T: Into> { + where + T: Into>, + { self.in_progress.q_once(query, inputs) } fn q_prepare(&self, query: &str, inputs: T) -> PreparedResult - where T: Into> { + where + T: Into>, + { self.in_progress.q_prepare(query, inputs) } fn q_explain(&self, query: &str, inputs: T) -> Result - where T: Into> { + where + T: Into>, + { self.in_progress.q_explain(query, inputs) } - fn lookup_values_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into { - self.in_progress.lookup_values_for_attribute(entity, attribute) + fn lookup_values_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into, + { + self.in_progress + .lookup_values_for_attribute(entity, attribute) } - fn lookup_value_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into { - self.in_progress.lookup_value_for_attribute(entity, attribute) + fn lookup_value_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into, + { + self.in_progress + .lookup_value_for_attribute(entity, attribute) } } impl<'a, 'c> Pullable for InProgressRead<'a, 'c> { - fn pull_attributes_for_entities(&self, entities: E, attributes: A) -> Result>> - where E: IntoIterator, - A: IntoIterator { - self.in_progress.pull_attributes_for_entities(entities, attributes) + fn pull_attributes_for_entities( + &self, + entities: E, + attributes: A, + ) -> Result>> + where + E: IntoIterator, + A: IntoIterator, + { + self.in_progress + .pull_attributes_for_entities(entities, attributes) } fn pull_attributes_for_entity(&self, entity: Entid, attributes: A) -> Result - where A: IntoIterator { - self.in_progress.pull_attributes_for_entity(entity, attributes) + where + A: IntoIterator, + { + self.in_progress + .pull_attributes_for_entity(entity, attributes) } } impl<'a, 'c> Queryable for InProgress<'a, 'c> { fn q_once(&self, query: &str, inputs: T) -> Result - where T: Into> { - + where + T: Into>, + { if self.use_caching { let known = Known::new(&self.schema, Some(&self.cache)); - q_once(&*(self.transaction), - known, - query, - inputs) + q_once(&*(self.transaction), known, query, inputs) } else { - q_uncached(&*(self.transaction), - &self.schema, - query, - inputs) + q_uncached(&*(self.transaction), &self.schema, query, inputs) } } fn q_prepare(&self, query: &str, inputs: T) -> PreparedResult - where T: Into> { - + where + T: Into>, + { let known = Known::new(&self.schema, Some(&self.cache)); - q_prepare(&*(self.transaction), - known, - query, - inputs) + q_prepare(&*(self.transaction), known, query, inputs) } fn q_explain(&self, query: &str, inputs: T) -> Result - where T: Into> { - + where + T: Into>, + { let known = Known::new(&self.schema, Some(&self.cache)); - q_explain(&*(self.transaction), - known, - query, - inputs) + q_explain(&*(self.transaction), known, query, inputs) } - fn lookup_values_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into { + fn lookup_values_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into, + { let known = Known::new(&self.schema, Some(&self.cache)); lookup_values_for_attribute(&*(self.transaction), known, entity, attribute) } - fn lookup_value_for_attribute(&self, entity: E, attribute: &edn::Keyword) -> Result> - where E: Into { + fn lookup_value_for_attribute( + &self, + entity: E, + attribute: &edn::Keyword, + ) -> Result> + where + E: Into, + { let known = Known::new(&self.schema, Some(&self.cache)); lookup_value_for_attribute(&*(self.transaction), known, entity, attribute) } } impl<'a, 'c> Pullable for InProgress<'a, 'c> { - fn pull_attributes_for_entities(&self, entities: E, attributes: A) -> Result>> - where E: IntoIterator, - A: IntoIterator { + fn pull_attributes_for_entities( + &self, + entities: E, + attributes: A, + ) -> Result>> + where + E: IntoIterator, + A: IntoIterator, + { pull_attributes_for_entities(&self.schema, &*(self.transaction), entities, attributes) .map_err(|e| e.into()) } fn pull_attributes_for_entity(&self, entity: Entid, attributes: A) -> Result - where A: IntoIterator { + where + A: IntoIterator, + { pull_attributes_for_entity(&self.schema, &*(self.transaction), entity, attributes) .map_err(|e| e.into()) } @@ -455,7 +493,10 @@ impl<'a, 'c> HasSchema for InProgressRead<'a, 'c> { self.in_progress.entid_for_type(t) } - fn get_ident(&self, x: T) -> Option<&Keyword> where T: Into { + fn get_ident(&self, x: T) -> Option<&Keyword> + where + T: Into, + { self.in_progress.get_ident(x) } @@ -463,7 +504,10 @@ impl<'a, 'c> HasSchema for InProgressRead<'a, 'c> { self.in_progress.get_entid(x) } - fn attribute_for_entid(&self, x: T) -> Option<&Attribute> where T: Into { + fn attribute_for_entid(&self, x: T) -> Option<&Attribute> + where + T: Into, + { self.in_progress.attribute_for_entid(x) } @@ -472,7 +516,10 @@ impl<'a, 'c> HasSchema for InProgressRead<'a, 'c> { } /// Return true if the provided entid identifies an attribute in this schema. - fn is_attribute(&self, x: T) -> bool where T: Into { + fn is_attribute(&self, x: T) -> bool + where + T: Into, + { self.in_progress.is_attribute(x) } @@ -491,7 +538,10 @@ impl<'a, 'c> HasSchema for InProgress<'a, 'c> { self.schema.entid_for_type(t) } - fn get_ident(&self, x: T) -> Option<&Keyword> where T: Into { + fn get_ident(&self, x: T) -> Option<&Keyword> + where + T: Into, + { self.schema.get_ident(x) } @@ -499,7 +549,10 @@ impl<'a, 'c> HasSchema for InProgress<'a, 'c> { self.schema.get_entid(x) } - fn attribute_for_entid(&self, x: T) -> Option<&Attribute> where T: Into { + fn attribute_for_entid(&self, x: T) -> Option<&Attribute> + where + T: Into, + { self.schema.attribute_for_entid(x) } @@ -508,7 +561,10 @@ impl<'a, 'c> HasSchema for InProgress<'a, 'c> { } /// Return true if the provided entid identifies an attribute in this schema. - fn is_attribute(&self, x: T) -> bool where T: Into { + fn is_attribute(&self, x: T) -> bool + where + T: Into, + { self.schema.is_attribute(x) } @@ -529,7 +585,10 @@ struct InProgressTransactWatcher<'a, 'o> { } impl<'a, 'o> InProgressTransactWatcher<'a, 'o> { - fn new(observer_watcher: &'o mut InProgressObserverTransactWatcher, cache_watcher: InProgressCacheTransactWatcher<'a>) -> Self { + fn new( + observer_watcher: &'o mut InProgressObserverTransactWatcher, + cache_watcher: InProgressCacheTransactWatcher<'a>, + ) -> Self { InProgressTransactWatcher { cache_watcher: cache_watcher, observer_watcher: observer_watcher, @@ -540,8 +599,10 @@ impl<'a, 'o> InProgressTransactWatcher<'a, 'o> { impl<'a, 'o> TransactWatcher for InProgressTransactWatcher<'a, 'o> { fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue) { - self.cache_watcher.datom(op.clone(), e.clone(), a.clone(), v); - self.observer_watcher.datom(op.clone(), e.clone(), a.clone(), v); + self.cache_watcher + .datom(op.clone(), e.clone(), a.clone(), v); + self.observer_watcher + .datom(op.clone(), e.clone(), a.clone(), v); } fn done(&mut self, t: &Entid, schema: &Schema) -> ::db_traits::errors::Result<()> { diff --git a/transaction/src/metadata.rs b/transaction/src/metadata.rs index 79946497..99d2309d 100644 --- a/transaction/src/metadata.rs +++ b/transaction/src/metadata.rs @@ -1,41 +1,37 @@ -/// Connection metadata required to query from, or apply transactions to, a Mentat store. -/// -/// Owned data for the volatile parts (generation and partition map), and `Arc` for the infrequently -/// changing parts (schema) that we want to share across threads. -/// -/// See https://github.com/mozilla/mentat/wiki/Thoughts:-modeling-db-conn-in-Rust. - -use std::sync::{ - Arc, -}; - -use mentat_core::{ - Schema, -}; - -use mentat_db::{ - PartitionMap, -}; - -use mentat_db::cache::{ - SQLiteAttributeCache, -}; - -pub struct Metadata { - pub generation: u64, - pub partition_map: PartitionMap, - pub schema: Arc, - pub attribute_cache: SQLiteAttributeCache, -} - -impl Metadata { - // Intentionally not public. - pub fn new(generation: u64, partition_map: PartitionMap, schema: Arc, cache: SQLiteAttributeCache) -> Metadata { - Metadata { - generation: generation, - partition_map: partition_map, - schema: schema, - attribute_cache: cache, - } - } -} +/// Connection metadata required to query from, or apply transactions to, a Mentat store. +/// +/// Owned data for the volatile parts (generation and partition map), and `Arc` for the infrequently +/// changing parts (schema) that we want to share across threads. +/// +/// See https://github.com/mozilla/mentat/wiki/Thoughts:-modeling-db-conn-in-Rust. +use std::sync::Arc; + +use mentat_core::Schema; + +use mentat_db::PartitionMap; + +use mentat_db::cache::SQLiteAttributeCache; + +pub struct Metadata { + pub generation: u64, + pub partition_map: PartitionMap, + pub schema: Arc, + pub attribute_cache: SQLiteAttributeCache, +} + +impl Metadata { + // Intentionally not public. + pub fn new( + generation: u64, + partition_map: PartitionMap, + schema: Arc, + cache: SQLiteAttributeCache, + ) -> Metadata { + Metadata { + generation: generation, + partition_map: partition_map, + schema: schema, + attribute_cache: cache, + } + } +} diff --git a/transaction/src/query.rs b/transaction/src/query.rs index 79026d06..9232101e 100644 --- a/transaction/src/query.rs +++ b/transaction/src/query.rs @@ -13,73 +13,37 @@ use rusqlite::types::ToSql; use std::rc::Rc; -use core_traits::{ - Binding, - Entid, - KnownEntid, - TypedValue, -}; +use core_traits::{Binding, Entid, KnownEntid, TypedValue}; -use mentat_core::{ - HasSchema, - Schema, -}; +use mentat_core::{HasSchema, Schema}; use mentat_query_algebrizer::{ - AlgebraicQuery, - EmptyBecause, - FindQuery, - algebrize_with_inputs, - parse_find_string, + algebrize_with_inputs, parse_find_string, AlgebraicQuery, EmptyBecause, FindQuery, }; -pub use mentat_query_algebrizer::{ - QueryInputs, -}; +pub use mentat_query_algebrizer::QueryInputs; -pub use edn::query::{ - Keyword, - PlainSymbol, - Variable, -}; +pub use edn::query::{Keyword, PlainSymbol, Variable}; use edn::query::{ - Element, - FindSpec, - Pattern, - PatternNonValuePlace, - PatternValuePlace, - WhereClause, + Element, FindSpec, Pattern, PatternNonValuePlace, PatternValuePlace, WhereClause, }; -use mentat_query_projector::{ - ConstantProjector, - Projector, -}; +use mentat_query_projector::{ConstantProjector, Projector}; -use mentat_query_projector::translate::{ - ProjectedSelect, - query_to_select, -}; +use mentat_query_projector::translate::{query_to_select, ProjectedSelect}; -use mentat_sql::{ - SQLQuery, -}; +use mentat_sql::SQLQuery; -pub use mentat_query_algebrizer::{ - Known, -}; +pub use mentat_query_algebrizer::Known; pub use mentat_query_projector::{ - QueryOutput, // Includes the columns/find spec. - QueryResults, // The results themselves. + QueryOutput, // Includes the columns/find spec. + QueryResults, // The results themselves. RelResult, }; -use public_traits::errors::{ - MentatError, - Result, -}; +use public_traits::errors::{MentatError, Result}; pub type QueryExecutionResult = Result; pub type PreparedResult<'sqlite> = Result>; @@ -96,23 +60,31 @@ pub enum PreparedQuery<'sqlite> { schema: Schema, connection: &'sqlite rusqlite::Connection, args: Vec<(String, Rc)>, - projector: Box, + projector: Box, }, } impl<'sqlite> PreparedQuery<'sqlite> { - pub fn run(&mut self, _inputs: T) -> QueryExecutionResult where T: Into> { + pub fn run(&mut self, _inputs: T) -> QueryExecutionResult + where + T: Into>, + { match self { - &mut PreparedQuery::Empty { ref find_spec } => { - Ok(QueryOutput::empty(find_spec)) - }, + &mut PreparedQuery::Empty { ref find_spec } => Ok(QueryOutput::empty(find_spec)), &mut PreparedQuery::Constant { ref select } => { select.project_without_rows().map_err(|e| e.into()) - }, - &mut PreparedQuery::Bound { ref mut statement, ref schema, ref connection, ref args, ref projector } => { + } + &mut PreparedQuery::Bound { + ref mut statement, + ref schema, + ref connection, + ref args, + ref projector, + } => { let rows = run_statement(statement, args)?; - projector.project(schema, connection, rows) - .map_err(|e| e.into()) + projector + .project(schema, connection, rows) + .map_err(|e| e.into()) } } } @@ -169,43 +141,54 @@ pub struct QueryPlanStep { pub detail: String, } -fn algebrize_query -(known: Known, - query: FindQuery, - inputs: T) -> Result - where T: Into> +fn algebrize_query(known: Known, query: FindQuery, inputs: T) -> Result +where + T: Into>, { - let algebrized = algebrize_with_inputs(known, query, 0, inputs.into().unwrap_or(QueryInputs::default()))?; + let algebrized = algebrize_with_inputs( + known, + query, + 0, + inputs.into().unwrap_or(QueryInputs::default()), + )?; let unbound = algebrized.unbound_variables(); // Because we are running once, we can check that all of our `:in` variables are bound at this point. // If they aren't, the user has made an error -- perhaps writing the wrong variable in `:in`, or // not binding in the `QueryInput`. if !unbound.is_empty() { - bail!(MentatError::UnboundVariables(unbound.into_iter().map(|v| v.to_string()).collect())); + bail!(MentatError::UnboundVariables( + unbound.into_iter().map(|v| v.to_string()).collect() + )); } Ok(algebrized) } -fn fetch_values<'sqlite> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - entity: Entid, - attribute: Entid, - only_one: bool) -> QueryExecutionResult { +fn fetch_values<'sqlite>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + entity: Entid, + attribute: Entid, + only_one: bool, +) -> QueryExecutionResult { let v = Variable::from_valid_name("?v"); // This should never fail. // TODO: it should be possible to algebrize with variable entity and attribute, // particularly with known type, allowing the use of prepared statements. - let pattern = Pattern::simple(PatternNonValuePlace::Entid(entity), - PatternNonValuePlace::Entid(attribute), - PatternValuePlace::Variable(v.clone())) - .unwrap(); + let pattern = Pattern::simple( + PatternNonValuePlace::Entid(entity), + PatternNonValuePlace::Entid(attribute), + PatternValuePlace::Variable(v.clone()), + ) + .unwrap(); let element = Element::Variable(v); - let spec = if only_one { FindSpec::FindScalar(element) } else { FindSpec::FindColl(element) }; - let query = FindQuery::simple(spec, - vec![WhereClause::Pattern(pattern)]); + let spec = if only_one { + FindSpec::FindScalar(element) + } else { + FindSpec::FindColl(element) + }; + let query = FindQuery::simple(spec, vec![WhereClause::Pattern(pattern)]); let algebrized = algebrize_query(known, query, None)?; @@ -213,26 +196,32 @@ fn fetch_values<'sqlite> } fn lookup_attribute(schema: &Schema, attribute: &Keyword) -> Result { - schema.get_entid(attribute) - .ok_or_else(|| MentatError::UnknownAttribute(attribute.name().into()).into()) + schema + .get_entid(attribute) + .ok_or_else(|| MentatError::UnknownAttribute(attribute.name().into()).into()) } /// Return a single value for the provided entity and attribute. /// If the attribute is multi-valued, an arbitrary value is returned. /// If no value is present for that entity, `None` is returned. /// If `attribute` isn't an attribute, `None` is returned. -pub fn lookup_value<'sqlite, 'schema, 'cache, E, A> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - entity: E, - attribute: A) -> Result> - where E: Into, - A: Into { +pub fn lookup_value<'sqlite, 'schema, 'cache, E, A>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + entity: E, + attribute: A, +) -> Result> +where + E: Into, + A: Into, +{ let entid = entity.into(); let attrid = attribute.into(); if known.is_attribute_cached_forward(attrid) { - Ok(known.get_value_for_entid(known.schema, attrid, entid).cloned()) + Ok(known + .get_value_for_entid(known.schema, attrid, entid) + .cloned()) } else { fetch_values(sqlite, known, entid, attrid, true) .into_scalar_result() @@ -241,20 +230,24 @@ pub fn lookup_value<'sqlite, 'schema, 'cache, E, A> } } -pub fn lookup_values<'sqlite, E, A> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - entity: E, - attribute: A) -> Result> - where E: Into, - A: Into { +pub fn lookup_values<'sqlite, E, A>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + entity: E, + attribute: A, +) -> Result> +where + E: Into, + A: Into, +{ let entid = entity.into(); let attrid = attribute.into(); if known.is_attribute_cached_forward(attrid) { - Ok(known.get_values_for_entid(known.schema, attrid, entid) - .cloned() - .unwrap_or_else(|| vec![])) + Ok(known + .get_values_for_entid(known.schema, attrid, entid) + .cloned() + .unwrap_or_else(|| vec![])) } else { fetch_values(sqlite, known, entid, attrid, false) .into_coll_result() @@ -267,48 +260,56 @@ pub fn lookup_values<'sqlite, E, A> /// If the attribute is multi-valued, an arbitrary value is returned. /// If no value is present for that entity, `None` is returned. /// If `attribute` doesn't name an attribute, an error is returned. -pub fn lookup_value_for_attribute<'sqlite, 'attribute, E> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - entity: E, - attribute: &'attribute Keyword) -> Result> - where E: Into { +pub fn lookup_value_for_attribute<'sqlite, 'attribute, E>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + entity: E, + attribute: &'attribute Keyword, +) -> Result> +where + E: Into, +{ let attribute = lookup_attribute(known.schema, attribute)?; lookup_value(sqlite, known, entity.into(), attribute) } -pub fn lookup_values_for_attribute<'sqlite, 'attribute, E> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - entity: E, - attribute: &'attribute Keyword) -> Result> - where E: Into { +pub fn lookup_values_for_attribute<'sqlite, 'attribute, E>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + entity: E, + attribute: &'attribute Keyword, +) -> Result> +where + E: Into, +{ let attribute = lookup_attribute(known.schema, attribute)?; lookup_values(sqlite, known, entity.into(), attribute) } -fn run_statement<'sqlite, 'stmt, 'bound> -(statement: &'stmt mut rusqlite::Statement<'sqlite>, - bindings: &'bound [(String, Rc)]) -> Result> { - +fn run_statement<'sqlite, 'stmt, 'bound>( + statement: &'stmt mut rusqlite::Statement<'sqlite>, + bindings: &'bound [(String, Rc)], +) -> Result> { let rows = if bindings.is_empty() { statement.query(rusqlite::params![])? } else { - let refs: Vec<(&str, &ToSql)> = - bindings.iter() - .map(|&(ref k, ref v)| (k.as_str(), v.as_ref() as &ToSql)) - .collect(); + let refs: Vec<(&str, &dyn ToSql)> = bindings + .iter() + .map(|&(ref k, ref v)| (k.as_str(), v.as_ref() as &dyn ToSql)) + .collect(); statement.query_named(&refs)? }; Ok(rows) } -fn run_sql_query<'sqlite, 'sql, 'bound, T, F> -(sqlite: &'sqlite rusqlite::Connection, - sql: &'sql str, - bindings: &'bound [(String, Rc)], - mut mapper: F) -> Result> - where F: FnMut(&rusqlite::Row) -> T +fn run_sql_query<'sqlite, 'sql, 'bound, T, F>( + sqlite: &'sqlite rusqlite::Connection, + sql: &'sql str, + bindings: &'bound [(String, Rc)], + mut mapper: F, +) -> Result> +where + F: FnMut(&rusqlite::Row) -> T, { let mut statement = sqlite.prepare(sql)?; let mut rows = run_statement(&mut statement, &bindings)?; @@ -319,21 +320,27 @@ fn run_sql_query<'sqlite, 'sql, 'bound, T, F> Ok(result) } -fn algebrize_query_str<'query, T> -(known: Known, - query: &'query str, - inputs: T) -> Result - where T: Into> { +fn algebrize_query_str<'query, T>( + known: Known, + query: &'query str, + inputs: T, +) -> Result +where + T: Into>, +{ let parsed = parse_find_string(query)?; algebrize_query(known, parsed, inputs) } -fn run_algebrized_query<'sqlite> -(known: Known, - sqlite: &'sqlite rusqlite::Connection, - algebrized: AlgebraicQuery) -> QueryExecutionResult { - assert!(algebrized.unbound_variables().is_empty(), - "Unbound variables should be checked by now"); +fn run_algebrized_query<'sqlite>( + known: Known, + sqlite: &'sqlite rusqlite::Connection, + algebrized: AlgebraicQuery, +) -> QueryExecutionResult { + assert!( + algebrized.unbound_variables().is_empty(), + "Unbound variables should be checked by now" + ); if algebrized.is_known_empty() { // We don't need to do any SQL work at all. return Ok(QueryOutput::empty(&algebrized.find_spec)); @@ -342,17 +349,18 @@ fn run_algebrized_query<'sqlite> let select = query_to_select(known.schema, algebrized)?; match select { ProjectedSelect::Constant(constant) => { - constant.project_without_rows() - .map_err(|e| e.into()) - }, + constant.project_without_rows().map_err(|e| e.into()) + } ProjectedSelect::Query { query, projector } => { let SQLQuery { sql, args } = query.to_sql_query()?; let mut statement = sqlite.prepare(sql.as_str())?; let rows = run_statement(&mut statement, &args)?; - projector.project(known.schema, sqlite, rows).map_err(|e| e.into()) - }, + projector + .project(known.schema, sqlite, rows) + .map_err(|e| e.into()) + } } } @@ -363,24 +371,28 @@ fn run_algebrized_query<'sqlite> /// instances. /// The caller is responsible for ensuring that the SQLite connection has an open transaction if /// isolation is required. -pub fn q_once<'sqlite, 'query, T> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - query: &'query str, - inputs: T) -> QueryExecutionResult - where T: Into> +pub fn q_once<'sqlite, 'query, T>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + query: &'query str, + inputs: T, +) -> QueryExecutionResult +where + T: Into>, { let algebrized = algebrize_query_str(known, query, inputs)?; run_algebrized_query(known, sqlite, algebrized) } /// Just like `q_once`, but doesn't use any cached values. -pub fn q_uncached<'sqlite, 'schema, 'query, T> -(sqlite: &'sqlite rusqlite::Connection, - schema: &'schema Schema, - query: &'query str, - inputs: T) -> QueryExecutionResult - where T: Into> +pub fn q_uncached<'sqlite, 'schema, 'query, T>( + sqlite: &'sqlite rusqlite::Connection, + schema: &'schema Schema, + query: &'query str, + inputs: T, +) -> QueryExecutionResult +where + T: Into>, { let known = Known::for_schema(schema); let algebrized = algebrize_query_str(known, query, inputs)?; @@ -388,12 +400,14 @@ pub fn q_uncached<'sqlite, 'schema, 'query, T> run_algebrized_query(known, sqlite, algebrized) } -pub fn q_prepare<'sqlite, 'schema, 'cache, 'query, T> -(sqlite: &'sqlite rusqlite::Connection, - known: Known<'schema, 'cache>, - query: &'query str, - inputs: T) -> PreparedResult<'sqlite> - where T: Into> +pub fn q_prepare<'sqlite, 'schema, 'cache, 'query, T>( + sqlite: &'sqlite rusqlite::Connection, + known: Known<'schema, 'cache>, + query: &'query str, + inputs: T, +) -> PreparedResult<'sqlite> +where + T: Into>, { let algebrized = algebrize_query_str(known, query, inputs)?; @@ -401,7 +415,9 @@ pub fn q_prepare<'sqlite, 'schema, 'cache, 'query, T> if !unbound.is_empty() { // TODO: Allow binding variables at execution time, not just // preparation time. - bail!(MentatError::UnboundVariables(unbound.into_iter().map(|v| v.to_string()).collect())); + bail!(MentatError::UnboundVariables( + unbound.into_iter().map(|v| v.to_string()).collect() + )); } if algebrized.is_known_empty() { @@ -413,11 +429,7 @@ pub fn q_prepare<'sqlite, 'schema, 'cache, 'query, T> let select = query_to_select(known.schema, algebrized)?; match select { - ProjectedSelect::Constant(constant) => { - Ok(PreparedQuery::Constant { - select: constant, - }) - }, + ProjectedSelect::Constant(constant) => Ok(PreparedQuery::Constant { select: constant }), ProjectedSelect::Query { query, projector } => { let SQLQuery { sql, args } = query.to_sql_query()?; let statement = sqlite.prepare(sql.as_str())?; @@ -427,40 +439,45 @@ pub fn q_prepare<'sqlite, 'schema, 'cache, 'query, T> schema: known.schema.clone(), connection: sqlite, args, - projector: projector + projector: projector, }) - }, + } } } -pub fn q_explain<'sqlite, 'query, T> -(sqlite: &'sqlite rusqlite::Connection, - known: Known, - query: &'query str, - inputs: T) -> Result - where T: Into> +pub fn q_explain<'sqlite, 'query, T>( + sqlite: &'sqlite rusqlite::Connection, + known: Known, + query: &'query str, + inputs: T, +) -> Result +where + T: Into>, { let algebrized = algebrize_query_str(known, query, inputs)?; if algebrized.is_known_empty() { - return Ok(QueryExplanation::KnownEmpty(algebrized.cc.empty_because.unwrap())); + return Ok(QueryExplanation::KnownEmpty( + algebrized.cc.empty_because.unwrap(), + )); } match query_to_select(known.schema, algebrized)? { ProjectedSelect::Constant(_constant) => Ok(QueryExplanation::KnownConstant), - ProjectedSelect::Query { query, projector: _projector } => { + ProjectedSelect::Query { + query, + projector: _projector, + } => { let query = query.to_sql_query()?; let plan_sql = format!("EXPLAIN QUERY PLAN {}", query.sql); - let steps = run_sql_query(sqlite, &plan_sql, &query.args, |row| { - QueryPlanStep { - select_id: row.get(0).unwrap(), - order: row.get(1).unwrap(), - from: row.get(2).unwrap(), - detail: row.get(3).unwrap() - } + let steps = run_sql_query(sqlite, &plan_sql, &query.args, |row| QueryPlanStep { + select_id: row.get(0).unwrap(), + order: row.get(1).unwrap(), + from: row.get(2).unwrap(), + detail: row.get(3).unwrap(), })?; Ok(QueryExplanation::ExecutionPlan { query, steps }) - }, + } } }