Pre: Move core/Attribute* to core-traits

This commit is contained in:
Grisha Kruglov 2018-08-08 14:19:27 -07:00 committed by Grisha Kruglov
parent 68d0e17824
commit 9381af4289
28 changed files with 262 additions and 257 deletions

View file

@ -40,6 +40,8 @@ use std::sync::{
Arc,
};
use std::collections::BTreeMap;
use indexmap::{
IndexMap,
};
@ -115,6 +117,161 @@ impl<V: TransactableValueMarker> Into<ValuePlace<V>> for KnownEntid {
}
}
/// Bit flags used in `flags0` column in temporary tables created during search,
/// such as the `search_results`, `inexact_searches` and `exact_searches` tables.
/// 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,
IndexFulltext = 1 << 2,
UniqueValue = 1 << 3,
}
pub mod attribute {
use ::{
TypedValue,
};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum Unique {
Value,
Identity,
}
impl Unique {
// This is easier than rejigging DB_UNIQUE_VALUE to not be EDN.
pub fn into_typed_value(self) -> TypedValue {
match self {
Unique::Value => TypedValue::typed_ns_keyword("db.unique", "value"),
Unique::Identity => TypedValue::typed_ns_keyword("db.unique", "identity"),
}
}
}
}
/// A Mentat schema attribute has a value type and several other flags determining how assertions
/// with the attribute are interpreted.
///
/// TODO: consider packing this into a bitfield or similar.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)]
pub struct Attribute {
/// The associated value type, i.e., `:db/valueType`?
pub value_type: ValueType,
/// `true` if this attribute is multi-valued, i.e., it is `:db/cardinality
/// :db.cardinality/many`. `false` if this attribute is single-valued (the default), i.e., it
/// is `:db/cardinality :db.cardinality/one`.
pub multival: bool,
/// `None` if this attribute is neither unique-value nor unique-identity.
///
/// `Some(attribute::Unique::Value)` if this attribute is unique-value, i.e., it is `:db/unique
/// :db.unique/value`.
///
/// *Unique-value* means that there is at most one assertion with the attribute and a
/// particular value in the datom store. Unique-value attributes can be used in lookup-refs.
///
/// `Some(attribute::Unique::Identity)` if this attribute is unique-identity, i.e., it is `:db/unique
/// :db.unique/identity`.
///
/// Unique-identity attributes always have value type `Ref`.
///
/// *Unique-identity* means that the attribute is *unique-value* and that they can be used in
/// lookup-refs and will automatically upsert where appropriate.
pub unique: Option<attribute::Unique>,
/// `true` if this attribute is automatically indexed, i.e., it is `:db/indexing true`.
pub index: bool,
/// `true` if this attribute is automatically fulltext indexed, i.e., it is `:db/fulltext true`.
///
/// Fulltext attributes always have string values.
pub fulltext: bool,
/// `true` if this attribute is a component, i.e., it is `:db/isComponent true`.
///
/// Component attributes always have value type `Ref`.
///
/// They are used to compose entities from component sub-entities: they are fetched recursively
/// by pull expressions, and they are automatically recursively deleted where appropriate.
pub component: bool,
/// `true` if this attribute doesn't require history to be kept, i.e., it is `:db/noHistory true`.
pub no_history: bool,
}
impl Attribute {
/// Combine several attribute flags into a bitfield used in temporary search tables.
pub fn flags(&self) -> u8 {
let mut flags: u8 = 0;
if self.index {
flags |= AttributeBitFlags::IndexAVET as u8;
}
if self.value_type == ValueType::Ref {
flags |= AttributeBitFlags::IndexVAET as u8;
}
if self.fulltext {
flags |= AttributeBitFlags::IndexFulltext as u8;
}
if self.unique.is_some() {
flags |= AttributeBitFlags::UniqueValue as u8;
}
flags
}
pub fn to_edn_value(&self, ident: Option<Keyword>) -> edn::Value {
let mut attribute_map: BTreeMap<edn::Value, edn::Value> = BTreeMap::default();
if let Some(ident) = ident {
attribute_map.insert(values::DB_IDENT.clone(), edn::Value::Keyword(ident));
}
attribute_map.insert(values::DB_VALUE_TYPE.clone(), self.value_type.into_edn_value());
attribute_map.insert(values::DB_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()); },
None => (),
}
if self.index {
attribute_map.insert(values::DB_INDEX.clone(), edn::Value::Boolean(true));
}
if self.fulltext {
attribute_map.insert(values::DB_FULLTEXT.clone(), edn::Value::Boolean(true));
}
if self.component {
attribute_map.insert(values::DB_IS_COMPONENT.clone(), edn::Value::Boolean(true));
}
if self.no_history {
attribute_map.insert(values::DB_NO_HISTORY.clone(), edn::Value::Boolean(true));
}
edn::Value::Map(attribute_map)
}
}
impl Default for Attribute {
fn default() -> Attribute {
Attribute {
// There's no particular reason to favour one value type, so Ref it is.
value_type: ValueType::Ref,
fulltext: false,
index: false,
multival: false,
unique: None,
component: false,
no_history: false,
}
}
}
/// The attribute of each Mentat assertion has a :db/valueType constraining the value to a
/// particular set. Mentat recognizes the following :db/valueType values.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
@ -863,3 +1020,56 @@ fn test_typed_value() {
assert!(TypedValue::typed_string("foo").is_congruent_with(ValueType::String));
assert!(TypedValue::typed_string("foo").is_congruent_with(None));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attribute_flags() {
let attr1 = Attribute {
index: true,
value_type: ValueType::Ref,
fulltext: false,
unique: None,
multival: false,
component: false,
no_history: false,
};
assert!(attr1.flags() & AttributeBitFlags::IndexAVET as u8 != 0);
assert!(attr1.flags() & AttributeBitFlags::IndexVAET as u8 != 0);
assert!(attr1.flags() & AttributeBitFlags::IndexFulltext as u8 == 0);
assert!(attr1.flags() & AttributeBitFlags::UniqueValue as u8 == 0);
let attr2 = Attribute {
index: false,
value_type: ValueType::Boolean,
fulltext: true,
unique: Some(attribute::Unique::Value),
multival: false,
component: false,
no_history: false,
};
assert!(attr2.flags() & AttributeBitFlags::IndexAVET as u8 == 0);
assert!(attr2.flags() & AttributeBitFlags::IndexVAET as u8 == 0);
assert!(attr2.flags() & AttributeBitFlags::IndexFulltext as u8 != 0);
assert!(attr2.flags() & AttributeBitFlags::UniqueValue as u8 != 0);
let attr3 = Attribute {
index: false,
value_type: ValueType::Boolean,
fulltext: true,
unique: Some(attribute::Unique::Identity),
multival: false,
component: false,
no_history: false,
};
assert!(attr3.flags() & AttributeBitFlags::IndexAVET as u8 == 0);
assert!(attr3.flags() & AttributeBitFlags::IndexVAET as u8 == 0);
assert!(attr3.flags() & AttributeBitFlags::IndexFulltext as u8 != 0);
assert!(attr3.flags() & AttributeBitFlags::UniqueValue as u8 != 0);
}
}

View file

@ -20,9 +20,9 @@ extern crate core_traits;
extern crate edn;
use core_traits::{
Attribute,
Entid,
KnownEntid,
values,
ValueType,
};
@ -77,161 +77,6 @@ pub use sql_types::{
SQLValueTypeSet,
};
/// Bit flags used in `flags0` column in temporary tables created during search,
/// such as the `search_results`, `inexact_searches` and `exact_searches` tables.
/// 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,
IndexFulltext = 1 << 2,
UniqueValue = 1 << 3,
}
pub mod attribute {
use core_traits::{
TypedValue,
};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum Unique {
Value,
Identity,
}
impl Unique {
// This is easier than rejigging DB_UNIQUE_VALUE to not be EDN.
pub fn into_typed_value(self) -> TypedValue {
match self {
Unique::Value => TypedValue::typed_ns_keyword("db.unique", "value"),
Unique::Identity => TypedValue::typed_ns_keyword("db.unique", "identity"),
}
}
}
}
/// A Mentat schema attribute has a value type and several other flags determining how assertions
/// with the attribute are interpreted.
///
/// TODO: consider packing this into a bitfield or similar.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)]
pub struct Attribute {
/// The associated value type, i.e., `:db/valueType`?
pub value_type: ValueType,
/// `true` if this attribute is multi-valued, i.e., it is `:db/cardinality
/// :db.cardinality/many`. `false` if this attribute is single-valued (the default), i.e., it
/// is `:db/cardinality :db.cardinality/one`.
pub multival: bool,
/// `None` if this attribute is neither unique-value nor unique-identity.
///
/// `Some(attribute::Unique::Value)` if this attribute is unique-value, i.e., it is `:db/unique
/// :db.unique/value`.
///
/// *Unique-value* means that there is at most one assertion with the attribute and a
/// particular value in the datom store. Unique-value attributes can be used in lookup-refs.
///
/// `Some(attribute::Unique::Identity)` if this attribute is unique-identity, i.e., it is `:db/unique
/// :db.unique/identity`.
///
/// Unique-identity attributes always have value type `Ref`.
///
/// *Unique-identity* means that the attribute is *unique-value* and that they can be used in
/// lookup-refs and will automatically upsert where appropriate.
pub unique: Option<attribute::Unique>,
/// `true` if this attribute is automatically indexed, i.e., it is `:db/indexing true`.
pub index: bool,
/// `true` if this attribute is automatically fulltext indexed, i.e., it is `:db/fulltext true`.
///
/// Fulltext attributes always have string values.
pub fulltext: bool,
/// `true` if this attribute is a component, i.e., it is `:db/isComponent true`.
///
/// Component attributes always have value type `Ref`.
///
/// They are used to compose entities from component sub-entities: they are fetched recursively
/// by pull expressions, and they are automatically recursively deleted where appropriate.
pub component: bool,
/// `true` if this attribute doesn't require history to be kept, i.e., it is `:db/noHistory true`.
pub no_history: bool,
}
impl Attribute {
/// Combine several attribute flags into a bitfield used in temporary search tables.
pub fn flags(&self) -> u8 {
let mut flags: u8 = 0;
if self.index {
flags |= AttributeBitFlags::IndexAVET as u8;
}
if self.value_type == ValueType::Ref {
flags |= AttributeBitFlags::IndexVAET as u8;
}
if self.fulltext {
flags |= AttributeBitFlags::IndexFulltext as u8;
}
if self.unique.is_some() {
flags |= AttributeBitFlags::UniqueValue as u8;
}
flags
}
pub fn to_edn_value(&self, ident: Option<Keyword>) -> edn::Value {
let mut attribute_map: BTreeMap<edn::Value, edn::Value> = BTreeMap::default();
if let Some(ident) = ident {
attribute_map.insert(values::DB_IDENT.clone(), edn::Value::Keyword(ident));
}
attribute_map.insert(values::DB_VALUE_TYPE.clone(), self.value_type.into_edn_value());
attribute_map.insert(values::DB_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()); },
None => (),
}
if self.index {
attribute_map.insert(values::DB_INDEX.clone(), edn::Value::Boolean(true));
}
if self.fulltext {
attribute_map.insert(values::DB_FULLTEXT.clone(), edn::Value::Boolean(true));
}
if self.component {
attribute_map.insert(values::DB_IS_COMPONENT.clone(), edn::Value::Boolean(true));
}
if self.no_history {
attribute_map.insert(values::DB_NO_HISTORY.clone(), edn::Value::Boolean(true));
}
edn::Value::Map(attribute_map)
}
}
impl Default for Attribute {
fn default() -> Attribute {
Attribute {
// There's no particular reason to favour one value type, so Ref it is.
value_type: ValueType::Ref,
fulltext: false,
index: false,
multival: false,
unique: None,
component: false,
no_history: false,
}
}
}
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
pub type IdentMap = BTreeMap<Keyword, Entid>;
@ -409,6 +254,7 @@ mod test {
use std::str::FromStr;
use core_traits::{
attribute,
TypedValue,
};
@ -421,54 +267,6 @@ mod test {
schema.attribute_map.insert(e, a);
}
#[test]
fn test_attribute_flags() {
let attr1 = Attribute {
index: true,
value_type: ValueType::Ref,
fulltext: false,
unique: None,
multival: false,
component: false,
no_history: false,
};
assert!(attr1.flags() & AttributeBitFlags::IndexAVET as u8 != 0);
assert!(attr1.flags() & AttributeBitFlags::IndexVAET as u8 != 0);
assert!(attr1.flags() & AttributeBitFlags::IndexFulltext as u8 == 0);
assert!(attr1.flags() & AttributeBitFlags::UniqueValue as u8 == 0);
let attr2 = Attribute {
index: false,
value_type: ValueType::Boolean,
fulltext: true,
unique: Some(attribute::Unique::Value),
multival: false,
component: false,
no_history: false,
};
assert!(attr2.flags() & AttributeBitFlags::IndexAVET as u8 == 0);
assert!(attr2.flags() & AttributeBitFlags::IndexVAET as u8 == 0);
assert!(attr2.flags() & AttributeBitFlags::IndexFulltext as u8 != 0);
assert!(attr2.flags() & AttributeBitFlags::UniqueValue as u8 != 0);
let attr3 = Attribute {
index: false,
value_type: ValueType::Boolean,
fulltext: true,
unique: Some(attribute::Unique::Identity),
multival: false,
component: false,
no_history: false,
};
assert!(attr3.flags() & AttributeBitFlags::IndexAVET as u8 == 0);
assert!(attr3.flags() & AttributeBitFlags::IndexVAET as u8 == 0);
assert!(attr3.flags() & AttributeBitFlags::IndexFulltext as u8 != 0);
assert!(attr3.flags() & AttributeBitFlags::UniqueValue as u8 != 0);
}
#[test]
fn test_datetime_truncation() {
let dt: DateTime<Utc> = DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed");

View file

@ -42,15 +42,15 @@ use edn::{
use entids;
use core_traits::{
attribute,
Attribute,
AttributeBitFlags,
Entid,
TypedValue,
ValueType,
};
use mentat_core::{
attribute,
Attribute,
AttributeBitFlags,
FromMicros,
IdentMap,
Schema,
@ -1227,12 +1227,12 @@ mod tests {
OpType,
};
use core_traits::{
attribute,
KnownEntid,
};
use mentat_core::{
HasSchema,
Keyword,
attribute,
};
use mentat_core::util::Either::*;
use std::collections::{

View file

@ -19,6 +19,7 @@ use std::collections::{
};
use core_traits::{
Attribute,
Entid,
KnownEntid,
TypedValue,
@ -50,7 +51,6 @@ use schema::{
SchemaTypeChecking,
};
use types::{
Attribute,
AVMap,
AVPair,
Schema,

View file

@ -40,13 +40,13 @@ use db_traits::errors::{
};
use core_traits::{
attribute,
Entid,
TypedValue,
ValueType,
};
use mentat_core::{
attribute,
Schema,
AttributeMap,
};

View file

@ -19,6 +19,8 @@ use db_traits::errors::{
use edn::symbols;
use core_traits::{
attribute,
Attribute,
Entid,
KnownEntid,
TypedValue,
@ -26,8 +28,6 @@ use core_traits::{
};
use mentat_core::{
attribute,
Attribute,
EntidMap,
HasSchema,
IdentMap,

View file

@ -90,6 +90,8 @@ use internal_types::{
use mentat_core::util::Either;
use core_traits::{
attribute,
Attribute,
Entid,
KnownEntid,
TypedValue,
@ -102,7 +104,6 @@ use mentat_core::{
Schema,
TxReport,
Utc,
attribute,
};
use edn::entities as entmod;
@ -121,7 +122,6 @@ use tx_checking;
use types::{
AVMap,
AVPair,
Attribute,
PartitionMap,
TransactableValue,
};

View file

@ -33,8 +33,6 @@ use core_traits::{
};
pub use self::mentat_core::{
Attribute,
AttributeBitFlags,
DateTime,
Schema,
Utc,

View file

@ -41,13 +41,13 @@ use internal_types::{
use mentat_core::util::Either::*;
use core_traits::{
attribute,
Attribute,
Entid,
TypedValue,
};
use mentat_core::{
attribute,
Attribute,
Schema,
};
use edn::entities::OpType;

View file

@ -265,11 +265,11 @@ mod testing {
use super::*;
use core_traits::{
Attribute,
ValueType,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -325,11 +325,8 @@ mod testing {
use super::*;
use core_traits::{
ValueType,
};
use mentat_core::{
Attribute,
ValueType,
};
use edn::query::{

View file

@ -26,6 +26,7 @@ use std::fmt::{
};
use core_traits::{
Attribute,
Entid,
KnownEntid,
ValueType,
@ -34,7 +35,6 @@ use core_traits::{
};
use mentat_core::{
Attribute,
Cloned,
HasSchema,
Schema,

View file

@ -91,13 +91,13 @@ mod testing {
use super::*;
use core_traits::{
Attribute,
TypedValue,
ValueType,
ValueTypeSet,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -754,12 +754,12 @@ mod testing {
use super::*;
use core_traits::{
Attribute,
ValueType,
TypedValue,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -659,12 +659,14 @@ mod testing {
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use mentat_core::attribute::Unique;
use core_traits::attribute::{
Unique,
};
use core_traits::{
Attribute,
ValueTypeSet,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -190,14 +190,14 @@ impl Inequality {
mod testing {
use super::*;
use mentat_core::attribute::Unique;
use core_traits::attribute::{
Unique,
};
use core_traits::{
Attribute,
TypedValue,
ValueType,
};
use mentat_core::{
Attribute,
};
use edn::query::{
FnArg,

View file

@ -17,11 +17,11 @@ extern crate query_algebrizer_traits;
mod utils;
use core_traits::{
Attribute,
ValueType,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -19,12 +19,12 @@ mod utils;
use std::collections::BTreeMap;
use core_traits::{
Attribute,
ValueType,
TypedValue,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -17,13 +17,13 @@ extern crate query_algebrizer_traits;
mod utils;
use core_traits::{
Attribute,
ValueType,
TypedValue,
ValueTypeSet,
};
use mentat_core::{
Attribute,
DateTime,
Schema,
Utc,

View file

@ -14,12 +14,12 @@
#![allow(dead_code)]
use core_traits::{
Attribute,
Entid,
ValueType,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -16,12 +16,12 @@ extern crate mentat_query_projector;
extern crate query_projector_traits;
use core_traits::{
Attribute,
Entid,
ValueType,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -26,13 +26,13 @@ use edn::query::{
};
use core_traits::{
Attribute,
Entid,
TypedValue,
ValueType,
};
use mentat_core::{
Attribute,
Schema,
};

View file

@ -46,6 +46,7 @@ use edn::{
};
pub use core_traits::{
Attribute,
Entid,
KnownEntid,
StructuredMap,
@ -54,7 +55,6 @@ pub use core_traits::{
};
use mentat_core::{
Attribute,
HasSchema,
Keyword,
Schema,

View file

@ -19,11 +19,8 @@ use rusqlite;
use edn;
use core_traits::{
ValueType,
};
use mentat_core::{
Attribute,
ValueType,
};
use db_traits::errors::DbError;

View file

@ -39,6 +39,7 @@ extern crate mentat_sql;
extern crate mentat_tolstoy;
pub use core_traits::{
Attribute,
Binding,
Entid,
KnownEntid,
@ -49,7 +50,6 @@ pub use core_traits::{
};
pub use mentat_core::{
Attribute,
DateTime,
HasSchema,
Keyword,

View file

@ -308,7 +308,7 @@ mod tests {
VersionedStore,
};
use ::vocabulary::attribute::{
use core_traits::attribute::{
Unique,
};

View file

@ -93,16 +93,14 @@
use std::collections::BTreeMap;
pub use mentat_core::attribute;
use mentat_core::attribute::{
Unique,
};
use core_traits::{
KnownEntid,
};
use core_traits::attribute::{
Unique,
};
use ::{
CORE_SCHEMA_VERSION,
Attribute,

View file

@ -14,6 +14,7 @@ extern crate lazy_static;
#[macro_use]
extern crate mentat;
extern crate mentat_core;
extern crate core_traits;
extern crate mentat_db;
extern crate rusqlite;
@ -37,6 +38,10 @@ use mentat_core::{
HasSchema,
};
use core_traits::attribute::{
Unique,
};
// To check our working.
use mentat_db::AttributeValidation;
@ -79,7 +84,7 @@ lazy_static! {
vocabulary::AttributeBuilder::helpful()
.value_type(ValueType::String)
.multival(false)
.unique(vocabulary::attribute::Unique::Identity)
.unique(Unique::Identity)
.build()),
(FOO_MOMENT.clone(),
vocabulary::AttributeBuilder::helpful()
@ -452,7 +457,7 @@ fn test_upgrade_with_functions() {
vocabulary::AttributeBuilder::helpful()
.value_type(ValueType::String)
.multival(false)
.unique(vocabulary::attribute::Unique::Identity)
.unique(Unique::Identity)
.index(true)
.build()),
],
@ -468,7 +473,7 @@ fn test_upgrade_with_functions() {
vocabulary::AttributeBuilder::helpful()
.value_type(ValueType::String)
.multival(false)
.unique(vocabulary::attribute::Unique::Identity)
.unique(Unique::Identity)
.index(true)
.build()),
(kw!(:person/height),
@ -759,7 +764,7 @@ fn test_upgrade_with_functions() {
vocabulary::AttributeBuilder::helpful()
.value_type(ValueType::String)
.multival(false)
.unique(vocabulary::attribute::Unique::Identity)
.unique(Unique::Identity)
.build()),
],
pre: |ip, from| {
@ -781,7 +786,7 @@ fn test_upgrade_with_functions() {
vocabulary::AttributeBuilder::helpful()
.value_type(ValueType::String)
.multival(false)
.unique(vocabulary::attribute::Unique::Identity)
.unique(Unique::Identity)
.build()),
],
pre: |ip, from| {
@ -877,7 +882,7 @@ fn test_upgrade_with_functions() {
vocabulary::AttributeBuilder::helpful()
.value_type(ValueType::String)
.multival(false)
.unique(vocabulary::attribute::Unique::Identity)
.unique(Unique::Identity)
.build()),
// This phrasing is backward, but this is just a test.