Skip to main content

sochdb_core/
key.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// SochDB - LLM-Optimized Embedded Database
3// Copyright (C) 2026 Sushanth Reddy Vanagala (https://github.com/sushanthpy)
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! Key types for SochDB indexing
19
20use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
21use std::cmp::Ordering;
22use std::io::Result as IoResult;
23
24/// Composite key for temporal ordering
25///
26/// Primary: timestamp_us (microseconds for temporal queries)
27/// Secondary: record_id (for uniqueness within same timestamp)
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct TemporalKey {
30    pub timestamp_us: u64,
31    pub edge_id: u128,
32}
33
34impl TemporalKey {
35    pub fn new(timestamp_us: u64, edge_id: u128) -> Self {
36        Self {
37            timestamp_us,
38            edge_id,
39        }
40    }
41
42    pub fn to_bytes(&self) -> Vec<u8> {
43        let mut buf = Vec::with_capacity(24);
44        buf.write_u64::<LittleEndian>(self.timestamp_us).unwrap();
45        buf.write_u128::<LittleEndian>(self.edge_id).unwrap();
46        buf
47    }
48
49    pub fn from_bytes(bytes: &[u8]) -> IoResult<Self> {
50        let mut cursor = bytes;
51        let timestamp_us = cursor.read_u64::<LittleEndian>()?;
52        let edge_id = cursor.read_u128::<LittleEndian>()?;
53        Ok(Self {
54            timestamp_us,
55            edge_id,
56        })
57    }
58}
59
60impl PartialOrd for TemporalKey {
61    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
62        Some(self.cmp(other))
63    }
64}
65
66impl Ord for TemporalKey {
67    fn cmp(&self, other: &Self) -> Ordering {
68        self.timestamp_us
69            .cmp(&other.timestamp_us)
70            .then_with(|| self.edge_id.cmp(&other.edge_id))
71    }
72}
73
74/// Causal key for graph traversal
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub struct CausalKey {
77    pub parent_id: u128,
78    pub child_id: u128,
79}
80
81impl CausalKey {
82    pub fn new(parent_id: u128, child_id: u128) -> Self {
83        Self {
84            parent_id,
85            child_id,
86        }
87    }
88
89    pub fn to_bytes(&self) -> Vec<u8> {
90        let mut buf = Vec::with_capacity(32);
91        buf.write_u128::<LittleEndian>(self.parent_id).unwrap();
92        buf.write_u128::<LittleEndian>(self.child_id).unwrap();
93        buf
94    }
95
96    pub fn from_bytes(bytes: &[u8]) -> IoResult<Self> {
97        let mut cursor = bytes;
98        let parent_id = cursor.read_u128::<LittleEndian>()?;
99        let child_id = cursor.read_u128::<LittleEndian>()?;
100        Ok(Self {
101            parent_id,
102            child_id,
103        })
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_temporal_key_ordering() {
113        let k1 = TemporalKey::new(100, 1);
114        let k2 = TemporalKey::new(100, 2);
115        let k3 = TemporalKey::new(200, 1);
116
117        assert!(k1 < k2);
118        assert!(k2 < k3);
119        assert!(k1 < k3);
120    }
121
122    #[test]
123    fn test_temporal_key_serialization() {
124        let key = TemporalKey::new(12345, 67890);
125        let bytes = key.to_bytes();
126        let decoded = TemporalKey::from_bytes(&bytes).unwrap();
127        assert_eq!(key, decoded);
128    }
129}