2017-02-04 00:51:13 +00:00
|
|
|
// Copyright 2016 Mozilla
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
|
|
// this file except in compliance with the License. You may obtain a copy of the
|
|
|
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
// Unless required by applicable law or agreed to in writing, software distributed
|
|
|
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
|
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
|
|
// specific language governing permissions and limitations under the License.
|
|
|
|
|
2018-02-01 17:17:07 +00:00
|
|
|
extern crate chrono;
|
2017-04-24 21:15:26 +00:00
|
|
|
extern crate enum_set;
|
2018-06-06 01:23:59 +00:00
|
|
|
extern crate failure;
|
2018-04-24 22:08:38 +00:00
|
|
|
extern crate indexmap;
|
2018-02-01 17:17:07 +00:00
|
|
|
extern crate ordered_float;
|
|
|
|
extern crate uuid;
|
2017-04-24 21:15:26 +00:00
|
|
|
|
2018-08-08 17:35:06 +00:00
|
|
|
extern crate core_traits;
|
|
|
|
|
2017-03-30 10:08:36 +00:00
|
|
|
extern crate edn;
|
|
|
|
|
2018-08-08 17:35:06 +00:00
|
|
|
use core_traits::{
|
2018-08-08 21:19:27 +00:00
|
|
|
Attribute,
|
2018-08-08 17:35:06 +00:00
|
|
|
Entid,
|
|
|
|
KnownEntid,
|
2018-08-08 17:36:41 +00:00
|
|
|
ValueType,
|
2018-08-08 17:35:06 +00:00
|
|
|
};
|
|
|
|
|
2018-02-14 00:51:21 +00:00
|
|
|
mod cache;
|
2017-03-30 10:08:36 +00:00
|
|
|
|
2017-11-30 23:02:07 +00:00
|
|
|
use std::collections::{
|
|
|
|
BTreeMap,
|
|
|
|
};
|
|
|
|
|
2017-12-06 20:42:59 +00:00
|
|
|
pub use uuid::Uuid;
|
2017-04-29 03:11:55 +00:00
|
|
|
|
2018-01-18 15:06:23 +00:00
|
|
|
pub use chrono::{
|
2017-04-29 03:11:55 +00:00
|
|
|
DateTime,
|
2018-01-18 15:06:23 +00:00
|
|
|
Timelike, // For truncation.
|
|
|
|
};
|
|
|
|
|
|
|
|
pub use edn::{
|
2018-05-28 20:01:14 +00:00
|
|
|
Cloned,
|
2017-04-29 03:11:55 +00:00
|
|
|
FromMicros,
|
2018-05-28 20:01:14 +00:00
|
|
|
FromRc,
|
2018-05-11 16:52:17 +00:00
|
|
|
Keyword,
|
2017-04-29 03:11:55 +00:00
|
|
|
ToMicros,
|
2017-11-21 16:24:08 +00:00
|
|
|
Utc,
|
2018-05-28 20:01:14 +00:00
|
|
|
ValueRc,
|
2017-04-29 03:11:55 +00:00
|
|
|
};
|
2018-06-04 22:21:27 +00:00
|
|
|
|
2018-05-31 21:10:49 +00:00
|
|
|
pub use edn::parse::{
|
2018-06-04 22:21:27 +00:00
|
|
|
parse_query,
|
2018-05-31 21:10:49 +00:00
|
|
|
};
|
2017-02-08 21:59:56 +00:00
|
|
|
|
2018-03-06 17:01:20 +00:00
|
|
|
pub use cache::{
|
|
|
|
CachedAttributes,
|
|
|
|
UpdateableCache,
|
|
|
|
};
|
2018-02-14 00:51:21 +00:00
|
|
|
|
2017-02-04 00:51:13 +00:00
|
|
|
/// Core types defining a Mentat knowledge base.
|
2018-04-16 19:54:36 +00:00
|
|
|
mod types;
|
2018-07-03 20:18:02 +00:00
|
|
|
mod tx_report;
|
2018-04-16 19:54:36 +00:00
|
|
|
mod sql_types;
|
|
|
|
|
2018-07-03 20:18:02 +00:00
|
|
|
pub use tx_report::{
|
|
|
|
TxReport,
|
|
|
|
};
|
|
|
|
|
2018-04-16 19:54:36 +00:00
|
|
|
pub use types::{
|
|
|
|
ValueTypeTag,
|
|
|
|
};
|
2017-02-04 00:51:13 +00:00
|
|
|
|
2018-04-16 19:54:36 +00:00
|
|
|
pub use sql_types::{
|
|
|
|
SQLTypeAffinity,
|
|
|
|
SQLValueType,
|
|
|
|
SQLValueTypeSet,
|
|
|
|
};
|
2017-02-10 01:12:55 +00:00
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
|
|
|
|
pub type IdentMap = BTreeMap<Keyword, Entid>;
|
2017-02-14 03:20:20 +00:00
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
/// Map positive integer entids (`1`) to `Keyword` idents (`:db/ident`).
|
|
|
|
pub type EntidMap = BTreeMap<Entid, Keyword>;
|
2017-02-14 03:20:20 +00:00
|
|
|
|
|
|
|
/// Map attribute entids to `Attribute` instances.
|
2018-01-23 16:23:37 +00:00
|
|
|
pub type AttributeMap = BTreeMap<Entid, Attribute>;
|
2017-02-14 03:20:20 +00:00
|
|
|
|
|
|
|
/// Represents a Mentat schema.
|
|
|
|
///
|
|
|
|
/// Maintains the mapping between string idents and positive integer entids; and exposes the schema
|
|
|
|
/// flags associated to a given entid (equivalently, ident).
|
|
|
|
///
|
|
|
|
/// TODO: consider a single bi-directional map instead of separate ident->entid and entid->ident
|
|
|
|
/// maps.
|
|
|
|
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
|
|
|
pub struct Schema {
|
|
|
|
/// Map entid->ident.
|
|
|
|
///
|
|
|
|
/// Invariant: is the inverse map of `ident_map`.
|
|
|
|
pub entid_map: EntidMap,
|
|
|
|
|
|
|
|
/// Map ident->entid.
|
|
|
|
///
|
|
|
|
/// Invariant: is the inverse map of `entid_map`.
|
|
|
|
pub ident_map: IdentMap,
|
|
|
|
|
|
|
|
/// Map entid->attribute flags.
|
|
|
|
///
|
|
|
|
/// Invariant: key-set is the same as the key-set of `entid_map` (equivalently, the value-set of
|
|
|
|
/// `ident_map`).
|
2018-01-23 16:23:37 +00:00
|
|
|
pub attribute_map: AttributeMap,
|
2018-04-03 21:25:53 +00:00
|
|
|
|
|
|
|
/// Maintain a vec of unique attribute IDs for which the corresponding attribute in `attribute_map`
|
|
|
|
/// has `.component == true`.
|
|
|
|
pub component_attributes: Vec<Entid>,
|
2018-01-23 16:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait HasSchema {
|
2018-01-23 16:31:27 +00:00
|
|
|
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
|
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid>;
|
|
|
|
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid>;
|
2018-01-23 16:31:27 +00:00
|
|
|
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.
|
2018-05-11 16:52:17 +00:00
|
|
|
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>;
|
2018-01-23 16:23:37 +00:00
|
|
|
|
|
|
|
/// Return true if the provided entid identifies an attribute in this schema.
|
2018-01-23 16:31:27 +00:00
|
|
|
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid>;
|
2018-01-23 16:23:37 +00:00
|
|
|
|
|
|
|
/// Return true if the provided ident identifies an attribute in this schema.
|
2018-05-11 16:52:17 +00:00
|
|
|
fn identifies_attribute(&self, x: &Keyword) -> bool;
|
2018-04-03 21:25:53 +00:00
|
|
|
|
|
|
|
fn component_attributes(&self) -> &[Entid];
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Schema {
|
2018-04-03 21:25:53 +00:00
|
|
|
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() };
|
|
|
|
s.update_component_attributes();
|
|
|
|
s
|
|
|
|
}
|
|
|
|
|
2018-01-23 16:23:37 +00:00
|
|
|
/// 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())
|
|
|
|
}
|
2018-01-23 16:31:27 +00:00
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> {
|
2018-01-23 16:31:27 +00:00
|
|
|
self.ident_map.get(x).map(|x| *x)
|
|
|
|
}
|
2018-04-03 21:25:53 +00:00
|
|
|
|
|
|
|
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.sort_unstable();
|
|
|
|
self.component_attributes = components;
|
|
|
|
}
|
2018-01-23 16:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl HasSchema for Schema {
|
2018-01-23 16:31:27 +00:00
|
|
|
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid> {
|
|
|
|
// TODO: this can be made more efficient.
|
|
|
|
self.get_entid(&t.into_keyword())
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid> {
|
2018-01-23 16:31:27 +00:00
|
|
|
self.entid_map.get(&x.into())
|
|
|
|
}
|
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid> {
|
2018-01-23 16:31:27 +00:00
|
|
|
self.get_raw_entid(x).map(KnownEntid)
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
|
|
|
|
2018-01-23 16:31:27 +00:00
|
|
|
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid> {
|
|
|
|
self.attribute_map.get(&x.into())
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
|
2018-01-23 16:31:27 +00:00
|
|
|
self.get_raw_entid(&ident)
|
|
|
|
.and_then(|entid| {
|
|
|
|
self.attribute_for_entid(entid).map(|a| (a, KnownEntid(entid)))
|
|
|
|
})
|
2017-02-15 23:53:13 +00:00
|
|
|
}
|
|
|
|
|
2017-02-14 03:39:47 +00:00
|
|
|
/// Return true if the provided entid identifies an attribute in this schema.
|
2018-01-23 16:31:27 +00:00
|
|
|
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid> {
|
|
|
|
self.attribute_map.contains_key(&x.into())
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
|
|
|
|
2017-02-14 03:39:47 +00:00
|
|
|
/// Return true if the provided ident identifies an attribute in this schema.
|
2018-05-11 16:52:17 +00:00
|
|
|
fn identifies_attribute(&self, x: &Keyword) -> bool {
|
2018-01-23 16:31:27 +00:00
|
|
|
self.get_raw_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false)
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
2018-04-03 21:25:53 +00:00
|
|
|
|
|
|
|
fn component_attributes(&self) -> &[Entid] {
|
|
|
|
&self.component_attributes
|
|
|
|
}
|
2017-02-14 03:20:20 +00:00
|
|
|
}
|
|
|
|
|
2018-04-24 22:04:00 +00:00
|
|
|
pub mod counter;
|
|
|
|
pub mod util;
|
|
|
|
|
2018-05-04 19:56:00 +00:00
|
|
|
/// A helper macro to sequentially process an iterable sequence,
|
|
|
|
/// evaluating a block between each pair of items.
|
|
|
|
///
|
|
|
|
/// This is used to simply and efficiently produce output like
|
|
|
|
///
|
|
|
|
/// ```sql
|
|
|
|
/// 1, 2, 3
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// or
|
|
|
|
///
|
|
|
|
/// ```sql
|
|
|
|
/// x = 1 AND y = 2
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// without producing an intermediate string sequence.
|
|
|
|
#[macro_export]
|
|
|
|
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,
|
|
|
|
/// and running `inter` between each value. See `interpose` for examples.
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! interpose_iter {
|
|
|
|
( $name: pat, $across: expr, $body: block, $inter: block ) => {
|
|
|
|
let mut seq = $across;
|
|
|
|
if let Some($name) = seq.next() {
|
|
|
|
$body;
|
|
|
|
for $name in seq {
|
|
|
|
$inter;
|
|
|
|
$body;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-10 20:03:49 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
2018-01-18 15:06:23 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
2018-08-08 17:36:41 +00:00
|
|
|
use core_traits::{
|
2018-08-08 21:19:27 +00:00
|
|
|
attribute,
|
2018-08-08 17:36:41 +00:00
|
|
|
TypedValue,
|
|
|
|
};
|
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
2017-03-30 10:08:36 +00:00
|
|
|
schema.entid_map.insert(e, i.clone());
|
|
|
|
schema.ident_map.insert(i, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) {
|
2018-01-23 16:23:37 +00:00
|
|
|
schema.attribute_map.insert(e, a);
|
2017-03-30 10:08:36 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 15:06:23 +00:00
|
|
|
#[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 tv: TypedValue = dt.into();
|
|
|
|
if let TypedValue::Instant(roundtripped) = tv {
|
|
|
|
assert_eq!(roundtripped, expected);
|
|
|
|
} else {
|
|
|
|
panic!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-30 10:08:36 +00:00
|
|
|
#[test]
|
|
|
|
fn test_as_edn_value() {
|
|
|
|
let mut schema = Schema::default();
|
|
|
|
|
|
|
|
let attr1 = Attribute {
|
|
|
|
index: true,
|
|
|
|
value_type: ValueType::Ref,
|
|
|
|
fulltext: false,
|
|
|
|
unique: None,
|
|
|
|
multival: false,
|
|
|
|
component: false,
|
2018-04-03 21:23:46 +00:00
|
|
|
no_history: true,
|
2017-03-30 10:08:36 +00:00
|
|
|
};
|
2018-05-11 16:52:17 +00:00
|
|
|
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 97);
|
2017-03-30 10:08:36 +00:00
|
|
|
add_attribute(&mut schema, 97, attr1);
|
|
|
|
|
|
|
|
let attr2 = Attribute {
|
|
|
|
index: false,
|
|
|
|
value_type: ValueType::String,
|
|
|
|
fulltext: true,
|
|
|
|
unique: Some(attribute::Unique::Value),
|
|
|
|
multival: true,
|
|
|
|
component: false,
|
2018-04-03 21:23:46 +00:00
|
|
|
no_history: false,
|
2017-03-30 10:08:36 +00:00
|
|
|
};
|
2018-05-11 16:52:17 +00:00
|
|
|
associate_ident(&mut schema, Keyword::namespaced("foo", "bas"), 98);
|
2017-03-30 10:08:36 +00:00
|
|
|
add_attribute(&mut schema, 98, attr2);
|
|
|
|
|
|
|
|
let attr3 = Attribute {
|
|
|
|
index: false,
|
|
|
|
value_type: ValueType::Boolean,
|
|
|
|
fulltext: false,
|
|
|
|
unique: Some(attribute::Unique::Identity),
|
|
|
|
multival: false,
|
|
|
|
component: true,
|
2018-04-03 21:23:46 +00:00
|
|
|
no_history: false,
|
2017-03-30 10:08:36 +00:00
|
|
|
};
|
|
|
|
|
2018-05-11 16:52:17 +00:00
|
|
|
associate_ident(&mut schema, Keyword::namespaced("foo", "bat"), 99);
|
2017-03-30 10:08:36 +00:00
|
|
|
add_attribute(&mut schema, 99, attr3);
|
|
|
|
|
|
|
|
let value = schema.to_edn_value();
|
|
|
|
|
|
|
|
let expected_output = r#"[ { :db/ident :foo/bar
|
|
|
|
:db/valueType :db.type/ref
|
|
|
|
:db/cardinality :db.cardinality/one
|
2018-04-03 21:23:46 +00:00
|
|
|
:db/index true
|
|
|
|
:db/noHistory true },
|
2017-03-30 10:08:36 +00:00
|
|
|
{ :db/ident :foo/bas
|
|
|
|
:db/valueType :db.type/string
|
|
|
|
:db/cardinality :db.cardinality/many
|
|
|
|
:db/unique :db.unique/value
|
|
|
|
:db/fulltext true },
|
|
|
|
{ :db/ident :foo/bat
|
|
|
|
:db/valueType :db.type/boolean
|
|
|
|
:db/cardinality :db.cardinality/one
|
|
|
|
:db/unique :db.unique/identity
|
2018-04-03 21:25:28 +00:00
|
|
|
:db/isComponent true }, ]"#;
|
2017-03-30 10:08:36 +00:00
|
|
|
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.
|
|
|
|
let value2 = schema.to_edn_value();
|
|
|
|
assert_eq!(expected_value, value2);
|
|
|
|
}
|
2017-02-10 20:03:49 +00:00
|
|
|
}
|