Combine NamespacedKeyword and Keyword. (#689) r=nalexander
* Make properties on NamespacedKeyword/NamespacedSymbol private * Use only a single String for NamespacedKeyword/NamespacedSymbol * Review comments. * Remove unsafe code in namespaced_name. Benchmarking shows approximately zero change. * Allow the types of ns and name to differ when constructing a NamespacedName. * Make symbol namespaces optional. * Normalize names of keyword/symbol constructors. This will make the subsequent refactor much less painful. * Use expect not unwrap. * Merge Keyword and NamespacedKeyword.
This commit is contained in:
parent
c8f74fa41b
commit
3dc68bcd38
59 changed files with 1001 additions and 602 deletions
|
@ -165,7 +165,7 @@ This is the lowest-level Mentat crate. It collects together the following things
|
|||
|
||||
- Fundamental domain-specific data structures like `ValueType` and `TypedValue`.
|
||||
- Fundamental SQL-related linkages like `SQLValueType`. These encode the mapping between Mentat's types and values and their representation in our SQLite format.
|
||||
- Conversion to and from EDN types (_e.g._, `edn::NamespacedKeyword` to `TypedValue::Keyword`).
|
||||
- Conversion to and from EDN types (_e.g._, `edn::Keyword` to `TypedValue::Keyword`).
|
||||
- Common utilities (some in the `util` module, and others that should be moved there or broken out) like `Either`, `InternSet`, and `RcCounter`.
|
||||
- Reusable lazy namespaced keywords (_e.g._, `DB_TYPE_DOUBLE`) that are used by `mentat_db` and EDN serialization of core structs.
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ pub use chrono::{
|
|||
|
||||
pub use edn::{
|
||||
FromMicros,
|
||||
NamespacedKeyword,
|
||||
Keyword,
|
||||
ToMicros,
|
||||
Utc,
|
||||
};
|
||||
|
@ -180,10 +180,10 @@ impl Attribute {
|
|||
flags
|
||||
}
|
||||
|
||||
pub fn to_edn_value(&self, ident: Option<NamespacedKeyword>) -> edn::Value {
|
||||
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::NamespacedKeyword(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());
|
||||
|
@ -231,11 +231,11 @@ impl Default for Attribute {
|
|||
}
|
||||
}
|
||||
|
||||
/// Map `NamespacedKeyword` idents (`:db/ident`) to positive integer entids (`1`).
|
||||
pub type IdentMap = BTreeMap<NamespacedKeyword, Entid>;
|
||||
/// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`).
|
||||
pub type IdentMap = BTreeMap<Keyword, Entid>;
|
||||
|
||||
/// Map positive integer entids (`1`) to `NamespacedKeyword` idents (`:db/ident`).
|
||||
pub type EntidMap = BTreeMap<Entid, NamespacedKeyword>;
|
||||
/// Map positive integer entids (`1`) to `Keyword` idents (`:db/ident`).
|
||||
pub type EntidMap = BTreeMap<Entid, Keyword>;
|
||||
|
||||
/// Map attribute entids to `Attribute` instances.
|
||||
pub type AttributeMap = BTreeMap<Entid, Attribute>;
|
||||
|
@ -273,18 +273,18 @@ pub struct Schema {
|
|||
pub trait HasSchema {
|
||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
|
||||
|
||||
fn get_ident<T>(&self, x: T) -> Option<&NamespacedKeyword> where T: Into<Entid>;
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<KnownEntid>;
|
||||
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>;
|
||||
|
||||
// Returns the attribute and the entid named by the provided ident.
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<(&Attribute, KnownEntid)>;
|
||||
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>;
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
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: &NamespacedKeyword) -> bool;
|
||||
fn identifies_attribute(&self, x: &Keyword) -> bool;
|
||||
|
||||
fn component_attributes(&self) -> &[Entid];
|
||||
}
|
||||
|
@ -304,7 +304,7 @@ impl Schema {
|
|||
.collect())
|
||||
}
|
||||
|
||||
fn get_raw_entid(&self, x: &NamespacedKeyword) -> Option<Entid> {
|
||||
fn get_raw_entid(&self, x: &Keyword) -> Option<Entid> {
|
||||
self.ident_map.get(x).map(|x| *x)
|
||||
}
|
||||
|
||||
|
@ -325,11 +325,11 @@ impl HasSchema for Schema {
|
|||
self.get_entid(&t.into_keyword())
|
||||
}
|
||||
|
||||
fn get_ident<T>(&self, x: T) -> Option<&NamespacedKeyword> where T: Into<Entid> {
|
||||
fn get_ident<T>(&self, x: T) -> Option<&Keyword> where T: Into<Entid> {
|
||||
self.entid_map.get(&x.into())
|
||||
}
|
||||
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<KnownEntid> {
|
||||
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid> {
|
||||
self.get_raw_entid(x).map(KnownEntid)
|
||||
}
|
||||
|
||||
|
@ -337,7 +337,7 @@ impl HasSchema for Schema {
|
|||
self.attribute_map.get(&x.into())
|
||||
}
|
||||
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<(&Attribute, KnownEntid)> {
|
||||
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)))
|
||||
|
@ -350,7 +350,7 @@ impl HasSchema for Schema {
|
|||
}
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool {
|
||||
fn identifies_attribute(&self, x: &Keyword) -> bool {
|
||||
self.get_raw_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false)
|
||||
}
|
||||
|
||||
|
@ -408,7 +408,7 @@ mod test {
|
|||
|
||||
use std::str::FromStr;
|
||||
|
||||
fn associate_ident(schema: &mut Schema, i: NamespacedKeyword, e: Entid) {
|
||||
fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) {
|
||||
schema.entid_map.insert(e, i.clone());
|
||||
schema.ident_map.insert(i, e);
|
||||
}
|
||||
|
@ -491,7 +491,7 @@ mod test {
|
|||
component: false,
|
||||
no_history: true,
|
||||
};
|
||||
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bar"), 97);
|
||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 97);
|
||||
add_attribute(&mut schema, 97, attr1);
|
||||
|
||||
let attr2 = Attribute {
|
||||
|
@ -503,7 +503,7 @@ mod test {
|
|||
component: false,
|
||||
no_history: false,
|
||||
};
|
||||
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bas"), 98);
|
||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bas"), 98);
|
||||
add_attribute(&mut schema, 98, attr2);
|
||||
|
||||
let attr3 = Attribute {
|
||||
|
@ -516,7 +516,7 @@ mod test {
|
|||
no_history: false,
|
||||
};
|
||||
|
||||
associate_ident(&mut schema, NamespacedKeyword::new("foo", "bat"), 99);
|
||||
associate_ident(&mut schema, Keyword::namespaced("foo", "bat"), 99);
|
||||
add_attribute(&mut schema, 99, attr3);
|
||||
|
||||
let value = schema.to_edn_value();
|
||||
|
|
|
@ -40,7 +40,7 @@ use ::indexmap::{
|
|||
use ::edn::{
|
||||
self,
|
||||
FromMicros,
|
||||
NamespacedKeyword,
|
||||
Keyword,
|
||||
Utc,
|
||||
};
|
||||
|
||||
|
@ -205,8 +205,8 @@ impl ::enum_set::CLike for ValueType {
|
|||
}
|
||||
|
||||
impl ValueType {
|
||||
pub fn into_keyword(self) -> NamespacedKeyword {
|
||||
NamespacedKeyword::new("db.type", match self {
|
||||
pub fn into_keyword(self) -> Keyword {
|
||||
Keyword::namespaced("db.type", match self {
|
||||
ValueType::Ref => "ref",
|
||||
ValueType::Boolean => "boolean",
|
||||
ValueType::Instant => "instant",
|
||||
|
@ -280,7 +280,7 @@ pub enum TypedValue {
|
|||
Instant(DateTime<Utc>), // Use `into()` to ensure truncation.
|
||||
// TODO: &str throughout?
|
||||
String(ValueRc<String>),
|
||||
Keyword(ValueRc<NamespacedKeyword>),
|
||||
Keyword(ValueRc<Keyword>),
|
||||
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
|
||||
}
|
||||
|
||||
|
@ -335,23 +335,23 @@ impl Binding {
|
|||
/// We entirely support the former, and partially support the latter -- you can alias
|
||||
/// using a different keyword only.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct StructuredMap(pub IndexMap<ValueRc<NamespacedKeyword>, Binding>);
|
||||
pub struct StructuredMap(pub IndexMap<ValueRc<Keyword>, Binding>);
|
||||
|
||||
impl StructuredMap {
|
||||
pub fn insert<N, B>(&mut self, name: N, value: B) where N: Into<ValueRc<NamespacedKeyword>>, 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());
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexMap<ValueRc<NamespacedKeyword>, Binding>> for StructuredMap {
|
||||
fn from(src: IndexMap<ValueRc<NamespacedKeyword>, Binding>) -> Self {
|
||||
impl From<IndexMap<ValueRc<Keyword>, Binding>> for StructuredMap {
|
||||
fn from(src: IndexMap<ValueRc<Keyword>, Binding>) -> Self {
|
||||
StructuredMap(src)
|
||||
}
|
||||
}
|
||||
|
||||
// Mostly for testing.
|
||||
impl<T> From<Vec<(NamespacedKeyword, T)>> for StructuredMap where T: Into<Binding> {
|
||||
fn from(value: Vec<(NamespacedKeyword, T)>) -> Self {
|
||||
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() {
|
||||
sm.insert(k, v);
|
||||
|
@ -414,7 +414,7 @@ impl TypedValue {
|
|||
/// values and wrapping them in a new `ValueRc`. This is expensive, so this might
|
||||
/// be best limited to tests.
|
||||
pub fn typed_ns_keyword(ns: &str, name: &str) -> TypedValue {
|
||||
NamespacedKeyword::new(ns, name).into()
|
||||
Keyword::namespaced(ns, name).into()
|
||||
}
|
||||
|
||||
/// Construct a new `TypedValue::String` instance by cloning the provided
|
||||
|
@ -509,20 +509,20 @@ impl From<String> for TypedValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Arc<NamespacedKeyword>> for TypedValue {
|
||||
fn from(value: Arc<NamespacedKeyword>) -> TypedValue {
|
||||
impl From<Arc<Keyword>> for TypedValue {
|
||||
fn from(value: Arc<Keyword>) -> TypedValue {
|
||||
TypedValue::Keyword(ValueRc::from_arc(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<NamespacedKeyword>> for TypedValue {
|
||||
fn from(value: Rc<NamespacedKeyword>) -> TypedValue {
|
||||
impl From<Rc<Keyword>> for TypedValue {
|
||||
fn from(value: Rc<Keyword>) -> TypedValue {
|
||||
TypedValue::Keyword(ValueRc::from_rc(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NamespacedKeyword> for TypedValue {
|
||||
fn from(value: NamespacedKeyword) -> TypedValue {
|
||||
impl From<Keyword> for TypedValue {
|
||||
fn from(value: Keyword) -> TypedValue {
|
||||
TypedValue::Keyword(ValueRc::new(value))
|
||||
}
|
||||
}
|
||||
|
@ -560,7 +560,7 @@ impl TypedValue {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_kw(self) -> Option<ValueRc<NamespacedKeyword>> {
|
||||
pub fn into_kw(self) -> Option<ValueRc<Keyword>> {
|
||||
match self {
|
||||
TypedValue::Keyword(v) => Some(v),
|
||||
_ => None,
|
||||
|
@ -687,7 +687,7 @@ impl Binding {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_kw(self) -> Option<ValueRc<NamespacedKeyword>> {
|
||||
pub fn into_kw(self) -> Option<ValueRc<Keyword>> {
|
||||
match self {
|
||||
Binding::Scalar(TypedValue::Keyword(v)) => Some(v),
|
||||
_ => None,
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
use edn::types::Value;
|
||||
use edn::symbols;
|
||||
|
||||
/// Declare a lazy static `ident` of type `Value::NamespacedKeyword` with the given `namespace` and
|
||||
/// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and
|
||||
/// `name`.
|
||||
///
|
||||
/// It may look surprising that we declare a new `lazy_static!` block rather than including
|
||||
|
@ -31,7 +31,7 @@ macro_rules! lazy_static_namespaced_keyword_value (
|
|||
($tag:ident, $namespace:expr, $name:expr) => (
|
||||
lazy_static! {
|
||||
pub static ref $tag: Value = {
|
||||
Value::NamespacedKeyword(symbols::NamespacedKeyword::new($namespace, $name))
|
||||
Value::Keyword(symbols::Keyword::namespaced($namespace, $name))
|
||||
};
|
||||
}
|
||||
)
|
||||
|
|
|
@ -38,7 +38,7 @@ pub const USER0: i64 = 0x10000;
|
|||
pub const CORE_SCHEMA_VERSION: u32 = 1;
|
||||
|
||||
lazy_static! {
|
||||
static ref V1_IDENTS: [(symbols::NamespacedKeyword, i64); 40] = {
|
||||
static ref V1_IDENTS: [(symbols::Keyword, i64); 40] = {
|
||||
[(ns_keyword!("db", "ident"), entids::DB_IDENT),
|
||||
(ns_keyword!("db.part", "db"), entids::DB_PART_DB),
|
||||
(ns_keyword!("db", "txInstant"), entids::DB_TX_INSTANT),
|
||||
|
@ -82,14 +82,14 @@ lazy_static! {
|
|||
]
|
||||
};
|
||||
|
||||
static ref V1_PARTS: [(symbols::NamespacedKeyword, i64, i64); 3] = {
|
||||
static ref V1_PARTS: [(symbols::Keyword, i64, i64); 3] = {
|
||||
[(ns_keyword!("db.part", "db"), 0, (1 + V1_IDENTS.len()) as i64),
|
||||
(ns_keyword!("db.part", "user"), USER0, USER0),
|
||||
(ns_keyword!("db.part", "tx"), TX0, TX0),
|
||||
]
|
||||
};
|
||||
|
||||
static ref V1_CORE_SCHEMA: [(symbols::NamespacedKeyword); 16] = {
|
||||
static ref V1_CORE_SCHEMA: [(symbols::Keyword); 16] = {
|
||||
[(ns_keyword!("db", "ident")),
|
||||
(ns_keyword!("db.install", "partition")),
|
||||
(ns_keyword!("db.install", "valueType")),
|
||||
|
@ -162,25 +162,25 @@ lazy_static! {
|
|||
}
|
||||
|
||||
/// Convert (ident, entid) pairs into [:db/add IDENT :db/ident IDENT] `Value` instances.
|
||||
fn idents_to_assertions(idents: &[(symbols::NamespacedKeyword, i64)]) -> Vec<Value> {
|
||||
fn idents_to_assertions(idents: &[(symbols::Keyword, i64)]) -> Vec<Value> {
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|&(ref ident, _)| {
|
||||
let value = Value::NamespacedKeyword(ident.clone());
|
||||
let value = Value::Keyword(ident.clone());
|
||||
Value::Vector(vec![values::DB_ADD.clone(), value.clone(), values::DB_IDENT.clone(), value.clone()])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Convert an ident list into [:db/add :db.schema/core :db.schema/attribute IDENT] `Value` instances.
|
||||
fn schema_attrs_to_assertions(version: u32, idents: &[symbols::NamespacedKeyword]) -> Vec<Value> {
|
||||
let schema_core = Value::NamespacedKeyword(ns_keyword!("db.schema", "core"));
|
||||
let schema_attr = Value::NamespacedKeyword(ns_keyword!("db.schema", "attribute"));
|
||||
let schema_version = Value::NamespacedKeyword(ns_keyword!("db.schema", "version"));
|
||||
fn schema_attrs_to_assertions(version: u32, idents: &[symbols::Keyword]) -> Vec<Value> {
|
||||
let schema_core = Value::Keyword(ns_keyword!("db.schema", "core"));
|
||||
let schema_attr = Value::Keyword(ns_keyword!("db.schema", "attribute"));
|
||||
let schema_version = Value::Keyword(ns_keyword!("db.schema", "version"));
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|ident| {
|
||||
let value = Value::NamespacedKeyword(ident.clone());
|
||||
let value = Value::Keyword(ident.clone());
|
||||
Value::Vector(vec![values::DB_ADD.clone(),
|
||||
schema_core.clone(),
|
||||
schema_attr.clone(),
|
||||
|
@ -194,28 +194,28 @@ fn schema_attrs_to_assertions(version: u32, idents: &[symbols::NamespacedKeyword
|
|||
}
|
||||
|
||||
/// Convert {:ident {:key :value ...} ...} to
|
||||
/// vec![(symbols::NamespacedKeyword(:ident), symbols::NamespacedKeyword(:key), TypedValue(:value)), ...].
|
||||
/// vec![(symbols::Keyword(:ident), symbols::Keyword(:key), TypedValue(:value)), ...].
|
||||
///
|
||||
/// 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::NamespacedKeyword, symbols::NamespacedKeyword, 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::NamespacedKeyword, symbols::NamespacedKeyword, TypedValue)> = vec![];
|
||||
let mut triples: Vec<(symbols::Keyword, symbols::Keyword, TypedValue)> = vec![];
|
||||
// TODO: Consider `flat_map` and `map` rather than loop.
|
||||
match *symbolic_schema {
|
||||
Value::Map(ref m) => {
|
||||
for (ident, mp) in m {
|
||||
let ident = match ident {
|
||||
&Value::NamespacedKeyword(ref ident) => ident,
|
||||
_ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident)))
|
||||
&Value::Keyword(ref ident) => ident,
|
||||
v => bail!(ErrorKind::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::NamespacedKeyword(ref attr) => attr,
|
||||
_ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr)))
|
||||
};
|
||||
&Value::Keyword(ref attr) => attr,
|
||||
_ => bail!(ErrorKind::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
|
||||
|
@ -286,19 +286,19 @@ pub(crate) fn bootstrap_ident_map() -> IdentMap {
|
|||
|
||||
pub(crate) fn bootstrap_schema() -> Schema {
|
||||
let ident_map = bootstrap_ident_map();
|
||||
let bootstrap_triples = symbolic_schema_to_triples(&ident_map, &V1_SYMBOLIC_SCHEMA).unwrap();
|
||||
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> {
|
||||
let bootstrap_assertions: Value = Value::Vector([
|
||||
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).unwrap(),
|
||||
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::parse::entities(&bootstrap_assertions.to_string()).unwrap();
|
||||
let bootstrap_entities: Vec<Entity> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions");
|
||||
return bootstrap_entities;
|
||||
}
|
||||
|
|
|
@ -422,7 +422,7 @@ impl TypedSQLValue for TypedValue {
|
|||
&Value::Uuid(x) => Some(TypedValue::Uuid(x)),
|
||||
&Value::Float(ref x) => Some(TypedValue::Double(x.clone())),
|
||||
&Value::Text(ref x) => Some(x.clone().into()),
|
||||
&Value::NamespacedKeyword(ref x) => Some(x.clone().into()),
|
||||
&Value::Keyword(ref x) => Some(x.clone().into()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ impl TypedSQLValue for TypedValue {
|
|||
&TypedValue::Double(x) => (Value::Float(x), ValueType::Double),
|
||||
&TypedValue::String(ref x) => (Value::Text(x.as_ref().clone()), ValueType::String),
|
||||
&TypedValue::Uuid(ref u) => (Value::Uuid(u.clone()), ValueType::Uuid),
|
||||
&TypedValue::Keyword(ref x) => (Value::NamespacedKeyword(x.as_ref().clone()), ValueType::Keyword),
|
||||
&TypedValue::Keyword(ref x) => (Value::Keyword(x.as_ref().clone()), ValueType::Keyword),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1155,7 +1155,7 @@ mod tests {
|
|||
use edn;
|
||||
use mentat_core::{
|
||||
HasSchema,
|
||||
NamespacedKeyword,
|
||||
Keyword,
|
||||
Schema,
|
||||
attribute,
|
||||
};
|
||||
|
@ -1840,7 +1840,7 @@ mod tests {
|
|||
|
||||
// Once we've done so, the schema shows it's not unique…
|
||||
{
|
||||
let attr = conn.schema.attribute_for_ident(&NamespacedKeyword::new("test", "ident")).unwrap().0;
|
||||
let attr = conn.schema.attribute_for_ident(&Keyword::namespaced("test", "ident")).unwrap().0;
|
||||
assert_eq!(None, attr.unique);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ impl Datom {
|
|||
let f = |entid: &Entid| -> edn::Value {
|
||||
match *entid {
|
||||
Entid::Entid(ref y) => edn::Value::Integer(y.clone()),
|
||||
Entid::Ident(ref y) => edn::Value::NamespacedKeyword(y.clone()),
|
||||
Entid::Ident(ref y) => edn::Value::Keyword(y.clone()),
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -73,7 +73,14 @@ impl TransactableValue for ValueAndSpan {
|
|||
use self::SpannedValue::*;
|
||||
match self.inner {
|
||||
Integer(v) => Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Entid(v))),
|
||||
NamespacedKeyword(v) => Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Ident(v))),
|
||||
Keyword(v) => {
|
||||
if v.is_namespaced() {
|
||||
Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Ident(v)))
|
||||
} else {
|
||||
// We only allow namespaced idents.
|
||||
bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace))
|
||||
}
|
||||
},
|
||||
Text(v) => Ok(EntidOrLookupRefOrTempId::TempId(TempId::External(v))),
|
||||
List(ls) => {
|
||||
let mut it = ls.iter();
|
||||
|
@ -102,7 +109,6 @@ impl TransactableValue for ValueAndSpan {
|
|||
Uuid(_) |
|
||||
PlainSymbol(_) |
|
||||
NamespacedSymbol(_) |
|
||||
Keyword(_) |
|
||||
Vector(_) |
|
||||
Set(_) |
|
||||
Map(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)),
|
||||
|
|
|
@ -98,16 +98,16 @@ pub use types::{
|
|||
TxReport,
|
||||
};
|
||||
|
||||
pub fn to_namespaced_keyword(s: &str) -> Result<symbols::NamespacedKeyword> {
|
||||
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::NamespacedKeyword::new(namespace, name)),
|
||||
(Some(""), Some(namespace), Some(name), None) => Some(symbols::Keyword::namespaced(namespace, name)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// TODO Use custom ErrorKind https://github.com/brson/error-chain/issues/117
|
||||
nsk.ok_or(ErrorKind::NotYetImplemented(format!("InvalidNamespacedKeyword: {}", s)).into())
|
||||
nsk.ok_or(ErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into())
|
||||
}
|
||||
|
||||
/// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?).
|
||||
|
|
|
@ -72,7 +72,7 @@ pub enum AttributeAlteration {
|
|||
/// An alteration to an ident.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum IdentAlteration {
|
||||
Ident(symbols::NamespacedKeyword),
|
||||
Ident(symbols::Keyword),
|
||||
}
|
||||
|
||||
/// Summarizes changes to metadata such as a a `Schema` and (in the future) a `PartitionMap`.
|
||||
|
@ -285,7 +285,7 @@ pub fn update_schema_from_entid_quadruples<U>(schema: &mut Schema, assertions: U
|
|||
// 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 ident_set: AddRetractAlterSet<Entid, symbols::NamespacedKeyword> = AddRetractAlterSet::default();
|
||||
let mut ident_set: AddRetractAlterSet<Entid, symbols::Keyword> = AddRetractAlterSet::default();
|
||||
|
||||
for (e, a, typed_value, added) in assertions.into_iter() {
|
||||
// Here we handle :db/ident assertions.
|
||||
|
|
|
@ -237,20 +237,20 @@ impl AttributeBuilder {
|
|||
}
|
||||
|
||||
pub trait SchemaBuilding {
|
||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::NamespacedKeyword>;
|
||||
fn require_entid(&self, ident: &symbols::NamespacedKeyword) -> Result<KnownEntid>;
|
||||
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_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
||||
where U: IntoIterator<Item=(symbols::NamespacedKeyword, symbols::NamespacedKeyword, TypedValue)>;
|
||||
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>;
|
||||
}
|
||||
|
||||
impl SchemaBuilding for Schema {
|
||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::NamespacedKeyword> {
|
||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> {
|
||||
self.get_ident(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into())
|
||||
}
|
||||
|
||||
fn require_entid(&self, ident: &symbols::NamespacedKeyword) -> Result<KnownEntid> {
|
||||
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> {
|
||||
self.get_entid(&ident).ok_or(ErrorKind::UnrecognizedIdent(ident.to_string()).into())
|
||||
}
|
||||
|
||||
|
@ -266,9 +266,9 @@ impl SchemaBuilding for Schema {
|
|||
Ok(Schema::new(ident_map, entid_map, attribute_map))
|
||||
}
|
||||
|
||||
/// Turn vec![(NamespacedKeyword(:ident), NamespacedKeyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
|
||||
/// Turn vec![(Keyword(:ident), Keyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
|
||||
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
||||
where U: IntoIterator<Item=(symbols::NamespacedKeyword, symbols::NamespacedKeyword, 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(ErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?;
|
||||
|
@ -342,11 +342,11 @@ impl SchemaTypeChecking for Schema {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use self::edn::NamespacedKeyword;
|
||||
use self::edn::Keyword;
|
||||
use errors::Error;
|
||||
|
||||
fn add_attribute(schema: &mut Schema,
|
||||
ident: NamespacedKeyword,
|
||||
ident: Keyword,
|
||||
entid: Entid,
|
||||
attribute: Attribute) {
|
||||
|
||||
|
@ -364,7 +364,7 @@ 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, NamespacedKeyword::new("foo", "bar"), 97, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 97, Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
fulltext: false,
|
||||
|
@ -374,7 +374,7 @@ mod test {
|
|||
no_history: false,
|
||||
});
|
||||
// attribute is unique by value and an index
|
||||
add_attribute(&mut schema, NamespacedKeyword::new("foo", "baz"), 98, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "baz"), 98, Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Long,
|
||||
fulltext: false,
|
||||
|
@ -384,7 +384,7 @@ mod test {
|
|||
no_history: false,
|
||||
});
|
||||
// attribue is unique by identity and an index
|
||||
add_attribute(&mut schema, NamespacedKeyword::new("foo", "bat"), 99, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bat"), 99, Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Ref,
|
||||
fulltext: false,
|
||||
|
@ -394,7 +394,7 @@ mod test {
|
|||
no_history: false,
|
||||
});
|
||||
// attribute is a components and a `Ref`
|
||||
add_attribute(&mut schema, NamespacedKeyword::new("foo", "bak"), 100, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bak"), 100, Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Ref,
|
||||
fulltext: false,
|
||||
|
@ -404,7 +404,7 @@ mod test {
|
|||
no_history: false,
|
||||
});
|
||||
// fulltext attribute is a string and an index
|
||||
add_attribute(&mut schema, NamespacedKeyword::new("foo", "bap"), 101, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bap"), 101, Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::String,
|
||||
fulltext: true,
|
||||
|
@ -421,7 +421,7 @@ mod test {
|
|||
fn invalid_schema_unique_value_not_index() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute unique by value but not index
|
||||
let ident = NamespacedKeyword::new("foo", "bar");
|
||||
let ident = Keyword::namespaced("foo", "bar");
|
||||
add_attribute(&mut schema, ident , 99, Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
|
@ -445,7 +445,7 @@ mod 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, NamespacedKeyword::new("foo", "bar"), 99, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Long,
|
||||
fulltext: false,
|
||||
|
@ -468,7 +468,7 @@ mod 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, NamespacedKeyword::new("foo", "bar"), 99, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::Boolean,
|
||||
fulltext: false,
|
||||
|
@ -491,7 +491,7 @@ mod test {
|
|||
fn invalid_schema_fulltext_not_index() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute that is fulltext is not an index
|
||||
add_attribute(&mut schema, NamespacedKeyword::new("foo", "bar"), 99, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
||||
index: false,
|
||||
value_type: ValueType::String,
|
||||
fulltext: true,
|
||||
|
@ -513,7 +513,7 @@ mod test {
|
|||
fn invalid_schema_fulltext_index_not_string() {
|
||||
let mut schema = Schema::default();
|
||||
// attribute that is fulltext and not a `String`
|
||||
add_attribute(&mut schema, NamespacedKeyword::new("foo", "bar"), 99, Attribute {
|
||||
add_attribute(&mut schema, Keyword::namespaced("foo", "bar"), 99, Attribute {
|
||||
index: true,
|
||||
value_type: ValueType::Long,
|
||||
fulltext: true,
|
||||
|
|
|
@ -64,7 +64,7 @@ use db::{
|
|||
PartitionMapping,
|
||||
};
|
||||
use edn::{
|
||||
NamespacedKeyword,
|
||||
Keyword,
|
||||
};
|
||||
use entids;
|
||||
use errors;
|
||||
|
@ -165,7 +165,7 @@ pub struct Tx<'conn, 'a, W> where W: TransactWatcher {
|
|||
/// something suitable for the entity position rather than something suitable for a value position.
|
||||
pub fn remove_db_id(map: &mut entmod::MapNotation) -> Result<Option<entmod::EntidOrLookupRefOrTempId>> {
|
||||
// TODO: extract lazy defined constant.
|
||||
let db_id_key = entmod::Entid::Ident(NamespacedKeyword::new("db", "id"));
|
||||
let db_id_key = entmod::Entid::Ident(Keyword::namespaced("db", "id"));
|
||||
|
||||
let db_id: Option<entmod::EntidOrLookupRefOrTempId> = if let Some(id) = map.remove(&db_id_key) {
|
||||
match id {
|
||||
|
@ -281,7 +281,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
|
|||
}
|
||||
}
|
||||
|
||||
fn ensure_ident_exists(&self, e: &NamespacedKeyword) -> Result<KnownEntid> {
|
||||
fn ensure_ident_exists(&self, e: &Keyword) -> Result<KnownEntid> {
|
||||
self.schema.require_entid(e)
|
||||
}
|
||||
|
||||
|
|
|
@ -54,5 +54,5 @@ fn test_to_edn_value_pair() {
|
|||
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::NamespacedKeyword(symbols::NamespacedKeyword::new("db", "keyword")), ValueType::Keyword));
|
||||
assert_eq!(TypedValue::typed_ns_keyword("db", "keyword").to_edn_value_pair(), (edn::Value::Keyword(symbols::Keyword::namespaced("db", "keyword")), ValueType::Keyword));
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ uuid = { version = "0.5", features = ["v4", "serde"] }
|
|||
serde = { version = "1.0", optional = true }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
[features]
|
||||
serde_support = ["serde", "serde_derive"]
|
||||
|
||||
|
|
|
@ -203,13 +203,13 @@ pub op -> OpType
|
|||
= ":db/add" { OpType::Add }
|
||||
/ ":db/retract" { OpType::Retract }
|
||||
|
||||
raw_keyword -> NamespacedKeyword
|
||||
= keyword_prefix ns:$(symbol_namespace) namespace_separator n:$(symbol_name) { NamespacedKeyword::new(ns, n) }
|
||||
raw_keyword -> Keyword
|
||||
= keyword_prefix ns:$(symbol_namespace) namespace_separator n:$(symbol_name) { Keyword::namespaced(ns, n) }
|
||||
|
||||
raw_forward_keyword -> NamespacedKeyword
|
||||
raw_forward_keyword -> Keyword
|
||||
= v:raw_keyword {? if v.is_forward() { Ok(v) } else { Err("expected :forward/keyword") } }
|
||||
|
||||
raw_backward_keyword -> NamespacedKeyword
|
||||
raw_backward_keyword -> Keyword
|
||||
= v:raw_keyword {? if v.is_backward() { Ok(v) } else { Err("expected :backward/_keyword") } }
|
||||
|
||||
entid -> Entid
|
||||
|
@ -227,7 +227,7 @@ lookup_ref -> LookupRef
|
|||
= "(" __ "lookup-ref" __ a:(entid) __ v:(value) __ ")" { LookupRef { a, v: v.without_spans() } }
|
||||
|
||||
tx_function -> TxFunction
|
||||
= "(" __ n:$(symbol_name) __ ")" { TxFunction { op: PlainSymbol::new(n) } }
|
||||
= "(" __ n:$(symbol_name) __ ")" { TxFunction { op: PlainSymbol::plain(n) } }
|
||||
|
||||
entity_place -> EntidOrLookupRefOrTempId
|
||||
= v:raw_text { EntidOrLookupRefOrTempId::TempId(TempId::External(v)) }
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::collections::BTreeMap;
|
|||
use std::fmt;
|
||||
|
||||
use symbols::{
|
||||
NamespacedKeyword,
|
||||
Keyword,
|
||||
PlainSymbol,
|
||||
};
|
||||
use types::{
|
||||
|
@ -51,7 +51,7 @@ impl fmt::Display for TempId {
|
|||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
pub enum Entid {
|
||||
Entid(i64),
|
||||
Ident(NamespacedKeyword),
|
||||
Ident(Keyword),
|
||||
}
|
||||
|
||||
impl Entid {
|
||||
|
|
|
@ -23,6 +23,8 @@ extern crate serde;
|
|||
extern crate serde_derive;
|
||||
|
||||
pub mod entities;
|
||||
// Intentionally not pub.
|
||||
mod namespaceable_name;
|
||||
pub mod symbols;
|
||||
pub mod types;
|
||||
pub mod pretty_print;
|
||||
|
@ -53,7 +55,6 @@ pub use types::{
|
|||
|
||||
pub use symbols::{
|
||||
Keyword,
|
||||
NamespacedKeyword,
|
||||
NamespacedSymbol,
|
||||
PlainSymbol,
|
||||
};
|
||||
|
|
285
edn/src/namespaceable_name.rs
Normal file
285
edn/src/namespaceable_name.rs
Normal file
|
@ -0,0 +1,285 @@
|
|||
// Copyright 2018 Mozilla
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
// this file except in compliance with the License. You may obtain a copy of the
|
||||
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
use std::cmp::{
|
||||
Ord,
|
||||
Ordering,
|
||||
PartialOrd,
|
||||
};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(feature = "serde_support")]
|
||||
use serde::{
|
||||
de::{self, Deserialize, Deserializer},
|
||||
ser::{Serialize, Serializer}
|
||||
};
|
||||
|
||||
// Data storage for both NamespaceableKeyword and NamespaceableSymbol.
|
||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||
pub struct NamespaceableName {
|
||||
// The bytes that make up the namespace followed directly by those
|
||||
// that make up the name.
|
||||
components: String,
|
||||
|
||||
// The index (in bytes) into `components` where the namespace ends and
|
||||
// name begins.
|
||||
//
|
||||
// If this is zero, it means that this is _not_ a namespaced value!
|
||||
//
|
||||
// Important: The following invariants around `boundary` must be maintained
|
||||
// for memory safety.
|
||||
//
|
||||
// 1. `boundary` must always be less than or equal to `components.len()`.
|
||||
// 2. `boundary` must be byte index that points to a character boundary,
|
||||
// and not point into the middle of a utf8 codepoint. That is,
|
||||
// `components.is_char_boundary(boundary)` must always be true.
|
||||
//
|
||||
// These invariants are enforced by `NamespaceableName::namespaced()`, and since
|
||||
// we never mutate `NamespaceableName`s, that's the only place we need to
|
||||
// worry about them.
|
||||
boundary: usize,
|
||||
}
|
||||
|
||||
impl NamespaceableName {
|
||||
#[inline]
|
||||
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.");
|
||||
|
||||
NamespaceableName {
|
||||
components: n,
|
||||
boundary: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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.");
|
||||
|
||||
let mut dest = String::with_capacity(n.len() + ns.len());
|
||||
|
||||
dest.push_str(ns);
|
||||
dest.push_str(n);
|
||||
|
||||
let boundary = ns.len();
|
||||
|
||||
NamespaceableName {
|
||||
components: dest,
|
||||
boundary: boundary,
|
||||
}
|
||||
}
|
||||
|
||||
fn dwim<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 {
|
||||
Self::plain(name.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_namespaced(&self) -> bool {
|
||||
self.boundary > 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_backward(&self) -> bool {
|
||||
self.name().starts_with('_')
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_forward(&self) -> bool {
|
||||
!self.is_backward()
|
||||
}
|
||||
|
||||
pub fn to_reversed(&self) -> NamespaceableName {
|
||||
let name = self.name();
|
||||
|
||||
if name.starts_with('_') {
|
||||
Self::dwim(self.namespace(), &name[1..])
|
||||
} else {
|
||||
Self::dwim(self.namespace(), &format!("_{}", name))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn namespace(&self) -> Option<&str> {
|
||||
if self.boundary > 0 {
|
||||
Some(&self.components[0..self.boundary])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
if self.boundary == 0 {
|
||||
&self.components
|
||||
} else {
|
||||
&self.components[self.boundary..]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn components<'a>(&'a self) -> (&'a str, &'a str) {
|
||||
self.components.split_at(self.boundary)
|
||||
}
|
||||
}
|
||||
|
||||
// We order by namespace then by name.
|
||||
// Non-namespaced values always sort before.
|
||||
impl PartialOrd for NamespaceableName {
|
||||
fn partial_cmp(&self, other: &NamespaceableName) -> Option<Ordering> {
|
||||
match (self.boundary, other.boundary) {
|
||||
(0, 0) => self.components.partial_cmp(&other.components),
|
||||
(0, _) => Some(Ordering::Less),
|
||||
(_, 0) => Some(Ordering::Greater),
|
||||
(_, _) => {
|
||||
// Just use a lexicographic ordering.
|
||||
self.components().partial_cmp(&other.components())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NamespaceableName {
|
||||
fn cmp(&self, other: &NamespaceableName) -> Ordering {
|
||||
self.components().cmp(&other.components())
|
||||
}
|
||||
}
|
||||
|
||||
// We could derive this, but it's really hard to make sense of as-is.
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// This is convoluted, but the basic idea is that since we don't want to rely on our input being
|
||||
// correct, we'll need to implement a custom serializer no matter what (e.g. we can't just
|
||||
// `derive(Deserialize)` since `unsafe` code depends on `self.boundary` being a valid index).
|
||||
//
|
||||
// We'd also like for users consuming our serialized data as e.g. JSON not to have to learn how we
|
||||
// store NamespaceableName internally, since it's very much an implementation detail.
|
||||
//
|
||||
// We achieve both of these by implemeting a type that can serialize in way that's both user-
|
||||
// friendly and automatic (e.g. `derive`d), and just pass all work off to it in our custom
|
||||
// implementation of Serialize and Deserialize.
|
||||
#[cfg(feature = "serde_support")]
|
||||
#[cfg_attr(feature = "serde_support", serde(rename = "NamespaceableName"))]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
struct SerializedNamespaceableName<'a> {
|
||||
namespace: Option<&'a str>,
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_support")]
|
||||
impl<'de> Deserialize<'de> for NamespaceableName {
|
||||
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"))
|
||||
} else {
|
||||
Ok(NamespaceableName::namespaced(ns, separated.name))
|
||||
}
|
||||
} else {
|
||||
Ok(NamespaceableName::plain(separated.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_support")]
|
||||
impl Serialize for NamespaceableName {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
let ser = SerializedNamespaceableName {
|
||||
namespace: self.namespace(),
|
||||
name: self.name(),
|
||||
};
|
||||
ser.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::panic;
|
||||
|
||||
#[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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let s = NamespaceableName::namespaced("aaaaa", "b");
|
||||
assert_eq!(s.namespace(), Some("aaaaa"));
|
||||
assert_eq!(s.name(), "b");
|
||||
assert_eq!(s.components(), ("aaaaa", "b"));
|
||||
|
||||
let s = NamespaceableName::namespaced("b", "aaaaa");
|
||||
assert_eq!(s.namespace(), Some("b"));
|
||||
assert_eq!(s.name(), "aaaaa");
|
||||
assert_eq!(s.components(), ("b", "aaaaa"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_order() {
|
||||
let n0 = NamespaceableName::namespaced("a", "aa");
|
||||
let n1 = NamespaceableName::namespaced("aa", "a");
|
||||
|
||||
let n2 = NamespaceableName::namespaced("a", "ab");
|
||||
let n3 = NamespaceableName::namespaced("aa", "b");
|
||||
|
||||
let n4 = NamespaceableName::namespaced("b", "ab");
|
||||
let n5 = NamespaceableName::namespaced("ba", "b");
|
||||
|
||||
let n6 = NamespaceableName::namespaced("z", "zz");
|
||||
|
||||
let mut arr = [
|
||||
n5.clone(),
|
||||
n6.clone(),
|
||||
n0.clone(),
|
||||
n3.clone(),
|
||||
n2.clone(),
|
||||
n1.clone(),
|
||||
n4.clone()
|
||||
];
|
||||
|
||||
arr.sort();
|
||||
|
||||
assert_eq!(arr, [
|
||||
n0.clone(),
|
||||
n2.clone(),
|
||||
n1.clone(),
|
||||
n3.clone(),
|
||||
n4.clone(),
|
||||
n5.clone(),
|
||||
n6.clone(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -68,10 +68,9 @@ impl Value {
|
|||
.append(pp.text("}"))
|
||||
.group()
|
||||
}
|
||||
Value::NamespacedSymbol(ref v) => pp.text(v.namespace.as_ref()).append("/").append(v.name.as_ref()),
|
||||
Value::PlainSymbol(ref v) => pp.text(v.0.as_ref()),
|
||||
Value::NamespacedKeyword(ref v) => pp.text(":").append(v.namespace.as_ref()).append("/").append(v.name.as_ref()),
|
||||
Value::Keyword(ref v) => pp.text(":").append(v.0.as_ref()),
|
||||
Value::NamespacedSymbol(ref v) => pp.text(v.namespace()).append("/").append(v.name()),
|
||||
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_ref()).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("\""),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Mozilla
|
||||
// Copyright 2018 Mozilla
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
// this file except in compliance with the License. You may obtain a copy of the
|
||||
|
@ -9,11 +9,12 @@
|
|||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use namespaceable_name::NamespaceableName;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ns_keyword {
|
||||
($ns: expr, $name: expr) => {{
|
||||
$crate::NamespacedKeyword::new($ns, $name)
|
||||
$crate::Keyword::namespaced($ns, $name)
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -22,12 +23,7 @@ macro_rules! ns_keyword {
|
|||
pub struct PlainSymbol(pub String);
|
||||
|
||||
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)]
|
||||
pub struct NamespacedSymbol {
|
||||
// We derive PartialOrd, which implements a lexicographic based
|
||||
// on the order of members, so put namespace first.
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
}
|
||||
pub struct NamespacedSymbol(NamespaceableName);
|
||||
|
||||
/// A keyword is a symbol, optionally with a namespace, that prints with a leading colon.
|
||||