use crate::dictionary::NodeId;
use oxicode::Decode;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct QuotedTriple {
pub subject: NodeId,
pub predicate: NodeId,
pub object: NodeId,
}
impl QuotedTriple {
pub fn new(subject: NodeId, predicate: NodeId, object: NodeId) -> Self {
Self {
subject,
predicate,
object,
}
}
pub fn is_nested(&self, quoted_triple_table: &QuotedTripleTable) -> bool {
quoted_triple_table.is_quoted_triple(self.subject)
|| quoted_triple_table.is_quoted_triple(self.object)
}
pub fn max_nesting_depth(&self, quoted_triple_table: &QuotedTripleTable) -> usize {
let subject_depth = if quoted_triple_table.is_quoted_triple(self.subject) {
if let Some(qt) = quoted_triple_table.get(self.subject) {
1 + qt.max_nesting_depth(quoted_triple_table)
} else {
0
}
} else {
0
};
let object_depth = if quoted_triple_table.is_quoted_triple(self.object) {
if let Some(qt) = quoted_triple_table.get(self.object) {
1 + qt.max_nesting_depth(quoted_triple_table)
} else {
0
}
} else {
0
};
subject_depth.max(object_depth)
}
}
pub const QUOTED_TRIPLE_MARKER: u8 = 0x90;
pub const MAX_NESTING_DEPTH: usize = 100;
#[derive(Debug, Clone)]
pub struct QuotedTripleTable {
triples: std::collections::HashMap<NodeId, QuotedTriple>,
reverse: std::collections::HashMap<QuotedTriple, NodeId>,
next_id: u64,
}
impl QuotedTripleTable {
pub fn new() -> Self {
Self {
triples: std::collections::HashMap::new(),
reverse: std::collections::HashMap::new(),
next_id: 0,
}
}
fn create_node_id(&self, id: u64) -> NodeId {
let encoded = ((QUOTED_TRIPLE_MARKER as u64) << 56) | (id & 0x00FFFFFFFFFFFFFF);
NodeId::from(encoded)
}
fn extract_id(node_id: NodeId) -> u64 {
node_id.as_u64() & 0x00FFFFFFFFFFFFFF
}
pub fn is_quoted_triple(&self, node_id: NodeId) -> bool {
let type_byte = ((node_id.as_u64() >> 56) & 0xFF) as u8;
type_byte == QUOTED_TRIPLE_MARKER
}
pub fn get_or_create(&mut self, triple: QuotedTriple) -> Result<NodeId, RdfStarError> {
let depth = triple.max_nesting_depth(self);
if depth > MAX_NESTING_DEPTH {
return Err(RdfStarError::ExcessiveNesting(depth));
}
if let Some(&node_id) = self.reverse.get(&triple) {
return Ok(node_id);
}
let id = self.next_id;
self.next_id += 1;
let node_id = self.create_node_id(id);
self.triples.insert(node_id, triple);
self.reverse.insert(triple, node_id);
Ok(node_id)
}
pub fn get(&self, node_id: NodeId) -> Option<QuotedTriple> {
self.triples.get(&node_id).copied()
}
pub fn len(&self) -> usize {
self.triples.len()
}
pub fn is_empty(&self) -> bool {
self.triples.is_empty()
}
pub fn clear(&mut self) {
self.triples.clear();
self.reverse.clear();
self.next_id = 0;
}
pub fn all_triples(&self) -> Vec<(NodeId, QuotedTriple)> {
self.triples
.iter()
.map(|(&id, &triple)| (id, triple))
.collect()
}
}
impl Default for QuotedTripleTable {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RdfStarError {
ExcessiveNesting(usize),
InvalidQuotedTripleId(NodeId),
NotFound(NodeId),
}
impl std::fmt::Display for RdfStarError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RdfStarError::ExcessiveNesting(depth) => {
write!(
f,
"Excessive nesting depth {} exceeds maximum {}",
depth, MAX_NESTING_DEPTH
)
}
RdfStarError::InvalidQuotedTripleId(id) => {
write!(f, "Invalid quoted triple NodeId: {:?}", id)
}
RdfStarError::NotFound(id) => write!(f, "Quoted triple not found: {:?}", id),
}
}
}
impl std::error::Error for RdfStarError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RdfStarStats {
pub total_quoted_triples: usize,
pub nested_count: usize,
pub max_depth: usize,
pub avg_depth: f64,
}
impl QuotedTripleTable {
pub fn statistics(&self) -> RdfStarStats {
let total = self.triples.len();
if total == 0 {
return RdfStarStats {
total_quoted_triples: 0,
nested_count: 0,
max_depth: 0,
avg_depth: 0.0,
};
}
let mut max_depth = 0;
let mut total_depth = 0;
let mut nested_count = 0;
for triple in self.triples.values() {
let depth = triple.max_nesting_depth(self);
if depth > 0 {
nested_count += 1;
}
max_depth = max_depth.max(depth);
total_depth += depth;
}
RdfStarStats {
total_quoted_triples: total,
nested_count,
max_depth,
avg_depth: total_depth as f64 / total as f64,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quoted_triple_creation() {
let triple = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
assert_eq!(triple.subject, NodeId::from(1));
assert_eq!(triple.predicate, NodeId::from(2));
assert_eq!(triple.object, NodeId::from(3));
}
#[test]
fn test_quoted_triple_table_basic() {
let mut table = QuotedTripleTable::new();
let triple = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let node_id = table.get_or_create(triple).unwrap();
assert!(table.is_quoted_triple(node_id));
assert_eq!(table.get(node_id), Some(triple));
}
#[test]
fn test_quoted_triple_deduplication() {
let mut table = QuotedTripleTable::new();
let triple = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let id1 = table.get_or_create(triple).unwrap();
let id2 = table.get_or_create(triple).unwrap();
assert_eq!(id1, id2);
assert_eq!(table.len(), 1);
}
#[test]
fn test_quoted_triple_nested() {
let mut table = QuotedTripleTable::new();
let triple1 = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let qt_id1 = table.get_or_create(triple1).unwrap();
let triple2 = QuotedTriple::new(
qt_id1, NodeId::from(4),
NodeId::from(5),
);
let qt_id2 = table.get_or_create(triple2).unwrap();
assert!(table.is_quoted_triple(qt_id1));
assert!(table.is_quoted_triple(qt_id2));
assert!(triple2.is_nested(&table));
assert_eq!(triple2.max_nesting_depth(&table), 1);
}
#[test]
fn test_quoted_triple_max_nesting_depth() {
let mut table = QuotedTripleTable::new();
let mut current = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let mut current_id = table.get_or_create(current).unwrap();
for i in 4..9 {
current = QuotedTriple::new(current_id, NodeId::from(i), NodeId::from(i + 1));
current_id = table.get_or_create(current).unwrap();
}
let final_triple = table.get(current_id).unwrap();
assert_eq!(final_triple.max_nesting_depth(&table), 5);
}
#[test]
fn test_excessive_nesting_prevention() {
let mut table = QuotedTripleTable::new();
let mut current = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let mut current_id = table.get_or_create(current).unwrap();
for i in 0..MAX_NESTING_DEPTH + 5 {
current = QuotedTriple::new(
current_id,
NodeId::from(i as u64 + 100),
NodeId::from(i as u64 + 200),
);
match table.get_or_create(current) {
Ok(id) => current_id = id,
Err(RdfStarError::ExcessiveNesting(depth)) => {
assert!(depth > MAX_NESTING_DEPTH);
return; }
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
panic!("Should have prevented excessive nesting");
}
#[test]
fn test_quoted_triple_table_clear() {
let mut table = QuotedTripleTable::new();
for i in 0..10 {
let triple = QuotedTriple::new(
NodeId::from(i * 3),
NodeId::from(i * 3 + 1),
NodeId::from(i * 3 + 2),
);
table.get_or_create(triple).unwrap();
}
assert_eq!(table.len(), 10);
table.clear();
assert_eq!(table.len(), 0);
assert!(table.is_empty());
}
#[test]
fn test_rdf_star_statistics() {
let mut table = QuotedTripleTable::new();
for i in 0..5 {
let triple = QuotedTriple::new(
NodeId::from(i * 3),
NodeId::from(i * 3 + 1),
NodeId::from(i * 3 + 2),
);
table.get_or_create(triple).unwrap();
}
let triple1 = QuotedTriple::new(NodeId::from(100), NodeId::from(101), NodeId::from(102));
let qt_id1 = table.get_or_create(triple1).unwrap();
let triple2 = QuotedTriple::new(qt_id1, NodeId::from(200), NodeId::from(201));
table.get_or_create(triple2).unwrap();
let stats = table.statistics();
assert_eq!(stats.total_quoted_triples, 7);
assert_eq!(stats.nested_count, 1);
assert_eq!(stats.max_depth, 1);
}
#[test]
fn test_quoted_triple_marker() {
let mut table = QuotedTripleTable::new();
let triple = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let qt_id = table.get_or_create(triple).unwrap();
let high_byte = ((qt_id.as_u64() >> 56) & 0xFF) as u8;
assert_eq!(high_byte, QUOTED_TRIPLE_MARKER);
assert!(!table.is_quoted_triple(NodeId::from(123)));
}
#[test]
fn test_quoted_triple_serialization() {
let triple = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let serialized =
oxicode::serde::encode_to_vec(&triple, oxicode::config::standard()).unwrap();
let deserialized: QuotedTriple =
oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
.unwrap()
.0;
assert_eq!(triple, deserialized);
}
#[test]
fn test_quoted_triple_ordering() {
let triple1 = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(3));
let triple2 = QuotedTriple::new(NodeId::from(1), NodeId::from(2), NodeId::from(4));
assert!(triple1 < triple2);
}
}