use std::fmt::Display;
use std::str::FromStr;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use smol_str::SmolStr;
use crate::types::error::StoreError;
pub type VertexKey = i64;
pub type LabelId = i32;
pub type Rank = u16;
pub const DEFAULT_RANK: Rank = 0;
const EDGE_ID_ENCODED_LEN: usize = 8 + 4 + 8 + 2;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Direction {
OUT,
IN,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DegreeDirection {
Out,
In,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AdjacentEdgeCursor {
pub label_id: LabelId,
pub secondary_id: VertexKey,
pub rank: Rank,
}
impl AdjacentEdgeCursor {
#[inline]
pub fn from_edge(edge: &super::element::Edge, direction: Direction) -> Self {
AdjacentEdgeCursor {
label_id: edge.label_id,
secondary_id: match direction {
Direction::OUT => edge.dst_id,
Direction::IN => edge.src_id,
},
rank: edge.rank,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdjacentEdgesOptions<'a> {
pub label: Option<LabelId>,
pub dst: Option<&'a [VertexKey]>,
pub rank: Option<&'a [Rank]>,
pub start_from: Option<AdjacentEdgeCursor>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BatchScenario {
ScanVertices,
ScanEdges,
GetAdjacentEdges,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CanonicalEdgeKey {
pub src_id: VertexKey,
pub label_id: LabelId,
pub dst_id: VertexKey,
pub rank: Rank,
}
impl CanonicalEdgeKey {
#[inline]
pub fn out_key(&self) -> EdgeKey {
EdgeKey {
primary_id: self.src_id,
direction: Direction::OUT,
label_id: self.label_id,
secondary_id: self.dst_id,
rank: self.rank,
}
}
#[inline]
pub fn in_key(&self) -> EdgeKey {
EdgeKey {
primary_id: self.dst_id,
direction: Direction::IN,
label_id: self.label_id,
secondary_id: self.src_id,
rank: self.rank,
}
}
}
impl Display for CanonicalEdgeKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({} -{}-> {})[rank={}]", self.src_id, self.label_id, self.dst_id, self.rank)
}
}
impl CanonicalEdgeKey {
pub fn to_id_string(self) -> String {
let mut buf = [0u8; EDGE_ID_ENCODED_LEN];
buf[0..8].copy_from_slice(&self.src_id.to_be_bytes());
buf[8..12].copy_from_slice(&self.label_id.to_be_bytes());
buf[12..20].copy_from_slice(&self.dst_id.to_be_bytes());
buf[20..22].copy_from_slice(&self.rank.to_be_bytes());
URL_SAFE_NO_PAD.encode(buf)
}
}
impl FromStr for CanonicalEdgeKey {
type Err = StoreError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = URL_SAFE_NO_PAD
.decode(s)
.map_err(|_| StoreError::UnexpectedDataType(format!("invalid edge id '{}': not valid base64", s)))?;
if bytes.len() != EDGE_ID_ENCODED_LEN {
return Err(StoreError::UnexpectedDataType(format!(
"invalid edge id '{}': expected {EDGE_ID_ENCODED_LEN} decoded bytes, got {}",
s,
bytes.len()
)));
}
Ok(CanonicalEdgeKey {
src_id: i64::from_be_bytes(bytes[0..8].try_into().unwrap()),
label_id: i32::from_be_bytes(bytes[8..12].try_into().unwrap()),
dst_id: i64::from_be_bytes(bytes[12..20].try_into().unwrap()),
rank: u16::from_be_bytes(bytes[20..22].try_into().unwrap()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EdgeKey {
pub primary_id: VertexKey,
pub direction: Direction,
pub label_id: LabelId,
pub secondary_id: VertexKey,
pub rank: Rank,
}
impl EdgeKey {
#[inline]
pub fn out_e(src: VertexKey, label: LabelId, dst: VertexKey, rank: Rank) -> Self {
Self { primary_id: src, direction: Direction::OUT, label_id: label, secondary_id: dst, rank }
}
#[inline]
pub fn in_e(src: VertexKey, label: LabelId, dst: VertexKey, rank: Rank) -> Self {
Self { primary_id: dst, direction: Direction::IN, label_id: label, secondary_id: src, rank }
}
#[inline]
pub fn flip(&self) -> Self {
Self {
primary_id: self.secondary_id,
direction: match self.direction {
Direction::OUT => Direction::IN,
Direction::IN => Direction::OUT,
},
label_id: self.label_id,
secondary_id: self.primary_id,
rank: self.rank,
}
}
#[inline]
pub fn canonical(self) -> Self {
match self.direction {
Direction::OUT => self,
Direction::IN => self.flip(),
}
}
#[inline]
pub fn canonical_edge_key(self) -> CanonicalEdgeKey {
match self.direction {
Direction::OUT => CanonicalEdgeKey {
src_id: self.primary_id,
label_id: self.label_id,
dst_id: self.secondary_id,
rank: self.rank,
},
Direction::IN => CanonicalEdgeKey {
src_id: self.secondary_id,
label_id: self.label_id,
dst_id: self.primary_id,
rank: self.rank,
},
}
}
#[inline]
pub fn to_id_string(self) -> SmolStr {
let (src, dst) = match self.direction {
Direction::OUT => (self.primary_id, self.secondary_id),
Direction::IN => (self.secondary_id, self.primary_id),
};
let mut buf = [0u8; EDGE_ID_ENCODED_LEN];
buf[0..8].copy_from_slice(&src.to_be_bytes());
buf[8..12].copy_from_slice(&self.label_id.to_be_bytes());
buf[12..20].copy_from_slice(&dst.to_be_bytes());
buf[20..22].copy_from_slice(&self.rank.to_be_bytes());
URL_SAFE_NO_PAD.encode(buf).into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CanonicalKey {
Empty,
Vertex(VertexKey),
Edge(CanonicalEdgeKey),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edge_id_round_trip() {
let cek = CanonicalEdgeKey { src_id: 1, label_id: 3, dst_id: 2, rank: 0 };
let s = cek.to_id_string();
assert_eq!(s.len(), 30, "Base64 edge id must be 30 chars");
let parsed: CanonicalEdgeKey = s.parse().unwrap();
assert_eq!(parsed, cek);
let big = CanonicalEdgeKey { src_id: i64::MAX, label_id: i32::MAX, dst_id: i64::MIN, rank: u16::MAX };
let s2 = big.to_id_string();
assert_eq!(s2.len(), 30);
assert_eq!(s2.parse::<CanonicalEdgeKey>().unwrap(), big);
}
#[test]
fn test_edge_id_from_str_rejects_bad_input() {
assert!("".parse::<CanonicalEdgeKey>().is_err());
assert!("too-short".parse::<CanonicalEdgeKey>().is_err());
assert!("not-valid-base64!!!".parse::<CanonicalEdgeKey>().is_err());
}
}