use bimap::BiHashMap;
use smol_str::SmolStr;
use std::collections::{HashMap, HashSet};
use crate::types::{CanonicalKey, LabelId, Primitive, PropKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum SchemaMode {
#[default]
Auto = 0,
Strict = 1,
}
impl SchemaMode {
pub fn to_u8(self) -> u8 {
self as u8
}
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(SchemaMode::Auto),
1 => Some(SchemaMode::Strict),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum EdgeMode {
#[default]
Single = 0,
Multi = 1,
}
impl EdgeMode {
pub fn to_u8(self) -> u8 {
self as u8
}
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(EdgeMode::Single),
1 => Some(EdgeMode::Multi),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DataType {
Null = 0,
Bool = 1,
Int32 = 2,
Int64 = 3,
Float32 = 4,
Float64 = 5,
String = 6,
Uuid = 7,
UInt16 = 8,
Bytes = 9,
}
impl DataType {
pub fn from_primitive(val: &Primitive) -> Self {
match val {
Primitive::Null => DataType::Null,
Primitive::Bool(_) => DataType::Bool,
Primitive::Int32(_) => DataType::Int32,
Primitive::Int64(_) => DataType::Int64,
Primitive::UInt16(_) => DataType::UInt16,
Primitive::Float32(_) => DataType::Float32,
Primitive::Float64(_) => DataType::Float64,
Primitive::String(_) => DataType::String,
Primitive::Uuid(_) => DataType::Uuid,
Primitive::Bytes(_) => DataType::Bytes,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(DataType::Null),
1 => Some(DataType::Bool),
2 => Some(DataType::Int32),
3 => Some(DataType::Int64),
4 => Some(DataType::Float32),
5 => Some(DataType::Float64),
6 => Some(DataType::String),
7 => Some(DataType::Uuid),
8 => Some(DataType::UInt16),
9 => Some(DataType::Bytes),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PropKeyConfig {
pub data_type: DataType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GraphOptions {
pub mode: SchemaMode,
pub edge_mode: EdgeMode,
}
impl Default for GraphOptions {
fn default() -> Self {
Self { mode: SchemaMode::Auto, edge_mode: EdgeMode::Single }
}
}
pub(crate) const MAX_LABELS: usize = i32::MAX as usize;
pub(crate) const MAX_PROP_KEYS: usize = (1 << 15) - 1;
#[derive(Debug, Clone)]
pub(crate) struct Schema {
pub mode: SchemaMode,
pub edge_mode: EdgeMode,
pub version: u64,
pub vertex_labels: BiHashMap<LabelId, SmolStr>,
pub edge_labels: BiHashMap<LabelId, SmolStr>,
pub prop_keys: BiHashMap<u16, PropKey>,
pub prop_key_types: HashMap<u16, PropKeyConfig>,
pub persisted_vertex_labels: HashSet<LabelId>,
pub persisted_edge_labels: HashSet<LabelId>,
pub persisted_prop_keys: HashSet<u16>,
}
impl Default for Schema {
fn default() -> Self {
use crate::types::prop_key::{ID, ID_KEY_ID, LABEL, LABEL_KEY_ID, RANK, RANK_KEY_ID};
use bimap::BiHashMap;
use std::collections::{HashMap, HashSet};
let mut prop_keys = BiHashMap::new();
prop_keys.insert(ID_KEY_ID, ID);
prop_keys.insert(LABEL_KEY_ID, LABEL);
prop_keys.insert(RANK_KEY_ID, RANK);
let mut prop_key_types = HashMap::new();
prop_key_types.insert(ID_KEY_ID, PropKeyConfig { data_type: DataType::Int64 });
prop_key_types.insert(LABEL_KEY_ID, PropKeyConfig { data_type: DataType::Int32 });
prop_key_types.insert(RANK_KEY_ID, PropKeyConfig { data_type: DataType::UInt16 });
let mut persisted_prop_keys = HashSet::new();
persisted_prop_keys.insert(ID_KEY_ID);
persisted_prop_keys.insert(LABEL_KEY_ID);
persisted_prop_keys.insert(RANK_KEY_ID);
Schema {
mode: SchemaMode::Auto,
edge_mode: EdgeMode::Single,
version: 0,
vertex_labels: BiHashMap::new(),
edge_labels: BiHashMap::new(),
prop_keys,
prop_key_types,
persisted_vertex_labels: HashSet::new(),
persisted_edge_labels: HashSet::new(),
persisted_prop_keys,
}
}
}
impl Schema {
pub fn new() -> Self {
Self::default()
}
pub fn vertex_label_str(&self, id: LabelId) -> Option<&SmolStr> {
self.vertex_labels.get_by_left(&id)
}
pub fn vertex_labels_count(&self) -> usize {
self.vertex_labels.len()
}
pub fn vertex_label_id(&self, name: &str) -> Option<LabelId> {
self.vertex_labels.get_by_right(name).copied()
}
pub fn register_vertex_label(&mut self, name: impl Into<SmolStr>) -> Option<LabelId> {
let s = name.into();
if let Some(&id) = self.vertex_labels.get_by_right(&s) {
return Some(id);
}
if self.vertex_labels.len() >= MAX_LABELS {
return None;
}
let id = self.vertex_labels.len() as LabelId + 1;
self.vertex_labels.insert(id, s);
Some(id)
}
pub fn edge_label_str(&self, id: LabelId) -> Option<&SmolStr> {
self.edge_labels.get_by_left(&id)
}
pub fn edge_labels_count(&self) -> usize {
self.edge_labels.len()
}
pub fn edge_label_id(&self, name: &str) -> Option<LabelId> {
self.edge_labels.get_by_right(name).copied()
}
pub fn register_edge_label(&mut self, name: impl Into<SmolStr>) -> Option<LabelId> {
let s = name.into();
if let Some(&id) = self.edge_labels.get_by_right(&s) {
return Some(id);
}
if self.edge_labels.len() >= MAX_LABELS {
return None;
}
let id = self.edge_labels.len() as LabelId + 1;
self.edge_labels.insert(id, s);
Some(id)
}
pub fn prop_key_str(&self, id: u16) -> Option<&PropKey> {
self.prop_keys.get_by_left(&id)
}
pub fn prop_key_id(&self, name: &str) -> Option<u16> {
self.prop_keys.get_by_right(name).copied()
}
pub fn register_prop_key(&mut self, name: impl Into<PropKey>) -> Option<u16> {
let s = name.into();
if let Some(&id) = self.prop_keys.get_by_right(&s) {
return Some(id);
}
if self.prop_keys.len() >= MAX_PROP_KEYS {
return None;
}
let id = self.prop_keys.len() as u16 + 1;
self.prop_keys.insert(id, s);
Some(id)
}
pub fn resolve_vertex_label(&mut self, name: &str) -> Result<LabelId, crate::types::StoreError> {
if let Some(id) = self.vertex_label_id(name) {
return Ok(id);
}
if self.mode == SchemaMode::Strict {
return Err(crate::types::StoreError::SchemaViolation(format!("Undeclared vertex label: '{}'", name)));
}
if let Some(id) = self.register_vertex_label(name) {
self.version += 1;
Ok(id)
} else {
Err(crate::types::StoreError::SchemaExhausted("Vertex label ID space exhausted".to_string()))
}
}
pub fn declare_vertex_label(&mut self, name: &str) -> Result<LabelId, crate::types::StoreError> {
if let Some(id) = self.vertex_label_id(name) {
return Ok(id);
}
if let Some(id) = self.register_vertex_label(name) {
Ok(id)
} else {
Err(crate::types::StoreError::SchemaExhausted("Vertex label ID space exhausted".to_string()))
}
}
pub fn resolve_edge_label(&mut self, name: &str) -> Result<LabelId, crate::types::StoreError> {
if let Some(id) = self.edge_label_id(name) {
return Ok(id);
}
if self.mode == SchemaMode::Strict {
return Err(crate::types::StoreError::SchemaViolation(format!("Undeclared edge label: '{}'", name)));
}
if let Some(id) = self.register_edge_label(name) {
self.version += 1;
Ok(id)
} else {
Err(crate::types::StoreError::SchemaExhausted("Edge label ID space exhausted".to_string()))
}
}
pub fn declare_edge_label(&mut self, name: &str) -> Result<LabelId, crate::types::StoreError> {
if let Some(id) = self.edge_label_id(name) {
return Ok(id);
}
if let Some(id) = self.register_edge_label(name) {
Ok(id)
} else {
Err(crate::types::StoreError::SchemaExhausted("Edge label ID space exhausted".to_string()))
}
}
pub fn resolve_prop_key(&mut self, name: &str, inferred_type: DataType) -> Result<u16, crate::types::StoreError> {
if let Some(id) = self.prop_key_id(name) {
if let Some(config) = self.prop_key_types.get(&id) {
if config.data_type != inferred_type {
return Err(crate::types::StoreError::SchemaViolation(format!(
"Property key '{}' is already defined with type {:?}, but requested {:?}",
name, config.data_type, inferred_type
)));
}
} else {
self.prop_key_types.insert(id, PropKeyConfig { data_type: inferred_type });
self.version += 1;
}
return Ok(id);
}
if self.mode == SchemaMode::Strict {
return Err(crate::types::StoreError::SchemaViolation(format!("Undeclared property key: '{}'", name)));
}
if let Some(id) = self.register_prop_key(name) {
self.prop_key_types.insert(id, PropKeyConfig { data_type: inferred_type });
self.version += 1;
Ok(id)
} else {
Err(crate::types::StoreError::SchemaExhausted("Property key ID space exhausted".to_string()))
}
}
pub fn declare_prop_key(&mut self, name: &str, data_type: DataType) -> Result<u16, crate::types::StoreError> {
if data_type == DataType::Null {
return Err(crate::types::StoreError::SchemaViolation(
"'Null' is not a valid declared property type".to_string(),
));
}
if name == "id" || name == "label" || name == "rank" {
return Err(crate::types::StoreError::SchemaViolation(format!(
"'{}' is a system-reserved key and cannot be used as an ordinary property",
name
)));
}
if let Some(id) = self.prop_key_id(name) {
if let Some(config) = self.prop_key_types.get(&id) {
if config.data_type != data_type {
return Err(crate::types::StoreError::SchemaConflict(format!(
"Property key '{}' is already defined with type {:?}, but requested {:?}",
name, config.data_type, data_type
)));
}
}
return Ok(id);
}
if let Some(id) = self.register_prop_key(name) {
self.prop_key_types.insert(id, PropKeyConfig { data_type });
Ok(id)
} else {
Err(crate::types::StoreError::SchemaExhausted("Property key ID space exhausted".to_string()))
}
}
pub fn declare_edge_mode(&mut self, mode: EdgeMode) -> Result<(), crate::types::StoreError> {
if self.edge_mode == EdgeMode::Multi && mode == EdgeMode::Single {
return Err(crate::types::StoreError::SchemaConflict(
"edge_mode: Multi -> Single is not allowed".to_string(),
));
}
self.edge_mode = mode;
Ok(())
}
pub fn declare_schema_mode(&mut self, mode: SchemaMode) -> Result<(), crate::types::StoreError> {
self.mode = mode;
Ok(())
}
pub fn decode_label_value(&self, key: &CanonicalKey, value: Primitive) -> Primitive {
let Primitive::Int32(label_id) = value else { return value };
let label_id = label_id as LabelId;
let name = match key {
CanonicalKey::Vertex(_) => self.vertex_label_str(label_id),
CanonicalKey::Edge(_) => self.edge_label_str(label_id),
CanonicalKey::Empty => None,
};
Primitive::String(name.cloned().unwrap_or_else(|| SmolStr::from(label_id.to_string())))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_mode_u8_roundtrip() {
for mode in [SchemaMode::Auto, SchemaMode::Strict] {
assert_eq!(SchemaMode::from_u8(mode.to_u8()), Some(mode));
}
assert_eq!(SchemaMode::from_u8(2), None);
}
#[test]
fn edge_mode_u8_roundtrip() {
for mode in [EdgeMode::Single, EdgeMode::Multi] {
assert_eq!(EdgeMode::from_u8(mode.to_u8()), Some(mode));
}
assert_eq!(EdgeMode::from_u8(2), None);
}
#[test]
fn data_type_u8_roundtrip() {
let all = [
DataType::Null,
DataType::Bool,
DataType::Int32,
DataType::Int64,
DataType::Float32,
DataType::Float64,
DataType::String,
DataType::Uuid,
DataType::UInt16,
DataType::Bytes,
];
for dt in all {
assert_eq!(DataType::from_u8(dt.to_u8()), Some(dt));
}
assert_eq!(DataType::from_u8(10), None);
}
}