sochdb_core/
key.rs

1// Copyright 2025 Sushanth (https://github.com/sushanthpy)
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Key types for SochDB indexing
16
17use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
18use std::cmp::Ordering;
19use std::io::Result as IoResult;
20
21/// Composite key for temporal ordering
22///
23/// Primary: timestamp_us (microseconds for temporal queries)
24/// Secondary: record_id (for uniqueness within same timestamp)
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct TemporalKey {
27    pub timestamp_us: u64,
28    pub edge_id: u128,
29}
30
31impl TemporalKey {
32    pub fn new(timestamp_us: u64, edge_id: u128) -> Self {
33        Self {
34            timestamp_us,
35            edge_id,
36        }
37    }
38
39    pub fn to_bytes(&self) -> Vec<u8> {
40        let mut buf = Vec::with_capacity(24);
41        buf.write_u64::<LittleEndian>(self.timestamp_us).unwrap();
42        buf.write_u128::<LittleEndian>(self.edge_id).unwrap();
43        buf
44    }
45
46    pub fn from_bytes(bytes: &[u8]) -> IoResult<Self> {
47        let mut cursor = bytes;
48        let timestamp_us = cursor.read_u64::<LittleEndian>()?;
49        let edge_id = cursor.read_u128::<LittleEndian>()?;
50        Ok(Self {
51            timestamp_us,
52            edge_id,
53        })
54    }
55}
56
57impl PartialOrd for TemporalKey {
58    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
59        Some(self.cmp(other))
60    }
61}
62
63impl Ord for TemporalKey {
64    fn cmp(&self, other: &Self) -> Ordering {
65        self.timestamp_us
66            .cmp(&other.timestamp_us)
67            .then_with(|| self.edge_id.cmp(&other.edge_id))
68    }
69}
70
71/// Causal key for graph traversal
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub struct CausalKey {
74    pub parent_id: u128,
75    pub child_id: u128,
76}
77
78impl CausalKey {
79    pub fn new(parent_id: u128, child_id: u128) -> Self {
80        Self {
81            parent_id,
82            child_id,
83        }
84    }
85
86    pub fn to_bytes(&self) -> Vec<u8> {
87        let mut buf = Vec::with_capacity(32);
88        buf.write_u128::<LittleEndian>(self.parent_id).unwrap();
89        buf.write_u128::<LittleEndian>(self.child_id).unwrap();
90        buf
91    }
92
93    pub fn from_bytes(bytes: &[u8]) -> IoResult<Self> {
94        let mut cursor = bytes;
95        let parent_id = cursor.read_u128::<LittleEndian>()?;
96        let child_id = cursor.read_u128::<LittleEndian>()?;
97        Ok(Self {
98            parent_id,
99            child_id,
100        })
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_temporal_key_ordering() {
110        let k1 = TemporalKey::new(100, 1);
111        let k2 = TemporalKey::new(100, 2);
112        let k3 = TemporalKey::new(200, 1);
113
114        assert!(k1 < k2);
115        assert!(k2 < k3);
116        assert!(k1 < k3);
117    }
118
119    #[test]
120    fn test_temporal_key_serialization() {
121        let key = TemporalKey::new(12345, 67890);
122        let bytes = key.to_bytes();
123        let decoded = TemporalKey::from_bytes(&bytes).unwrap();
124        assert_eq!(key, decoded);
125    }
126}