use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::OnceLock;
use crate::types::{
gvalue::Primitive,
keys::{CanonicalEdgeKey, CanonicalKey, LabelId, Rank, VertexKey},
prop_codec, EdgeKey,
};
static EMPTY_PROPS: OnceLock<HashMap<u16, Primitive>> = OnceLock::new();
#[inline]
fn empty_props() -> &'static HashMap<u16, Primitive> {
EMPTY_PROPS.get_or_init(HashMap::new)
}
#[derive(Debug)]
pub(crate) enum PropertyMap {
LabelOnly,
Blob(Box<[u8]>),
Map(HashMap<u16, Primitive>),
}
impl PropertyMap {
#[inline]
pub(crate) fn is_label_only(&self) -> bool {
matches!(self, PropertyMap::LabelOnly)
}
#[inline]
pub(crate) fn get_value(&self, key: u16) -> Option<Primitive> {
match self {
PropertyMap::LabelOnly => None,
PropertyMap::Blob(bytes) => prop_codec::decode_prop_by_key(bytes, key),
PropertyMap::Map(map) => map.get(&key).cloned(),
}
}
#[inline]
pub(crate) fn ensure_map(&mut self) {
if let PropertyMap::Blob(bytes) = self {
let map = prop_codec::decode_all_to_map(bytes);
*self = PropertyMap::Map(map);
}
}
#[inline]
pub(crate) fn as_map(&self) -> Option<&HashMap<u16, Primitive>> {
match self {
PropertyMap::Map(m) => Some(m),
_ => None,
}
}
#[inline]
pub(crate) fn as_map_mut(&mut self) -> Option<&mut HashMap<u16, Primitive>> {
match self {
PropertyMap::Map(m) => Some(m),
_ => None,
}
}
}
#[derive(Debug)]
pub struct Vertex {
pub id: VertexKey,
pub label_id: LabelId,
pub(crate) props: PropertyMap,
}
impl Vertex {
#[inline]
pub fn new(id: VertexKey, label_id: LabelId) -> Self {
Vertex { id, label_id, props: PropertyMap::Map(HashMap::new()) }
}
#[inline]
pub(crate) fn with_props(id: VertexKey, label_id: LabelId, props: HashMap<u16, Primitive>) -> Self {
Vertex { id, label_id, props: PropertyMap::Map(props) }
}
#[inline]
pub(crate) fn from_raw(id: VertexKey, label_id: LabelId, bytes: Box<[u8]>) -> Self {
Vertex { id, label_id, props: PropertyMap::Blob(bytes) }
}
#[inline]
pub fn label_only(id: VertexKey, label_id: LabelId) -> Self {
Vertex { id, label_id, props: PropertyMap::LabelOnly }
}
#[inline]
pub fn is_label_only(&self) -> bool {
self.props.is_label_only()
}
#[inline]
pub fn get_value(&self, prop_key_id: u16) -> Option<Primitive> {
use crate::types::prop_key::{ID_KEY_ID, LABEL_KEY_ID};
if prop_key_id == ID_KEY_ID {
return Some(Primitive::Int64(self.id));
}
if prop_key_id == LABEL_KEY_ID {
return Some(Primitive::Int32(self.label_id));
}
self.props.get_value(prop_key_id)
}
#[inline]
pub fn get_property(&self, prop_key_id: u16) -> Option<Property> {
let value = self.get_value(prop_key_id)?;
Some(Property { owner: CanonicalKey::Vertex(self.id), key: prop_key_id, value })
}
#[inline]
pub(crate) fn props(&mut self) -> &HashMap<u16, Primitive> {
self.props.ensure_map();
self.props.as_map().unwrap_or_else(|| empty_props())
}
#[inline]
pub(crate) fn props_mut(&mut self) -> &mut HashMap<u16, Primitive> {
self.props.ensure_map();
self.props.as_map_mut().expect("props_mut called on LabelOnly vertex")
}
}
#[derive(Debug)]
pub struct Edge {
pub src_id: VertexKey,
pub label_id: LabelId,
pub dst_id: VertexKey,
pub rank: Rank,
pub src_label: Option<LabelId>,
pub dst_label: Option<LabelId>,
pub(crate) props: PropertyMap,
}
impl Edge {
#[inline]
pub fn new(
src_id: VertexKey,
label_id: LabelId,
dst_id: VertexKey,
rank: Rank,
src_label: Option<LabelId>,
dst_label: Option<LabelId>,
) -> Self {
Edge { src_id, label_id, dst_id, rank, src_label, dst_label, props: PropertyMap::Map(HashMap::new()) }
}
#[inline]
pub(crate) fn with_props(
src_id: VertexKey,
label_id: LabelId,
dst_id: VertexKey,
rank: Rank,
props: HashMap<u16, Primitive>,
src_label: Option<LabelId>,
dst_label: Option<LabelId>,
) -> Self {
Edge { src_id, label_id, dst_id, rank, src_label, dst_label, props: PropertyMap::Map(props) }
}
#[inline]
pub(crate) fn from_raw(
src_id: VertexKey,
label_id: LabelId,
dst_id: VertexKey,
rank: Rank,
bytes: Box<[u8]>,
src_label: Option<LabelId>,
dst_label: Option<LabelId>,
) -> Self {
Edge { src_id, label_id, dst_id, rank, src_label, dst_label, props: PropertyMap::Blob(bytes) }
}
#[inline]
pub fn get_value(&self, prop_key_id: u16) -> Option<Primitive> {
use crate::types::prop_key::{LABEL_KEY_ID, RANK_KEY_ID};
if prop_key_id == LABEL_KEY_ID {
return Some(Primitive::Int32(self.label_id));
}
if prop_key_id == RANK_KEY_ID {
return Some(Primitive::UInt16(self.rank));
}
self.props.get_value(prop_key_id)
}
#[inline]
pub fn get_property(&self, prop_key_id: u16) -> Option<Property> {
let value = self.get_value(prop_key_id)?;
Some(Property { owner: CanonicalKey::Edge(self.canonical_key()), key: prop_key_id, value })
}
#[inline]
pub(crate) fn props(&mut self) -> &HashMap<u16, Primitive> {
self.props.ensure_map();
self.props.as_map().unwrap_or_else(|| empty_props())
}
#[inline]
pub(crate) fn props_mut(&mut self) -> &mut HashMap<u16, Primitive> {
self.props.ensure_map();
self.props.as_map_mut().expect("props_mut called on LabelOnly edge")
}
#[inline]
pub fn canonical_key(&self) -> CanonicalEdgeKey {
CanonicalEdgeKey { src_id: self.src_id, label_id: self.label_id, rank: self.rank, dst_id: self.dst_id }
}
#[inline]
pub fn edge_key_out(&self) -> EdgeKey {
EdgeKey {
primary_id: self.src_id,
direction: super::Direction::OUT,
label_id: self.label_id,
secondary_id: self.dst_id,
rank: self.rank,
}
}
#[inline]
pub fn edge_key_in(&self) -> EdgeKey {
EdgeKey {
primary_id: self.dst_id,
direction: super::Direction::IN,
label_id: self.label_id,
secondary_id: self.src_id,
rank: self.rank,
}
}
}
impl PartialEq for Vertex {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.label_id == other.label_id
}
}
impl Eq for Vertex {}
impl PartialEq for Edge {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.src_id == other.src_id
&& self.label_id == other.label_id
&& self.rank == other.rank
&& self.dst_id == other.dst_id
}
}
impl Eq for Edge {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn label_only_props_is_empty() {
let mut v = Vertex::label_only(1, 7);
assert!(v.is_label_only());
assert!(v.props().is_empty(), "LabelOnly props should return empty map");
assert!(v.is_label_only(), "props() must not transition LabelOnly to Map");
assert_eq!(v.get_value(crate::types::prop_key::ID_KEY_ID), Some(Primitive::Int64(1)));
assert_eq!(v.get_value(crate::types::prop_key::LABEL_KEY_ID), Some(Primitive::Int32(7)));
assert_eq!(v.get_value(100), None);
}
#[test]
fn label_only_stays_label_only_after_get_value() {
let v = Vertex::label_only(42, 3);
let _ = v.get_value(100);
assert!(v.is_label_only());
}
#[test]
fn g9_props_idempotent_on_map_state() {
let m: HashMap<u16, Primitive> = [(10u16, Primitive::Int32(42))].into();
let mut v = Vertex::with_props(1, 1, m);
assert_eq!(v.props().get(&10), Some(&Primitive::Int32(42)));
assert_eq!(v.props().get(&10), Some(&Primitive::Int32(42))); assert!(matches!(v.props, PropertyMap::Map(_)));
}
#[test]
fn g11_props_mut_on_blob_transitions_to_map() {
let blob = crate::types::prop_codec::encode_props(&[(5u16, Primitive::Bool(true))].into());
let mut v = Vertex::from_raw(1, 1, blob.into_boxed_slice());
assert!(matches!(v.props, PropertyMap::Blob(_)));
v.props_mut().insert(6, Primitive::Int32(99));
assert!(matches!(v.props, PropertyMap::Map(_)));
assert_eq!(v.props_mut().get(&5), Some(&Primitive::Bool(true))); assert_eq!(v.props_mut().get(&6), Some(&Primitive::Int32(99))); }
#[test]
#[should_panic(expected = "props_mut called on LabelOnly vertex")]
fn g12_props_mut_on_label_only_panics() {
let mut v = Vertex::label_only(1, 1);
let _ = v.props_mut(); }
#[test]
fn g14_props_on_blob_transitions_to_map() {
let blob = crate::types::prop_codec::encode_props(&[(10u16, Primitive::Int64(7))].into());
let mut v = Vertex::from_raw(1, 1, blob.into_boxed_slice());
assert!(matches!(v.props, PropertyMap::Blob(_)));
let map = v.props();
assert_eq!(map.get(&10), Some(&Primitive::Int64(7)));
assert!(matches!(v.props, PropertyMap::Map(_)));
assert!(v.props.as_map().is_some());
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Property {
pub owner: CanonicalKey,
pub key: u16,
pub value: Primitive,
}
impl Hash for Property {
fn hash<H: Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.key.hash(state);
self.value.hash(state);
}
}