use std::collections::BTreeMap;
use crate::{
DbError, IndexId, LabelId, ProjectionId, PropertyFamily, PropertyKeyId, PropertyType,
RelationTypeId, RoleId, encode_composite_key,
typed::{EqualityIndex, Key, ValueType},
};
#[derive(Clone, Debug)]
pub struct GraphProjectionSpec {
pub(crate) name: String,
pub(crate) relation_types: Vec<String>,
pub(crate) source_role: String,
pub(crate) target_role: String,
}
#[derive(Clone, Debug)]
pub struct HypergraphProjectionSpec {
pub(crate) name: String,
pub(crate) relation_types: Vec<String>,
pub(crate) source_roles: Vec<String>,
pub(crate) target_roles: Vec<String>,
}
#[derive(Clone, Debug, Default)]
pub struct Schema {
pub(crate) roles: Vec<String>,
pub(crate) labels: Vec<String>,
pub(crate) relation_types: Vec<String>,
pub(crate) keys: Vec<(String, PropertyFamily, PropertyType)>,
pub(crate) equality_indexes: Vec<(String, String)>,
pub(crate) graph_projections: Vec<GraphProjectionSpec>,
pub(crate) hypergraph_projections: Vec<HypergraphProjectionSpec>,
}
impl Schema {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn role(mut self, name: &str) -> Self {
self.roles.push(name.to_owned());
self
}
#[must_use]
pub fn label(mut self, name: &str) -> Self {
self.labels.push(name.to_owned());
self
}
#[must_use]
pub fn relation_type(mut self, name: &str) -> Self {
self.relation_types.push(name.to_owned());
self
}
#[must_use]
pub fn key<T: ValueType>(mut self, name: &str, family: PropertyFamily) -> Self {
self.keys.push((name.to_owned(), family, T::TYPE));
self
}
#[must_use]
pub fn equality_index(mut self, name: &str, key: &str) -> Self {
self.equality_indexes
.push((name.to_owned(), key.to_owned()));
self
}
#[must_use]
pub fn graph_projection(
mut self,
name: &str,
relation_types: &[&str],
source_role: &str,
target_role: &str,
) -> Self {
self.graph_projections.push(GraphProjectionSpec {
name: name.to_owned(),
relation_types: relation_types
.iter()
.map(|name| (*name).to_owned())
.collect(),
source_role: source_role.to_owned(),
target_role: target_role.to_owned(),
});
self
}
#[must_use]
pub fn hypergraph_projection(
mut self,
name: &str,
relation_types: &[&str],
source_roles: &[&str],
target_roles: &[&str],
) -> Self {
self.hypergraph_projections.push(HypergraphProjectionSpec {
name: name.to_owned(),
relation_types: relation_types
.iter()
.map(|name| (*name).to_owned())
.collect(),
source_roles: source_roles.iter().map(|name| (*name).to_owned()).collect(),
target_roles: target_roles.iter().map(|name| (*name).to_owned()).collect(),
});
self
}
#[must_use]
pub fn fingerprint(&self) -> u64 {
use std::hash::{Hash, Hasher};
let mut items = Vec::new();
for role in &self.roles {
items.push(encode_composite_key(&["role", role]));
}
for label in &self.labels {
items.push(encode_composite_key(&["label", label]));
}
for relation_type in &self.relation_types {
items.push(encode_composite_key(&["rtype", relation_type]));
}
for (name, family, value_type) in &self.keys {
items.push(encode_composite_key(&[
"key",
name,
&format!("{family:?}"),
&format!("{value_type:?}"),
]));
}
for (name, key) in &self.equality_indexes {
items.push(encode_composite_key(&["eqidx", name, key]));
}
for projection in &self.graph_projections {
items.push(encode_composite_key(&[
"gproj",
&projection.name,
&projection.source_role,
&projection.target_role,
&join_sorted(&projection.relation_types),
]));
}
for projection in &self.hypergraph_projections {
items.push(encode_composite_key(&[
"hproj",
&projection.name,
&join_sorted(&projection.relation_types),
&join_sorted(&projection.source_roles),
&join_sorted(&projection.target_roles),
]));
}
items.sort();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
items.len().hash(&mut hasher);
for item in &items {
item.hash(&mut hasher);
}
hasher.finish()
}
}
fn join_sorted(names: &[String]) -> String {
let mut sorted: Vec<&str> = names.iter().map(String::as_str).collect();
sorted.sort_unstable();
encode_composite_key(&sorted)
}
#[derive(Clone, Debug, Default)]
pub struct Bound {
pub(crate) roles: BTreeMap<String, RoleId>,
pub(crate) labels: BTreeMap<String, LabelId>,
pub(crate) relation_types: BTreeMap<String, RelationTypeId>,
pub(crate) keys: BTreeMap<String, (PropertyKeyId, PropertyType)>,
pub(crate) equality_indexes: BTreeMap<String, (IndexId, PropertyType)>,
pub(crate) projections: BTreeMap<String, ProjectionId>,
}
impl Bound {
pub fn role(&self, name: &str) -> Result<RoleId, DbError> {
self.roles.get(name).copied().ok_or_else(|| {
DbError::Catalog(crate::error::CatalogError::UnknownName {
kind: "role",
name: name.to_owned(),
})
})
}
pub fn label(&self, name: &str) -> Result<LabelId, DbError> {
self.labels.get(name).copied().ok_or_else(|| {
DbError::Catalog(crate::error::CatalogError::UnknownName {
kind: "label",
name: name.to_owned(),
})
})
}
pub fn relation_type(&self, name: &str) -> Result<RelationTypeId, DbError> {
self.relation_types.get(name).copied().ok_or_else(|| {
DbError::Catalog(crate::error::CatalogError::UnknownName {
kind: "relation type",
name: name.to_owned(),
})
})
}
pub fn key<T: ValueType>(&self, name: &str) -> Result<Key<T>, DbError> {
let (id, value_type) = self.keys.get(name).copied().ok_or_else(|| {
DbError::Catalog(crate::error::CatalogError::UnknownName {
kind: "property key",
name: name.to_owned(),
})
})?;
if value_type == T::TYPE {
Ok(Key::from_id(id))
} else {
Err(DbError::Catalog(
crate::error::CatalogError::SchemaConflict {
name: name.to_owned(),
reason: "property key value type differs from the requested typed handle",
},
))
}
}
pub fn equality_index<T: ValueType>(&self, name: &str) -> Result<EqualityIndex<T>, DbError> {
let (id, value_type) = self.equality_indexes.get(name).copied().ok_or_else(|| {
DbError::Catalog(crate::error::CatalogError::UnknownName {
kind: "index",
name: name.to_owned(),
})
})?;
if value_type == T::TYPE {
Ok(EqualityIndex::from_id(id))
} else {
Err(DbError::Catalog(
crate::error::CatalogError::SchemaConflict {
name: name.to_owned(),
reason: "equality index value type differs from the requested typed handle",
},
))
}
}
pub fn projection(&self, name: &str) -> Result<ProjectionId, DbError> {
self.projections.get(name).copied().ok_or_else(|| {
DbError::Catalog(crate::error::CatalogError::UnknownName {
kind: "projection",
name: name.to_owned(),
})
})
}
}