Breathe life back into this project. #1

Merged
gburd merged 22 commits from gburd/2018edition-fmt-fix-deps into master 2020-01-16 16:27:21 +00:00
144 changed files with 15646 additions and 11212 deletions
Showing only changes of commit b2f92b8461 - Show all commits

View file

@ -1,4 +1,5 @@
[package]
edition = "2018"
authors = [
"Richard Newman <rnewman@twinql.com>",
"Nicholas Alexander <nalexander@mozilla.com>",
@ -12,7 +13,7 @@ authors = [
"Thom Chiovoloni <tchiovoloni@mozilla.com>",
]
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"]

View file

@ -1,4 +1,4 @@
# UNMAINTAINED Project Mentat
# Project Mentat
[![Build Status](https://travis-ci.org/mozilla/mentat.svg?branch=master)](https://travis-ci.org/mozilla/mentat)
**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.

View file

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

View file

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

View file

@ -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<V: TransactableValueMarker> Into<ValuePlace<V>> 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<Self> {
@ -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<f64>),
Instant(DateTime<Utc>), // Use `into()` to ensure truncation.
Instant(DateTime<Utc>), // Use `into()` to ensure truncation.
// TODO: &str throughout?
String(ValueRc<String>),
Keyword(ValueRc<Keyword>),
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
}
impl From<KnownEntid> for TypedValue {
@ -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<String> {
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<Utc> {
}
}
/// 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<StructuredMap>),
}
impl<T> From<T> for Binding where T: Into<TypedValue> {
impl<T> From<T> for Binding
where
T: Into<TypedValue>,
{
fn from(value: T) -> Self {
Binding::Scalar(value.into())
}
@ -813,7 +812,11 @@ impl Deref for StructuredMap {
}
impl StructuredMap {
pub fn insert<N, B>(&mut self, name: N, value: B) where N: Into<ValueRc<Keyword>>, B: Into<Binding> {
pub fn insert<N, B>(&mut self, name: N, value: B)
where
N: Into<ValueRc<Keyword>>,
B: Into<Binding>,
{
self.0.insert(name.into(), value.into());
}
}
@ -825,7 +828,10 @@ impl From<IndexMap<ValueRc<Keyword>, Binding>> for StructuredMap {
}
// Mostly for testing.
impl<T> From<Vec<(Keyword, T)>> for StructuredMap where T: Into<Binding> {
impl<T> From<Vec<(Keyword, T)>> for StructuredMap
where
T: Into<Binding>,
{
fn from(value: Vec<(Keyword, T)>) -> Self {
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<String> {
match self {
Binding::Scalar(TypedValue::Uuid(v)) => Some(v.hyphenated().to_string()),
Binding::Scalar(TypedValue::Uuid(v)) => Some(v.to_hyphenated().to_string()),
_ => None,
}
}

View file

@ -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<T: ::enum_set::CLike + Clone> {
/// Return a set containing both `x` and `y`.
@ -41,7 +37,6 @@ impl<T: ::enum_set::CLike + Clone> EnumSetExtensions<T> for EnumSet<T> {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ValueTypeSet(pub EnumSet<ValueType>);

View file

@ -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`.

View file

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

View file

@ -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<TypedValue>>;
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<TypedValue>>;
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<Entid>;
fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet<Entid>>;
fn get_entids_for_value(
&self,
attribute: Entid,
value: &TypedValue,
) -> Option<&BTreeSet<Entid>>;
}
pub trait UpdateableCache<E> {
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E>
where I: Iterator<Item=(Entid, Entid, TypedValue)>;
where
I: Iterator<Item = (Entid, Entid, TypedValue)>;
}

View file

@ -19,11 +19,15 @@ pub struct RcCounter {
/// A simple shared counter.
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.

View file

@ -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<Keyword, Entid>;
@ -119,15 +91,21 @@ pub struct Schema {
pub trait HasSchema {
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid>;
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
where
T: Into<Entid>;
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>;
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid>;
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
where
T: Into<Entid>;
// Returns the attribute and the entid named by the provided ident.
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<T>(&self, x: T) -> bool where T: Into<Entid>;
fn is_attribute<T>(&self, x: T) -> bool
where
T: Into<Entid>;
/// Return true if the provided ident identifies an attribute in this schema.
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<Entid> {
@ -156,10 +141,11 @@ impl Schema {
pub fn update_component_attributes(&mut self) {
let mut components: Vec<Entid>;
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<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid> {
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
where
T: Into<Entid>,
{
self.entid_map.get(&x.into())
}
@ -179,25 +168,33 @@ impl HasSchema for Schema {
self.get_raw_entid(x).map(KnownEntid)
}
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid> {
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
where
T: Into<Entid>,
{
self.attribute_map.get(&x.into())
}
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<T>(&self, x: T) -> bool where T: Into<Entid> {
fn is_attribute<T>(&self, x: T) -> bool
where
T: Into<Entid>,
{
self.attribute_map.contains_key(&x.into())
}
/// 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<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed");
let expected: DateTime<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed");
let dt: DateTime<Utc> =
DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed");
let expected: DateTime<Utc> =
DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed");
let tv: TypedValue = dt.into();
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.

View file

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

View file

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

View file

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

View file

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

View file

@ -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<T> = ::std::result::Result<T, DbError>;
@ -72,40 +58,46 @@ pub enum SchemaConstraintViolation {
/// A transaction tried to assert a datom or datoms with the wrong value `v` type(s).
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<CardinalityConflict>,
},
CardinalityConflicts { conflicts: Vec<CardinalityConflict> },
}
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<DbErrorKind> for DbError {
fn from(kind: DbErrorKind) -> DbError {
DbError { inner: Context::new(kind) }
fn from(kind: DbErrorKind) -> Self {
DbError {
inner: Context::new(kind),
}
}
}
impl From<Context<DbErrorKind>> for DbError {
fn from(inner: Context<DbErrorKind>) -> DbError {
fn from(inner: Context<DbErrorKind>) -> Self {
DbError { inner: inner }
}
}
impl From<rusqlite::Error> 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.

View file

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

View file

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

View file

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

View file

@ -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<Value> {
.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<Vec<(symbols::Keyword, symbols::Keyword, TypedValue)>> {
fn symbolic_schema_to_triples(
ident_map: &IdentMap,
symbolic_schema: &Value,
) -> Result<Vec<(symbols::Keyword, symbols::Keyword, TypedValue)>> {
// Failure here is a coding error, not a runtime error.
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<Vec<Value>>
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<Entity<edn::ValueAndSpan>> {
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<Entity<edn::ValueAndSpan>> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions");
let bootstrap_entities: Vec<Entity<edn::ValueAndSpan>> =
edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions");
return bootstrap_entities;
}

View file

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

View file

@ -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<Vec<(Entid, Entid, TypedValue)>> {
let mut stmt: rusqlite::Statement =
conn.prepare(format!("SELECT e, a, v, value_type_tag FROM {}", table).as_str())?;
let m: Result<Vec<_>> = stmt.query_and_then(rusqlite::params![], row_to_datom_assertion)?.collect();
let m: Result<Vec<_>> = 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<Vec<_>> = 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");
}

View file

@ -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<TxReport, String>`.
@ -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<S: Borrow<Schema>>(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<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result<Datoms> {
pub fn datoms_after<S: Borrow<Schema>>(
conn: &rusqlite::Connection,
schema: &S,
tx: i64,
) -> Result<Datoms> {
let borrowed_schema = schema.borrow();
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx FROM datoms WHERE tx > ? ORDER BY e ASC, a ASC, value_type_tag ASC, v ASC, tx ASC")?;
let r: Result<Vec<_>> = stmt.query_and_then(&[&tx], |row| {
let e: i64 = row.get(0)?;
let a: i64 = row.get(1)?;
let r: Result<Vec<_>> = 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<S: Borrow<Schema>>(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<S: Borrow<Schema>>(conn: &rusqlite::Connection, schema: &S, tx: i64) -> Result<Transactions> {
pub fn transactions_after<S: Borrow<Schema>>(
conn: &rusqlite::Connection,
schema: &S,
tx: i64,
) -> Result<Transactions> {
let borrowed_schema = schema.borrow();
let mut stmt: rusqlite::Statement = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM transactions WHERE tx > ? ORDER BY tx ASC, e ASC, a ASC, value_type_tag ASC, v ASC, added ASC")?;
let r: Result<Vec<_>> = stmt.query_and_then(&[&tx], |row| {
let e: i64 = row.get(0)?;
let a: i64 = row.get(1)?;
let r: Result<Vec<_>> = 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<Datoms> = r?.into_iter().group_by(|x| x.tx).into_iter().map(|(_key, group)| Datoms(group.collect())).collect();
let r: Vec<Datoms> = r?
.into_iter()
.group_by(|x| x.tx)
.into_iter()
.map(|(_key, group)| Datoms(group.collect()))
.collect();
Ok(Transactions(r))
}
/// Return the set of fulltext values in the store, ordered by rowid.
pub fn fulltext_values(conn: &rusqlite::Connection) -> Result<FulltextValues> {
let mut stmt: rusqlite::Statement = conn.prepare("SELECT rowid, text FROM fulltext_values ORDER BY rowid")?;
let mut stmt: rusqlite::Statement =
conn.prepare("SELECT rowid, text FROM fulltext_values ORDER BY rowid")?;
let params: &[i32; 0] = &[];
let r: Result<Vec<_>> = stmt.query_and_then(params, |row| {
let rowid: i64 = row.get(0)?;
let text: String = row.get(1)?;
Ok((rowid, text))
})?.collect();
let r: Result<Vec<_>> = 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<FulltextValues> {
///
/// 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<String> {
pub fn dump_sql_query(
conn: &rusqlite::Connection,
sql: &str,
params: &[&dyn ToSql],
) -> Result<String> {
let mut stmt: rusqlite::Statement = conn.prepare(sql)?;
let mut 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<Vec<_>> = 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<Vec<_>> = 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<I>(&mut self, transaction: I) -> Result<TxReport> where I: Borrow<str> {
pub fn transact<I>(&mut self, transaction: I) -> Result<TxReport>
where
I: Borrow<str>,
{
// Failure to parse the transaction is a coding error, so we unwrap.
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<I>(&mut self, terms: I, tempid_set: InternSet<TempId>) -> Result<TxReport> where I: IntoIterator<Item=TermWithTempIds> {
pub fn transact_simple_terms<I>(
&mut self,
terms: I,
tempid_set: InternSet<TempId>,
) -> Result<TxReport>
where
I: IntoIterator<Item = TermWithTempIds>,
{
let details = {
// 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 {

View file

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

View file

@ -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<TypedValue> {
@ -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<TempId> {
self.inner.as_text().cloned().map(TempId::External).map(|v| v.into())
self.inner
.as_text()
.cloned()
.map(TempId::External)
.map(|v| v.into())
}
}
@ -124,13 +101,17 @@ impl TransactableValue for TypedValue {
fn into_entity_place(self) -> Result<EntityPlace<Self>> {
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<AVPair>;
/// 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<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>;
pub type TermWithTempIdsAndLookupRefs =
Term<KnownEntidOr<LookupRefOrTempId>, TypedValueOr<LookupRefOrTempId>>;
pub type TermWithTempIds = Term<KnownEntidOr<TempIdHandle>, TypedValueOr<TempIdHandle>>;
pub type TermWithoutTempIds = Term<KnownEntid, TypedValue>;
pub type Population = Vec<TermWithTempIds>;
@ -186,7 +168,7 @@ impl TermWithTempIds {
impl TermWithoutTempIds {
pub(crate) fn rewrap<A, B>(self) -> Term<KnownEntidOr<A>, TypedValueOr<B>> {
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<T, U>(lookup_map: &AVMap, desired_or: Either<T, LookupRefOrTempId>, lift: U) -> errors::Result<Either<T, TempIdHandle>> where U: FnOnce(Entid) -> T {
pub fn replace_lookup_ref<T, U>(
lookup_map: &AVMap,
desired_or: Either<T, LookupRefOrTempId>,
lift: U,
) -> errors::Result<Either<T, TempIdHandle>>
where
U: FnOnce(Entid) -> T,
{
match desired_or {
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<Entid, AddAndRetract>>;
pub(crate) type AEVTrie<'schema> =
BTreeMap<(Entid, &'schema Attribute), BTreeMap<Entid, AddAndRetract>>;

View file

@ -11,113 +11,87 @@
extern crate failure;
extern crate 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<symbols::Keyword> {
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,
};

View file

@ -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<EAV>, ident_retractions: &BTreeMap<Entid, symbols::Keyword>) -> Result<Vec<EAV>> {
fn update_attribute_map_from_schema_retractions(
attribute_map: &mut AttributeMap,
retractions: Vec<EAV>,
ident_retractions: &BTreeMap<Entid, symbols::Keyword>,
) -> Result<Vec<EAV>> {
// Process retractions of schema attributes first. It's allowed to retract a schema attribute
// 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<EAV>, retractions: Vec<EAV>) -> Result<MetadataReport> {
fn attribute_builder_to_modify(attribute_id: Entid, existing: &AttributeMap) -> AttributeBuilder {
existing.get(&attribute_id)
.map(AttributeBuilder::to_modify_attribute)
.unwrap_or_else(AttributeBuilder::default)
pub fn update_attribute_map_from_entid_triples(
attribute_map: &mut AttributeMap,
assertions: Vec<EAV>,
retractions: Vec<EAV>,
) -> Result<MetadataReport> {
fn attribute_builder_to_modify(
attribute_id: Entid,
existing: &AttributeMap,
) -> AttributeBuilder {
existing
.get(&attribute_id)
.map(AttributeBuilder::to_modify_attribute)
.unwrap_or_else(AttributeBuilder::default)
}
// Group mutations by impacted entid.
@ -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<Entid> = BTreeSet::default();
let mut attributes_altered: BTreeMap<Entid, Vec<AttributeAlteration>> = BTreeMap::default();
@ -312,20 +309,30 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut AttributeMap,
match attribute_map.entry(entid) {
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<U>(schema: &mut Schema, assertions: U) -> Result<MetadataReport>
where U: IntoIterator<Item=(Entid, Entid, TypedValue, bool)> {
pub fn update_schema_from_entid_quadruples<U>(
schema: &mut Schema,
assertions: U,
) -> Result<MetadataReport>
where
U: IntoIterator<Item = (Entid, Entid, TypedValue, bool)>,
{
// Group attribute assertions into asserted, retracted, and updated. We assume all our
// 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<Entid, symbols::Keyword> = AddRetractAlterSet::default();
for (e, a, typed_value, added) in assertions.into_iter() {
@ -359,7 +371,7 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
if a == entids::DB_IDENT {
if 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<U>(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<Entid, IdentAlteration> = BTreeMap::new();
@ -420,6 +445,6 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
Ok(MetadataReport {
idents_altered: idents_altered,
.. report
..report
})
}

View file

@ -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<F>(&self, ident: F) -> Result<()> where F: Fn() -> String;
fn validate<F>(&self, ident: F) -> Result<()>
where
F: Fn() -> String;
}
impl AttributeValidation for Attribute {
fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String {
fn validate<F>(&self, ident: F) -> Result<()>
where
F: Fn() -> String,
{
if self.unique == Some(attribute::Unique::Value) && !self.index {
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<ValueType>,
@ -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<KnownEntid>;
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>;
fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result<Schema>;
fn from_ident_map_and_attribute_map(
ident_map: IdentMap,
attribute_map: AttributeMap,
) -> Result<Schema>;
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>;
where
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>;
}
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<KnownEntid> {
self.get_entid(&ident).ok_or(DbErrorKind::UnrecognizedIdent(ident.to_string()).into())
self.get_entid(&ident)
.ok_or(DbErrorKind::UnrecognizedIdent(ident.to_string()).into())
}
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
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<Schema> {
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<Schema> {
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<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>{
where
U: IntoIterator<Item = (symbols::Keyword, symbols::Keyword, TypedValue)>,
{
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions
.into_iter()
.map(|(symbolic_ident, symbolic_attr, value)| {
let ident: i64 = *ident_map
.get(&symbolic_ident)
.ok_or(DbErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?;
let attr: i64 = *ident_map
.get(&symbolic_attr)
.ok_or(DbErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?;
Ok((ident, attr, value))
})
.collect();
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions.into_iter().map(|(symbolic_ident, symbolic_attr, value)| {
let 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<TypedValue>;
fn to_typed_value(
&self,
value: &edn::ValueAndSpan,
value_type: ValueType,
) -> Result<TypedValue>;
}
impl SchemaTypeChecking for Schema {
fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue> {
fn to_typed_value(
&self,
value: &edn::ValueAndSpan,
value_type: ValueType,
) -> Result<TypedValue> {
// TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including
// 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()
))
);
}
}

View file

@ -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<Entid>, timeline: Entid) -> Result<Vec<Entid>> {
fn collect_ordered_txs_to_move(
conn: &rusqlite::Connection,
txs_from: RangeFrom<Entid>,
timeline: Entid,
) -> Result<Vec<Entid>> {
let mut stmt = conn.prepare("SELECT tx, timeline FROM timelined_transactions WHERE tx >= ? AND timeline = ? GROUP BY tx ORDER BY tx DESC")?;
let mut 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::<Vec<_>>())
),
&(tx_ids
.iter()
.map(|x| x as &dyn rusqlite::types::ToSql)
.collect::<Vec<_>>()),
)?;
Ok(())
}
@ -104,28 +95,34 @@ fn remove_tx_from_datoms(conn: &rusqlite::Connection, tx_id: Entid) -> Result<()
}
fn is_timeline_empty(conn: &rusqlite::Connection, timeline: Entid) -> Result<bool> {
let mut stmt = conn.prepare("SELECT timeline FROM timelined_transactions WHERE timeline = ? GROUP BY timeline")?;
let rows = stmt.query_and_then(&[&timeline], |row| -> Result<i64> {
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<i64> { 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<Vec<TermWithoutTempIds>> {
fn reversed_terms_for(
conn: &rusqlite::Connection,
tx_id: Entid,
) -> Result<Vec<TermWithoutTempIds>> {
let mut stmt = conn.prepare("SELECT e, a, v, value_type_tag, tx, added FROM timelined_transactions WHERE tx = ? AND timeline = ? ORDER BY tx DESC")?;
let mut rows = stmt.query_and_then(&[&tx_id, &::TIMELINE_MAIN], |row| -> Result<TermWithoutTempIds> {
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<TermWithoutTempIds> {
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<Vec<T
}
/// Move specified transaction RangeFrom off of main timeline.
pub fn move_from_main_timeline(conn: &rusqlite::Connection, schema: &Schema,
partition_map: PartitionMap, txs_from: RangeFrom<Entid>, new_timeline: Entid) -> Result<(Option<Schema>, PartitionMap)> {
pub fn move_from_main_timeline(
conn: &rusqlite::Connection,
schema: &Schema,
partition_map: PartitionMap,
txs_from: RangeFrom<Entid>,
new_timeline: Entid,
) -> Result<(Option<Schema>, 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<Schema>, 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);

File diff suppressed because it is too large Load diff

View file

@ -8,24 +8,13 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// 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<CardinalityConflict> {
pub(crate) fn cardinality_conflicts<'schema>(
aev_trie: &AEVTrie<'schema>,
) -> Vec<CardinalityConflict> {
let mut errors = vec![];
for (&(a, attribute), evs) in aev_trie {

View file

@ -8,64 +8,50 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// 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<Box<Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>,
notify_fn: Arc<Box<dyn Fn(&str, IndexMap<&Entid, &AttributeSet>) + Send + Sync>>,
attributes: AttributeSet,
}
impl TxObserver {
pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver where F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync {
pub fn new<F>(attributes: AttributeSet, notify_fn: F) -> TxObserver
where
F: Fn(&str, IndexMap<&Entid, &AttributeSet>) + 'static + Send + Sync,
{
TxObserver {
notify_fn: Arc::new(Box::new(notify_fn)),
attributes,
}
}
pub fn applicable_reports<'r>(&self, reports: &'r IndexMap<Entid, AttributeSet>) -> 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<Entid, AttributeSet>,
) -> 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<IndexMap<String, Arc<TxObserver>>>, reports: IndexMap<Entid, AttributeSet>) -> Self {
fn new(
observers: &Arc<IndexMap<String, Arc<TxObserver>>>,
reports: IndexMap<Entid, AttributeSet>,
) -> Self {
TxCommand {
reports,
observers: Arc::downgrade(observers),
@ -106,7 +95,7 @@ impl Command for TxCommand {
pub struct TxObservationService {
observers: Arc<IndexMap<String, Arc<TxObserver>>>,
executor: Option<Sender<Box<Command + Send>>>,
executor: Option<Sender<Box<dyn Command + Send>>>,
}
impl TxObservationService {
@ -141,7 +130,10 @@ impl TxObservationService {
}
let executor = self.executor.get_or_insert_with(|| {
let (tx, rx): (Sender<Box<Command + Send>>, Receiver<Box<Command + Send>>) = channel();
let (tx, rx): (
Sender<Box<dyn Command + Send>>,
Receiver<Box<dyn Command + Send>>,
) = channel();
let mut worker = CommandExecutor::new(rx);
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<Box<Command + Send>>,
receiver: Receiver<Box<dyn Command + Send>>,
}
impl CommandExecutor {
fn new(rx: Receiver<Box<Command + Send>>) -> Self {
CommandExecutor {
receiver: rx,
}
fn new(rx: Receiver<Box<dyn Command + Send>>) -> 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(),
}
}
}

View file

@ -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<String, Partition>);
impl Deref for PartitionMap {
@ -113,7 +105,7 @@ impl DerefMut for PartitionMap {
}
impl FromIterator<(String, Partition)> for PartitionMap {
fn from_iter<T: IntoIterator<Item=(String, Partition)>>(iter: T) -> Self {
fn from_iter<T: IntoIterator<Item = (String, Partition)>>(iter: T) -> Self {
PartitionMap(iter.into_iter().collect())
}
}
@ -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<Entid>;
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<TypedValue>;
fn into_typed_value(self, schema: &Schema, value_type: ValueType)
-> errors::Result<TypedValue>;
/// Make an entity place out of this value place. This is where we limit values in nested maps
/// to valid entity places.

View file

@ -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<UpsertE>,
@ -90,7 +69,7 @@ pub(crate) struct Generation {
resolved: Vec<TermWithoutTempIds>,
}
#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub(crate) struct FinalPopulations {
/// Upserts that upserted.
pub upserted: Vec<TermWithoutTempIds>,
@ -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<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)> where I: IntoIterator<Item=TermWithTempIds> {
pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)>
where
I: IntoIterator<Item = TermWithTempIds>,
{
let mut generation = Generation::default();
let mut 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<BTreeMap<TempIdHandle, usize>> {
pub(crate) fn temp_ids_in_allocations(
&self,
schema: &Schema,
) -> Result<BTreeMap<TempIdHandle, usize>> {
assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
let mut temp_ids: BTreeSet<TempIdHandle> = BTreeSet::default();
let mut tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> = BTreeMap::default();
let mut tempid_avs: BTreeMap<(Entid, TypedValueOr<TempIdHandle>), Vec<TempIdHandle>> =
BTreeMap::default();
for term in self.allocations.iter() {
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<TempIdHandle, usize> = temp_ids.into_iter().enumerate().map(|(i, tempid)| (tempid, i)).collect();
let temp_ids: BTreeMap<TempIdHandle, usize> = temp_ids
.into_iter()
.enumerate()
.map(|(i, tempid)| (tempid, i))
.collect();
debug!("need to label tempids aggregated using tempid_avs {:?}", tempid_avs);
debug!(
"need to label tempids aggregated using tempid_avs {:?}",
tempid_avs
);
for vs in tempid_avs.values() {
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<FinalPopulations> {
pub(crate) fn into_final_populations(
self,
temp_id_map: &TempIdMap,
) -> Result<FinalPopulations> {
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);

View file

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

View file

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

View file

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

View file

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

View file

@ -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<T> where T: Eq + Hash {
pub struct InternSet<T>
where
T: Eq + Hash,
{
inner: HashSet<ValueRc<T>>,
}
impl<T> Deref for InternSet<T> where T: Eq + Hash {
impl<T> Deref for InternSet<T>
where
T: Eq + Hash,
{
type Target = HashSet<ValueRc<T>>;
fn deref(&self) -> &Self::Target {
@ -41,13 +42,19 @@ impl<T> Deref for InternSet<T> where T: Eq + Hash {
}
}
impl<T> DerefMut for InternSet<T> where T: Eq + Hash {
impl<T> DerefMut for InternSet<T>
where
T: Eq + Hash,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T> InternSet<T> where T: Eq + Hash {
impl<T> InternSet<T>
where
T: Eq + Hash,
{
pub fn new() -> InternSet<T> {
InternSet {
inner: HashSet::new(),

View file

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

View file

@ -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<HashMap<&'a String, &'a Value>>
placeholders: RefCell<HashMap<&'a String, &'a Value>>,
}
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<T>(value: &'a Value, pattern: &'a Value) -> bool
where T: PatternMatchingRules<'a, Value> {
where
T: PatternMatchingRules<'a, Value>,
{
let matcher = Matcher::new();
matcher.match_internal::<T>(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<T>(&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::<T>(a, b)).is_none(),
(&List(ref v), &List(ref p)) =>
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none(),
(&Set(ref v), &Set(ref p)) =>
v.len() == p.len() &&
v.iter().all(|a| p.iter().any(|b| self.match_internal::<T>(a, b))) &&
p.iter().all(|b| v.iter().any(|a| self.match_internal::<T>(a, b))),
(&Map(ref v), &Map(ref p)) =>
v.len() == p.len() &&
v.iter().all(|a| p.iter().any(|b| self.match_internal::<T>(a.0, b.0) && self.match_internal::<T>(a.1, b.1))) &&
p.iter().all(|b| v.iter().any(|a| self.match_internal::<T>(a.0, b.0) && self.match_internal::<T>(a.1, b.1))),
_ => value == pattern
(&Vector(ref v), &Vector(ref p)) => {
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none()
}
(&List(ref v), &List(ref p)) => {
diff_with(v, p, |a, b| self.match_internal::<T>(a, b)).is_none()
}
(&Set(ref v), &Set(ref p)) => {
v.len() == p.len()
&& v.iter()
.all(|a| p.iter().any(|b| self.match_internal::<T>(a, b)))
&& p.iter()
.all(|b| v.iter().any(|a| self.match_internal::<T>(a, b)))
}
(&Map(ref v), &Map(ref p)) => {
v.len() == p.len()
&& v.iter().all(|a| {
p.iter().any(|b| {
self.match_internal::<T>(a.0, b.0)
&& self.match_internal::<T>(a.1, b.1)
})
})
&& p.iter().all(|b| {
v.iter().any(|a| {
self.match_internal::<T>(a.0, b.0)
&& self.match_internal::<T>(a.1, b.1)
})
})
}
_ => value == pattern,
}
}
}
@ -127,7 +153,7 @@ mod test {
};
( $pattern:tt !~ $value:tt ) => {
assert_match!($pattern, $value, false);
}
};
}
#[test]

View file

@ -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<T>(name: T) -> Self where T: Into<String> {
pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
let n = name.into();
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
@ -67,14 +59,21 @@ impl NamespaceableName {
}
#[inline]
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let n = name.as_ref();
let 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<N, T>(namespace: Option<N>, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
fn new<N, T>(namespace: Option<N>, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
if let Some(ns) = namespace {
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<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let separated = SerializedNamespaceableName::deserialize(deserializer)?;
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ser = SerializedNamespaceableName {
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(),
]
);
}
}

View file

@ -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<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error> where W: io::Write {
fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error>
where
W: io::Write,
{
self.as_doc(&pretty::BoxAllocator).1.render(width, out)
}
@ -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<Cow<'a, str>>, I: IntoIterator<Item=&'a Value> {
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>,
<A as pretty::DocAllocator<'a>>::Doc: std::clone::Clone,
T: Into<Cow<'a, str>>,
I: IntoIterator<Item = &'a Value>,
{
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>,
<A as 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 _])]"
);
}
}

View file

@ -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<PlainSymbol>);
@ -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<FnArg> for FnArg {
fn from_value(v: &::ValueAndSpan) -> Option<FnArg> {
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<Keyword>),
}
@ -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<PatternNonValuePlace> for PatternNonValuePlace {
fn from_value(v: &::ValueAndSpan) -> Option<PatternNonValuePlace> {
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<Keyword> for PatternValuePlace {
impl FromValue<PatternValuePlace> for PatternValuePlace {
fn from_value(v: &::ValueAndSpan) -> Option<PatternValuePlace> {
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<PatternNonValuePlace> {
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<PatternNonValuePlace> {
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<Variable> 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<Iterator<Item=&Element> + 's> {
pub fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + '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<Option<Variable>> {
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<Pattern> {
pub fn simple(
e: PatternNonValuePlace,
a: PatternNonValuePlace,
v: PatternValuePlace,
) -> Option<Pattern> {
Pattern::new(None, e, a, v, PatternNonValuePlace::Placeholder)
}
pub fn new(src: Option<SrcVar>,
e: PatternNonValuePlace,
a: PatternNonValuePlace,
v: PatternValuePlace,
tx: PatternNonValuePlace) -> Option<Pattern> {
let aa = a.clone(); // Too tired of fighting borrow scope for now.
pub fn new(
src: Option<SrcVar>,
e: PatternNonValuePlace,
a: PatternNonValuePlace,
v: PatternValuePlace,
tx: PatternNonValuePlace,
) -> Option<Pattern> {
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<QueryPart>) -> std::result::Result<ParsedQuery, &'static str> {
pub(crate) fn from_parts(
parts: Vec<QueryPart>,
) -> std::result::Result<ParsedQuery, &'static str> {
let mut find_spec: Option<FindSpec> = None;
let mut with: Option<Vec<Variable>> = None;
let mut in_vars: Option<Vec<Variable>> = None;
@ -1020,37 +1004,37 @@ impl ParsedQuery {
return Err("find query has repeated :find");
}
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<Variable>) {
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<Variable>) {
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<OrWhereClause>, UnifyVars, BTreeSet<Variable>) {
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<Variable>) {
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);
}
}
},
}
}
}
}

View file

@ -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<T>(name: T) -> Self where T: Into<String> {
pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
let n = name.into();
assert!(!n.is_empty(), "Symbols cannot be unnamed.");
@ -107,9 +106,16 @@ impl PlainSymbol {
}
impl NamespacedSymbol {
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let r = namespace.as_ref();
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<T>(name: T) -> Self where T: Into<String> {
pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
Keyword(NamespaceableName::plain(name))
}
}
@ -147,9 +156,16 @@ impl Keyword {
/// ```
///
/// See also the `kw!` macro in the main `mentat` crate.
pub fn namespaced<N, T>(namespace: N, name: T) -> Self where N: AsRef<str>, T: AsRef<str> {
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let r = namespace.as_ref();
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()
);
}

View file

@ -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<I>(spanned_value: SpannedValue, span: I) -> ValueAndSpan where I: Into<Option<Span>> {
pub fn new<I>(spanned_value: SpannedValue, span: I) -> ValueAndSpan
where
I: Into<Option<Span>>,
{
ValueAndSpan {
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<SpannedValue> 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<Value>);
@ -674,22 +691,19 @@ impl ToMillis for DateTime<Utc> {
#[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]

View file

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

View file

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

View file

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

View file

@ -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::<Keyword>(bad_ns_json);
assert!(not_kw.is_err());
}

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

@ -9,15 +9,10 @@
// specific language governing permissions and limitations under the License.
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<T>(r_string: T) -> *mut c_char where T: Into<String> {
pub fn string_to_c_char<T>(r_string: T) -> *mut c_char
where
T: Into<String>,
{
CString::new(r_string.into()).unwrap().into_raw()
}
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<T, E>(result: Result<T, E>, 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<T, E>(result: Result<Option<T>, E>, error: *mut ExternError) -> *mut T
where E: Display {
pub unsafe fn translate_opt_result<T, E>(
result: Result<Option<T>, 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<E>(result: Result<(), E>, error: *mut ExternError) where E: Display {
pub unsafe fn translate_void_result<E>(result: Result<(), E>, error: *mut ExternError)
where
E: Display,
{
// Note that Box<T> guarantees that if T is zero sized, it's not heap allocated. So not
// only do we never need to free the return value of this, it would be a problem if someone did.
translate_result(result, error);
}
}

View file

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

View file

@ -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<std::io::Error> for MentatError {
fn from(error: std::io::Error) -> MentatError {
fn from(error: std::io::Error) -> Self {
MentatError::IoError(error)
}
}
impl From<rusqlite::Error> 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<uuid::ParseError> for MentatError {
fn from(error: uuid::ParseError) -> MentatError {
impl From<uuid::Error> for MentatError {
fn from(error: uuid::Error) -> Self {
MentatError::UuidError(error)
}
}
impl From<edn::ParseError> for MentatError {
fn from(error: edn::ParseError) -> MentatError {
fn from(error: edn::ParseError) -> Self {
MentatError::EdnParseError(error)
}
}
impl From<DbError> for MentatError {
fn from(error: DbError) -> MentatError {
fn from(error: DbError) -> Self {
MentatError::DbError(error)
}
}
impl From<AlgebrizerError> for MentatError {
fn from(error: AlgebrizerError) -> MentatError {
fn from(error: AlgebrizerError) -> Self {
MentatError::AlgebrizerError(error)
}
}
impl From<ProjectorError> for MentatError {
fn from(error: ProjectorError) -> MentatError {
fn from(error: ProjectorError) -> Self {
MentatError::ProjectorError(error)
}
}
impl From<PullError> for MentatError {
fn from(error: PullError) -> MentatError {
fn from(error: PullError) -> Self {
MentatError::PullError(error)
}
}
impl From<SQLError> for MentatError {
fn from(error: SQLError) -> MentatError {
fn from(error: SQLError) -> Self {
MentatError::SQLError(error)
}
}
#[cfg(feature = "syncable")]
impl From<TolstoyError> for MentatError {
fn from(error: TolstoyError) -> MentatError {
fn from(error: TolstoyError) -> Self {
MentatError::TolstoyError(error)
}
}
#[cfg(feature = "syncable")]
impl From<serde_json::Error> for MentatError {
fn from(error: serde_json::Error) -> MentatError {
fn from(error: serde_json::Error) -> Self {
MentatError::SerializationError(error)
}
}
#[cfg(feature = "syncable")]
impl From<hyper::Error> for MentatError {
fn from(error: hyper::Error) -> MentatError {
fn from(error: hyper::Error) -> Self {
MentatError::NetworkError(error)
}
}
#[cfg(feature = "syncable")]
impl From<hyper::error::UriError> for MentatError {
fn from(error: hyper::error::UriError) -> MentatError {
impl From<http::uri::InvalidUri> for MentatError {
fn from(error: http::uri::InvalidUri) -> Self {
MentatError::UriError(error)
}
}

View file

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

View file

@ -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<T> = std::result::Result<T, AlgebrizerError>;
@ -42,7 +35,10 @@ pub enum BindingError {
/// Expected `[?x1 … ?xN]` or `[[?x1 … ?xN]]` but got some other number of bindings. Mentat is
/// 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")]

View file

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

View file

@ -8,49 +8,30 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// 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<ValueTypeSet> {
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<ValueConversion> {
pub(crate) fn typed_value_from_arg<'s>(
&self,
schema: &'s Schema,
var: &Variable,
arg: FnArg,
known_types: ValueTypeSet,
) -> Result<ValueConversion> {
use self::ValueConversion::*;
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)
},
}
}
}
}

View file

@ -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<TypedValue, QualifiedAlias> = 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());

View file

@ -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<Variable>, types: Vec<ValueType>, values: Vec<TypedValue>) {
fn collect_named_bindings<'s>(
&mut self,
schema: &'s Schema,
names: Vec<Variable>,
types: Vec<ValueType>,
values: Vec<TypedValue>,
) {
if values.is_empty() {
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<EmptyBecause> = None;
let values = children.into_iter()
.filter_map(|arg| -> Option<Result<TypedValue>> {
// 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::<Result<Vec<TypedValue>>>()?;
let values = children
.into_iter()
.filter_map(|arg| -> Option<Result<TypedValue>> {
// 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::<Result<Vec<TypedValue>>>()?;
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<Option<(Variable, ValueTypeSet)>> =
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<Option<(Variable, ValueTypeSet)>> = 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<Variable> = places.into_iter().filter_map(|x| x.into_var()).collect();
let names: Vec<Variable> =
places.into_iter().filter_map(|x| x.into_var()).collect();
let expected_width = names.len();
let expected_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.
}
}

View file

@ -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<Variable, TypedValue>) -> 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<Variable, ValueType>,
values: BTreeMap<Variable, TypedValue>) -> Result<QueryInputs> {
pub fn new(
mut types: BTreeMap<Variable, ValueType>,
values: BTreeMap<Variable, TypedValue>,
) -> Result<QueryInputs> {
// Make sure that the types of the values agree with those in types, and collect.
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,
})
}
}

View file

@ -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<T>(in_variables: BTreeSet<Variable>, inputs: T) -> ConjoiningClauses
where T: Into<Option<QueryInputs>> {
where
T: Into<Option<QueryInputs>>,
{
ConjoiningClauses::with_inputs_and_alias_counter(in_variables, inputs, RcCounter::new())
}
pub(crate) fn with_inputs_and_alias_counter<T>(in_variables: BTreeSet<Variable>,
inputs: T,
alias_counter: RcCounter) -> ConjoiningClauses
where T: Into<Option<QueryInputs>> {
pub(crate) fn with_inputs_and_alias_counter<T>(
in_variables: BTreeSet<Variable>,
inputs: T,
alias_counter: RcCounter,
) -> ConjoiningClauses
where
T: Into<Option<QueryInputs>>,
{
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<C: Into<Column>>(&mut self, schema: &Schema, table: TableAlias, column: C, var: Variable) {
pub(crate) fn bind_column_to_var<C: Into<Column>>(
&mut self,
schema: &Schema,
table: TableAlias,
column: C,
var: Variable,
) {
let column = column.into();
// 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<C: Into<Column>>(&mut self, table: TableAlias, column: C, constant: TypedValue) {
pub(crate) fn constrain_column_to_constant<C: Into<Column>>(
&mut self,
table: TableAlias,
column: C,
constant: TypedValue,
) {
match constant {
// 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<C: Into<Column>>(&mut self, table: TableAlias, column: C, entity: Entid) {
pub(crate) fn constrain_column_to_entity<C: Into<Column>>(
&mut self,
table: TableAlias,
column: C,
entity: Entid,
) {
let column = column.into();
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<EmptyBecause> {
pub(crate) fn can_constrain_var_to_type(
&self,
var: &Variable,
this_type: ValueType,
) -> Option<EmptyBecause> {
self.can_constrain_var_to_types(var, ValueTypeSet::of_one(this_type))
}
fn can_constrain_var_to_types(&self, var: &Variable, these_types: ValueTypeSet) -> Option<EmptyBecause> {
fn can_constrain_var_to_types(
&self,
var: &Variable,
these_types: ValueTypeSet,
) -> Option<EmptyBecause> {
if let Some(existing) = self.known_types.get(var) {
if 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<KnownEntid> {
fn entid_for_ident<'s, 'a>(
&self,
schema: &'s Schema,
ident: &'a Keyword,
) -> Option<KnownEntid> {
schema.get_entid(&ident)
}
fn table_for_attribute_and_value<'s, 'a>(&self, attribute: &'s Attribute, value: &'a EvolvedValuePlace) -> ::std::result::Result<DatomsTable, EmptyBecause> {
fn table_for_attribute_and_value<'s, 'a>(
&self,
attribute: &'s Attribute,
value: &'a EvolvedValuePlace,
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
if attribute.fulltext {
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<DatomsTable, EmptyBecause> {
fn table_for_unknown_attribute<'s, 'a>(
&self,
value: &'a EvolvedValuePlace,
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
// If the value is known to be non-textual, we can simply use the regular datoms
// 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<DatomsTable, EmptyBecause> {
fn table_for_places<'s, 'a>(
&self,
schema: &'s Schema,
attribute: &'a EvolvedNonValuePlace,
value: &'a EvolvedValuePlace,
) -> ::std::result::Result<DatomsTable, EmptyBecause> {
match attribute {
&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<SourceAlias> {
fn alias_table<'s, 'a>(
&mut self,
schema: &'s Schema,
pattern: &'a EvolvedPattern,
) -> Option<SourceAlias> {
self.table_for_places(schema, &pattern.attribute, &pattern.value)
.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<ValueType> {
fn get_value_type<'s, 'a>(
&self,
schema: &'s Schema,
pattern: &'a EvolvedPattern,
) -> Option<ValueType> {
self.get_attribute(schema, pattern).map(|a| a.value_type)
}
}
/// 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<EvolvedPattern>) -> Result<()> {
fn apply_evolved_patterns(
&mut self,
known: Known,
mut patterns: VecDeque<EvolvedPattern>,
) -> 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<WhereClause>) -> Result<()> {
pub(crate) fn apply_clauses(
&mut self,
known: Known,
where_clauses: Vec<WhereClause>,
) -> Result<()> {
// We apply (top level) type predicates first as an optimization.
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!(),
}
}

View file

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

View file

@ -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<Pattern>,
mentioned_vars: BTreeSet<Variable>)
-> Result<()> {
fn apply_simple_or_join(
&mut self,
known: Known,
patterns: Vec<Pattern>,
mentioned_vars: BTreeSet<Variable>,
) -> Result<()> {
if self.is_known_empty() {
return Ok(())
return Ok(());
}
assert!(patterns.len() >= 2);
let patterns: Vec<EvolvedPattern> = 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<EvolvedPattern> = 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<T, I, U>(iter: &I) -> Vec<T> where I: Iterator<Item=U> {
fn vec_for_iterator<T, I, U>(iter: &I) -> Vec<T>
where
I: Iterator<Item = U>,
{
match iter.size_hint().1 {
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<Variable, ValueTypeSet>,
additional_types: &BTreeMap<Variable, ValueTypeSet>) {
fn union_types(
into: &mut BTreeMap<Variable, ValueTypeSet>,
additional_types: &BTreeMap<Variable, ValueTypeSet>,
) {
// 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<Variable, ValueTypeSet>,
{
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<Variable, ValueTypeSet>,
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<Variable, ValueTypeSet>,
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.");
},
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,37 +8,19 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// 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),
}
);
}
}

View file

@ -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<QueryValue> {
pub(crate) fn resolve_numeric_argument(
&mut self,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
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<QueryValue> {
pub(crate) fn resolve_instant_argument(
&mut self,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
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<QueryValue> {
pub(crate) fn resolve_ref_argument(
&mut self,
schema: &Schema,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
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<QueryValue> {
pub(crate) fn resolve_tx_argument(
&mut self,
schema: &Schema,
function: &PlainSymbol,
position: usize,
arg: FnArg,
) -> Result<QueryValue> {
// Under the hood there's nothing special about a transaction ID -- it's just another ref.
// 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<QueryValue> {
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
}
}
}

View file

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

View file

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

View file

@ -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<U>(&self, entid: U) -> bool where U: Into<Entid> {
pub fn is_attribute_cached_reverse<U>(&self, entid: U) -> bool
where
U: Into<Entid>,
{
self.cache
.map(|cache| cache.is_attribute_cached_reverse(entid.into()))
.unwrap_or(false)
}
pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool where U: Into<Entid> {
pub fn is_attribute_cached_forward<U>(&self, entid: U) -> bool
where
U: Into<Entid>,
{
self.cache
.map(|cache| cache.is_attribute_cached_forward(entid.into()))
.unwrap_or(false)
}
pub fn get_values_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&Vec<TypedValue>>
where U: Into<Entid>, V: Into<Entid> {
self.cache.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into()))
pub fn get_values_for_entid<U, V>(
&self,
schema: &Schema,
attribute: U,
entid: V,
) -> Option<&Vec<TypedValue>>
where
U: Into<Entid>,
V: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_values_for_entid(schema, attribute.into(), entid.into()))
}
pub fn get_value_for_entid<U, V>(&self, schema: &Schema, attribute: U, entid: V) -> Option<&TypedValue>
where U: Into<Entid>, V: Into<Entid> {
self.cache.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into()))
pub fn get_value_for_entid<U, V>(
&self,
schema: &Schema,
attribute: U,
entid: V,
) -> Option<&TypedValue>
where
U: Into<Entid>,
V: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_value_for_entid(schema, attribute.into(), entid.into()))
}
pub fn get_entid_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<Entid>
where U: Into<Entid> {
self.cache.and_then(|cache| cache.get_entid_for_value(attribute.into(), value))
where
U: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_entid_for_value(attribute.into(), value))
}
pub fn get_entids_for_value<U>(&self, attribute: U, value: &TypedValue) -> Option<&BTreeSet<Entid>>
where U: Into<Entid> {
self.cache.and_then(|cache| cache.get_entids_for_value(attribute.into(), value))
pub fn get_entids_for_value<U>(
&self,
attribute: U,
value: &TypedValue,
) -> Option<&BTreeSet<Entid>>
where
U: Into<Entid>,
{
self.cache
.and_then(|cache| cache.get_entids_for_value(attribute.into(), value))
}
}
@ -157,38 +165,41 @@ impl AlgebraicQuery {
/// Return true if every variable in the find spec is fully bound to a single value.
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<Variable> {
self.cc.input_variables.sub(&self.cc.value_bound_variable_set())
self.cc
.input_variables
.sub(&self.cc.value_bound_variable_set())
}
}
pub fn algebrize_with_counter(known: Known, parsed: FindQuery, counter: usize) -> Result<AlgebraicQuery> {
pub fn algebrize_with_counter(
known: Known,
parsed: FindQuery,
counter: usize,
) -> Result<AlgebraicQuery> {
algebrize_with_inputs(known, parsed, counter, QueryInputs::default())
}
@ -200,12 +211,14 @@ pub fn algebrize(known: Known, parsed: FindQuery) -> Result<AlgebraicQuery> {
/// a vector of `OrderBy` instances, including type comparisons if necessary. This function also
/// 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<Vec<Order>>)
-> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> {
fn validate_and_simplify_order(
cc: &ConjoiningClauses,
order: Option<Vec<Order>>,
) -> Result<(Option<Vec<OrderBy>>, BTreeSet<Variable>)> {
match order {
None => Ok((None, BTreeSet::default())),
Some(order) => {
let mut order_bys: Vec<OrderBy> = Vec::with_capacity(order.len() * 2); // Space for tags.
let mut order_bys: Vec<OrderBy> = Vec::with_capacity(order.len() * 2); // Space for tags.
let mut vars: BTreeSet<Variable> = BTreeSet::default();
for Order(direction, var) in order.into_iter() {
@ -221,49 +234,63 @@ fn validate_and_simplify_order(cc: &ConjoiningClauses, order: Option<Vec<Order>>
// Otherwise, determine if we also need to order by type…
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<AlgebraicQuery> {
// 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<AlgebraicQuery> {
Ok(query)
}
pub fn algebrize_with_inputs(known: Known,
parsed: FindQuery,
counter: usize,
inputs: QueryInputs) -> Result<AlgebraicQuery> {
pub fn algebrize_with_inputs(
known: Known,
parsed: FindQuery,
counter: usize,
inputs: QueryInputs,
) -> Result<AlgebraicQuery> {
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<WhereClause>) -> FindQuery {
FindQuery {

View file

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

View file

@ -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<Variable> = 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<Variable> = 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<WhereClause> {
// Filter out all the clauses that are not `not`s.
@ -267,7 +269,7 @@ mod tests {
assert_eq!((), validate_not_join(&not_join).unwrap());
assert_eq!(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!(),
};
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<T>(self,
keyword_ns: T,
keyword_name: T,
value_type: ValueType,
multival: bool) -> Self
where T: AsRef<str>
pub fn define_simple_attr<T>(
self,
keyword_ns: T,
keyword_name: T,
value_type: ValueType,
multival: bool,
) -> Self
where
T: AsRef<str>,
{
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
}

View file

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

View file

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

View file

@ -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<T> = std::result::Result<T, ProjectorError>;
@ -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)]

View file

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

View file

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

View file

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

View file

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

View file

@ -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<QueryOutput> for QueryResults {
}
impl QueryOutput {
pub fn empty_factory(spec: &FindSpec) -> Box<Fn() -> QueryResults> {
pub fn empty_factory(spec: &FindSpec) -> Box<dyn Fn() -> 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<FindSpec>) -> 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<FindSpec>, 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<B>(self) -> Result<Option<B>> where B: BindingTuple {
pub fn into_tuple<B>(self) -> Result<Option<B>>
where
B: BindingTuple,
{
let expected = self.spec.expected_column_count();
self.results.into_tuple().and_then(|vec| B::from_binding_vec(expected, vec))
self.results
.into_tuple()
.and_then(|vec| B::from_binding_vec(expected, vec))
}
pub fn into_rel(self) -> Result<RelResult<Binding>> {
@ -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<Vec<Binding>> {
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<Option<Vec<Binding>>> {
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<RelResult<Binding>> {
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<Projector>,
pub datalog_projector: Box<dyn Projector>,
/// 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<Either<ConstantProjector, CombinedProjection>> {
pub fn query_projection(
schema: &Schema,
query: &AlgebraicQuery,
) -> Result<Either<ConstantProjector, CombinedProjection>> {
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<Variable> = spec.columns()
.map(|e| match e {
&Element::Variable(ref var) |
&Element::Corresponding(ref var) => var.clone(),
let variables: BTreeSet<Variable> = 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<Eithe
CollTwoStagePullProjector::combine(spec, elements)
} else {
CollProjector::combine(spec, elements)
}.map(|p| p.flip_distinct_for_limit(&query.limit))
},
}
.map(|p| p.flip_distinct_for_limit(&query.limit))
}
FindScalar(ref element) => {
let elements = project_elements(1, iter::once(element), query)?;
@ -498,7 +475,7 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
} else {
ScalarProjector::combine(spec, elements)
}
},
}
FindRel(ref elements) => {
let is_pull = elements.iter().any(|e| e.is_pull());
@ -508,8 +485,9 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
RelTwoStagePullProjector::combine(spec, column_count, elements)
} else {
RelProjector::combine(spec, column_count, elements)
}.map(|p| p.flip_distinct_for_limit(&query.limit))
},
}
.map(|p| p.flip_distinct_for_limit(&query.limit))
}
FindTuple(ref elements) => {
let is_pull = elements.iter().any(|e| e.is_pull());
@ -520,41 +498,51 @@ pub fn query_projection(schema: &Schema, query: &AlgebraicQuery) -> Result<Eithe
} else {
TupleProjector::combine(spec, column_count, elements)
}
},
}.map(Either::Right)
}
}
.map(Either::Right)
}
}
#[test]
fn test_into_tuple() {
let query_output = QueryOutput {
spec: Rc::new(FindSpec::FindTuple(vec![Element::Variable(Variable::from_valid_name("?x")),
Element::Variable(Variable::from_valid_name("?y"))])),
results: QueryResults::Tuple(Some(vec![Binding::Scalar(TypedValue::Long(0)),
Binding::Scalar(TypedValue::Long(2))])),
spec: Rc::new(FindSpec::FindTuple(vec![
Element::Variable(Variable::from_valid_name("?x")),
Element::Variable(Variable::from_valid_name("?y")),
])),
results: QueryResults::Tuple(Some(vec![
Binding::Scalar(TypedValue::Long(0)),
Binding::Scalar(TypedValue::Long(2)),
])),
};
assert_eq!(query_output.clone().into_tuple().expect("into_tuple"),
Some((Binding::Scalar(TypedValue::Long(0)),
Binding::Scalar(TypedValue::Long(2)))));
assert_eq!(
query_output.clone().into_tuple().expect("into_tuple"),
Some((
Binding::Scalar(TypedValue::Long(0)),
Binding::Scalar(TypedValue::Long(2))
))
);
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"),
}
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"),
}

View file

@ -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<Projector>, distinct: bool) -> Result<CombinedProjection> {
pub(crate) fn combine(
self,
projector: Box<dyn Projector>,
distinct: bool,
) -> Result<CombinedProjection> {
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<QualifiedAlias> {
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<Item = &'a Element>>(
count: usize,
elements: I,
query: &AlgebraicQuery) -> Result<ProjectedElements> {
query: &AlgebraicQuery,
) -> Result<ProjectedElements> {
// 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<Item = &'a Element>>(
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<Item = &'a Element>>(
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<Item = &'a Element>>(
// 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<Item = &'a Element>>(
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<Item = &'a Element>>(
},
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<Item = &'a Element>>(
match simple.op {
Max | Min => {
min_max_count += 1;
},
}
Avg | Count | Sum => (),
}
@ -330,16 +317,24 @@ pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
// - The type set must be appropriate for the operation. E.g., `Sum` is not a
// 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<Item = &'a Element>>(
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<Item = &'a Element>>(
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<Item = &'a Element>>(
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<Item = &'a Element>>(
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<Item = &'a Element>>(
// `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),

View file

@ -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<FindSpec>,
results_factory: Box<Fn() -> QueryResults>,
results_factory: Box<dyn Fn() -> QueryResults>,
}
impl ConstantProjector {
pub fn new(spec: Rc<FindSpec>, results_factory: Box<Fn() -> QueryResults>) -> ConstantProjector {
pub fn new(
spec: Rc<FindSpec>,
results_factory: Box<dyn Fn() -> QueryResults>,
) -> ConstantProjector {
ConstantProjector {
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<QueryOutput> {
fn project<'stmt, 's>(
&self,
_schema: &Schema,
_sqlite: &'s rusqlite::Connection,
_rows: Rows<'stmt>,
) -> Result<QueryOutput> {
self.project_without_rows()
}
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}

View file

@ -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<QueryOutput>;
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's>;
fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
rows: Rows<'stmt>,
) -> Result<QueryOutput>;
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's>;
}
mod constant;
mod 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,
};

View file

@ -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<FindSpec>,
@ -61,34 +36,53 @@ pub(crate) struct ScalarTwoStagePullProjector {
// The only output is the pull expression, and so we can directly supply the projected entity
// to the pull SQL.
impl ScalarTwoStagePullProjector {
fn with_template(schema: &Schema, spec: Rc<FindSpec>, pull: PullOperation) -> Result<ScalarTwoStagePullProjector> {
fn with_template(
schema: &Schema,
spec: Rc<FindSpec>,
pull: PullOperation,
) -> Result<ScalarTwoStagePullProjector> {
Ok(ScalarTwoStagePullProjector {
spec: spec,
puller: Puller::prepare(schema, pull.0.clone())?,
})
}
pub(crate) fn combine(schema: &Schema, spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> {
pub(crate) fn combine(
schema: &Schema,
spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let pull = elements.pulls.pop().expect("Expected a single pull");
let 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<QueryOutput> {
fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
// Scalar is pretty straightforward -- zero or one entity, do the pull directly.
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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}
@ -110,7 +104,12 @@ pub(crate) struct TupleTwoStagePullProjector {
}
impl TupleTwoStagePullProjector {
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>, pulls: Vec<PullTemplate>) -> TupleTwoStagePullProjector {
fn with_templates(
spec: Rc<FindSpec>,
len: usize,
templates: Vec<TypedIndex>,
pulls: Vec<PullTemplate>,
) -> TupleTwoStagePullProjector {
TupleTwoStagePullProjector {
spec: spec,
len: len,
@ -131,54 +130,68 @@ impl TupleTwoStagePullProjector {
.collect::<Result<Vec<Binding>>>()
}
pub(crate) fn combine(spec: Rc<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> {
let projector = Box::new(TupleTwoStagePullProjector::with_templates(spec, column_count, elements.take_templates(), elements.take_pulls()));
pub(crate) fn combine(
spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(TupleTwoStagePullProjector::with_templates(
spec,
column_count,
elements.take_templates(),
elements.take_pulls(),
));
let distinct = false;
elements.combine(projector, distinct)
}
}
impl Projector for TupleTwoStagePullProjector {
fn project<'stmt, 's>(&self, schema: &Schema, sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
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<QueryOutput> {
let results = if let Some(r) = rows.next().unwrap() {
let row = r;
// Keeping the compiler happy.
let pull_consumers: Result<Vec<PullConsumer>> = 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<Vec<PullConsumer>> = 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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}
@ -195,7 +208,12 @@ pub(crate) struct RelTwoStagePullProjector {
}
impl RelTwoStagePullProjector {
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>, pulls: Vec<PullTemplate>) -> RelTwoStagePullProjector {
fn with_templates(
spec: Rc<FindSpec>,
len: usize,
templates: Vec<TypedIndex>,
pulls: Vec<PullTemplate>,
) -> RelTwoStagePullProjector {
RelTwoStagePullProjector {
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<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> {
let projector = Box::new(RelTwoStagePullProjector::with_templates(spec, column_count, elements.take_templates(), elements.take_pulls()));
pub(crate) fn combine(
spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(RelTwoStagePullProjector::with_templates(
spec,
column_count,
elements.take_templates(),
elements.take_pulls(),
));
// If every column yields only one value, or if this is an aggregate query
// (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<QueryOutput> {
fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
// Allocate space for five rows to start.
// 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<Vec<PullConsumer>> = self.pulls
.iter()
.map(|op| PullConsumer::for_template(schema, op))
.collect();
let pull_consumers: Result<Vec<PullConsumer>> = 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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}
@ -295,20 +326,28 @@ impl CollTwoStagePullProjector {
}
}
pub(crate) fn combine(spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> {
pub(crate) fn combine(
spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let pull = elements.pulls.pop().expect("Expected a single pull");
let 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<QueryOutput> {
fn project<'stmt, 's>(
&self,
schema: &Schema,
sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
let mut pull_consumer = PullConsumer::for_operation(schema, &self.pull)?;
while let Some(r) = rows.next().unwrap() {
@ -328,8 +367,7 @@ impl Projector for CollTwoStagePullProjector {
})
}
fn columns<'s>(&'s self) -> Box<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}

View file

@ -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<FindSpec>,
@ -47,8 +32,14 @@ impl ScalarProjector {
}
}
pub(crate) fn combine(spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> {
let template = elements.templates.pop().expect("Expected a single template");
pub(crate) fn combine(
spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let template = elements
.templates
.pop()
.expect("Expected a single template");
let projector = Box::new(ScalarProjector::with_template(spec, template));
let 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<QueryOutput> {
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<QueryOutput> {
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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}
@ -84,7 +79,11 @@ pub(crate) struct TupleProjector {
}
impl TupleProjector {
fn with_templates(spec: Rc<FindSpec>, len: usize, templates: Vec<TypedIndex>) -> TupleProjector {
fn with_templates(
spec: Rc<FindSpec>,
len: usize,
templates: Vec<TypedIndex>,
) -> TupleProjector {
TupleProjector {
spec: spec,
len: len,
@ -104,30 +103,42 @@ impl TupleProjector {
.collect::<Result<Vec<Binding>>>()
}
pub(crate) fn combine(spec: Rc<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> {
let projector = Box::new(TupleProjector::with_templates(spec, column_count, elements.take_templates()));
pub(crate) fn combine(
spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(TupleProjector::with_templates(
spec,
column_count,
elements.take_templates(),
));
let distinct = false;
elements.combine(projector, distinct)
}
}
impl Projector for TupleProjector {
fn project<'stmt, 's>(&self, _schema: &Schema, _sqlite: &'s rusqlite::Connection, mut rows: Rows<'stmt>) -> Result<QueryOutput> {
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<QueryOutput> {
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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + '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<FindSpec>, column_count: usize, mut elements: ProjectedElements) -> Result<CombinedProjection> {
let projector = Box::new(RelProjector::with_templates(spec, column_count, elements.take_templates()));
pub(crate) fn combine(
spec: Rc<FindSpec>,
column_count: usize,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let projector = Box::new(RelProjector::with_templates(
spec,
column_count,
elements.take_templates(),
));
// If every column yields only one value, or if this is an aggregate query
// (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<QueryOutput> {
fn project<'stmt, 's>(
&self,
_schema: &Schema,
_sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
// Allocate space for five rows to start.
// 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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}
@ -219,22 +241,33 @@ impl CollProjector {
}
}
pub(crate) fn combine(spec: Rc<FindSpec>, mut elements: ProjectedElements) -> Result<CombinedProjection> {
let template = elements.templates.pop().expect("Expected a single template");
pub(crate) fn combine(
spec: Rc<FindSpec>,
mut elements: ProjectedElements,
) -> Result<CombinedProjection> {
let template = elements
.templates
.pop()
.expect("Expected a single template");
let projector = Box::new(CollProjector::with_template(spec, template));
// 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<QueryOutput> {
fn project<'stmt, 's>(
&self,
_schema: &Schema,
_sqlite: &'s rusqlite::Connection,
mut rows: Rows<'stmt>,
) -> Result<QueryOutput> {
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<Iterator<Item=&Element> + 's> {
fn columns<'s>(&'s self) -> Box<dyn Iterator<Item = &Element> + 's> {
self.spec.columns()
}
}

View file

@ -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<PullAttributeSpec>);
#[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<PullConsumer<'schema>> {
pub(crate) fn for_template(
schema: &'schema Schema,
template: &PullTemplate,
) -> Result<PullConsumer<'schema>> {
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<PullConsumer<'schema>> {
pub(crate) fn for_operation(
schema: &'schema Schema,
operation: &PullOperation,
) -> Result<PullConsumer<'schema>> {
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<Binding> {
self.results.values().cloned().map(|vrc| Binding::Map(vrc)).collect()
self.results
.values()
.cloned()
.map(|vrc| Binding::Map(vrc))
.collect()
}
}

View file

@ -8,10 +8,7 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// 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<Vec<Vec<TypedValue>>> for RelResult<Binding> {
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(),
}
}
}

View file

@ -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<Vec<SQLTypeAffinity>>) -> Constraint {
let type_column = QualifiedAlias::new(table.clone(),
DatomsColumn::ValueTypeTag).to_column();
fn type_constraint(
table: &TableAlias,
tag: i32,
to_check: Option<Vec<SQLTypeAffinity>>,
) -> Constraint {
let type_column = QualifiedAlias::new(table.clone(), DatomsColumn::ValueTypeTag).to_column();
let check_type_tag = Constraint::equal(type_column, ColumnOrExpression::Integer(tag));
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>,
projector: Box<dyn Projector>,
},
}
@ -265,7 +245,9 @@ struct ConsumableVec<T> {
impl<T> From<Vec<T>> for ConsumableVec<T> {
fn from(vec: Vec<T>) -> ConsumableVec<T> {
ConsumableVec { inner: vec.into_iter().map(|x| Some(x)).collect() }
ConsumableVec {
inner: vec.into_iter().map(|x| Some(x)).collect(),
}
}
}
@ -278,7 +260,9 @@ impl<T> ConsumableVec<T> {
fn table_for_computed(computed: ComputedTable, alias: TableAlias) -> TableOrSubquery {
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<GroupBy>,
order: Option<Vec<OrderBy>>,
limit: Limit) -> SelectQuery {
fn cc_to_select_query(
projection: Projection,
cc: ConjoiningClauses,
distinct: bool,
group_by: Vec<GroupBy>,
order: Option<Vec<OrderBy>>,
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<Project
query: match pre_aggregate_projection {
// If we know we need a nested query for aggregation, build that first.
Some(pre_aggregate) => {
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,
}
},
}
})
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -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<Entid, ValueRc<StructuredMap>>;
pub fn pull_attributes_for_entity<A>(schema: &Schema,
db: &rusqlite::Connection,
entity: Entid,
attributes: A) -> Result<StructuredMap>
where A: IntoIterator<Item=Entid> {
let attrs = attributes.into_iter()
.map(|e| PullAttributeSpec::Attribute(PullConcreteAttribute::Entid(e).into()))
.collect();
pub fn pull_attributes_for_entity<A>(
schema: &Schema,
db: &rusqlite::Connection,
entity: Entid,
attributes: A,
) -> Result<StructuredMap>
where
A: IntoIterator<Item = Entid>,
{
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<E, A>(schema: &Schema,
db: &rusqlite::Connection,
entities: E,
attributes: A) -> Result<PullResults>
where E: IntoIterator<Item=Entid>,
A: IntoIterator<Item=Entid> {
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<E, A>(
schema: &Schema,
db: &rusqlite::Connection,
entities: E,
attributes: A,
) -> Result<PullResults>
where
E: IntoIterator<Item = Entid>,
A: IntoIterator<Item = Entid>,
{
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<Entid, ValueRc<Keyword>> = 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<E>(&self,
schema: &Schema,
db: &rusqlite::Connection,
entities: E) -> Result<PullResults>
where E: IntoIterator<Item=Entid> {
pub fn pull<E>(
&self,
schema: &Schema,
db: &rusqlite::Connection,
entities: E,
) -> Result<PullResults>
where
E: IntoIterator<Item = Entid>,
{
// 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)
}
}

View file

@ -4,6 +4,7 @@ version = "0.0.1"
workspace = ".."
[dependencies]
rusqlite = "0.21.0"
[dependencies.edn]
path = "../edn"

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