1use crate::record_id::RecordId;
39use crate::soch::SochValue;
40
41const TAG_NODE: u8 = 0x01;
43const TAG_EDGE: u8 = 0x02;
44const TAG_REVERSE: u8 = 0x03;
45
46fn write_length_prefixed(buf: &mut Vec<u8>, data: &[u8]) {
49 assert!(data.len() <= u16::MAX as usize, "sub-key too long");
50 buf.extend_from_slice(&(data.len() as u16).to_be_bytes());
51 buf.extend_from_slice(data);
52}
53
54fn read_length_prefixed(data: &[u8]) -> Option<(&[u8], &[u8])> {
56 if data.len() < 2 {
57 return None;
58 }
59 let len = u16::from_be_bytes([data[0], data[1]]) as usize;
60 let rest = &data[2..];
61 if rest.len() < len {
62 return None;
63 }
64 Some((&rest[..len], &rest[len..]))
65}
66
67fn fnv1a_32(bytes: &[u8]) -> u32 {
69 let mut hash: u32 = 0x811c9dc5;
70 for &b in bytes {
71 hash ^= b as u32;
72 hash = hash.wrapping_mul(0x01000193);
73 }
74 hash
75}
76
77pub fn node_key(namespace: &str, record_id: &RecordId) -> Vec<u8> {
81 let ns_hash = fnv1a_32(namespace.as_bytes());
82 let rid_key = record_id.to_key();
83 let mut key = Vec::with_capacity(1 + 4 + rid_key.len());
84 key.push(TAG_NODE);
85 key.extend_from_slice(&ns_hash.to_be_bytes());
86 key.extend_from_slice(&rid_key);
87 key
88}
89
90pub fn node_prefix(namespace: &str) -> Vec<u8> {
94 let ns_hash = fnv1a_32(namespace.as_bytes());
95 let mut key = Vec::with_capacity(5);
96 key.push(TAG_NODE);
97 key.extend_from_slice(&ns_hash.to_be_bytes());
98 key
99}
100
101pub fn node_table_prefix(namespace: &str, table: &str) -> Vec<u8> {
105 let ns_hash = fnv1a_32(namespace.as_bytes());
106 let tbl_prefix = RecordId::table_prefix(table);
107 let mut key = Vec::with_capacity(1 + 4 + tbl_prefix.len());
108 key.push(TAG_NODE);
109 key.extend_from_slice(&ns_hash.to_be_bytes());
110 key.extend_from_slice(&tbl_prefix);
111 key
112}
113
114pub fn edge_key(
118 namespace: &str,
119 from_id: &RecordId,
120 edge_type: &str,
121 to_id: &RecordId,
122) -> Vec<u8> {
123 let ns_hash = fnv1a_32(namespace.as_bytes());
124 let et_hash = fnv1a_32(edge_type.as_bytes());
125 let from_key = from_id.to_key();
126 let to_key = to_id.to_key();
127 let mut key = Vec::with_capacity(1 + 4 + 2 + from_key.len() + 4 + 2 + to_key.len());
128 key.push(TAG_EDGE);
129 key.extend_from_slice(&ns_hash.to_be_bytes());
130 write_length_prefixed(&mut key, &from_key);
131 key.extend_from_slice(&et_hash.to_be_bytes());
132 write_length_prefixed(&mut key, &to_key);
133 key
134}
135
136pub fn edge_from_prefix(namespace: &str, from_id: &RecordId) -> Vec<u8> {
140 let ns_hash = fnv1a_32(namespace.as_bytes());
141 let from_key = from_id.to_key();
142 let mut key = Vec::with_capacity(1 + 4 + 2 + from_key.len());
143 key.push(TAG_EDGE);
144 key.extend_from_slice(&ns_hash.to_be_bytes());
145 write_length_prefixed(&mut key, &from_key);
146 key
147}
148
149pub fn edge_from_type_prefix(
153 namespace: &str,
154 from_id: &RecordId,
155 edge_type: &str,
156) -> Vec<u8> {
157 let ns_hash = fnv1a_32(namespace.as_bytes());
158 let et_hash = fnv1a_32(edge_type.as_bytes());
159 let from_key = from_id.to_key();
160 let mut key = Vec::with_capacity(1 + 4 + 2 + from_key.len() + 4);
161 key.push(TAG_EDGE);
162 key.extend_from_slice(&ns_hash.to_be_bytes());
163 write_length_prefixed(&mut key, &from_key);
164 key.extend_from_slice(&et_hash.to_be_bytes());
165 key
166}
167
168pub fn edge_prefix(namespace: &str) -> Vec<u8> {
172 let ns_hash = fnv1a_32(namespace.as_bytes());
173 let mut key = Vec::with_capacity(5);
174 key.push(TAG_EDGE);
175 key.extend_from_slice(&ns_hash.to_be_bytes());
176 key
177}
178
179pub fn reverse_key(
183 namespace: &str,
184 edge_type: &str,
185 to_id: &RecordId,
186 from_id: &RecordId,
187) -> Vec<u8> {
188 let ns_hash = fnv1a_32(namespace.as_bytes());
189 let et_hash = fnv1a_32(edge_type.as_bytes());
190 let to_key = to_id.to_key();
191 let from_key = from_id.to_key();
192 let mut key = Vec::with_capacity(1 + 4 + 4 + 2 + to_key.len() + 2 + from_key.len());
193 key.push(TAG_REVERSE);
194 key.extend_from_slice(&ns_hash.to_be_bytes());
195 key.extend_from_slice(&et_hash.to_be_bytes());
196 write_length_prefixed(&mut key, &to_key);
197 write_length_prefixed(&mut key, &from_key);
198 key
199}
200
201pub fn reverse_type_to_prefix(
205 namespace: &str,
206 edge_type: &str,
207 to_id: &RecordId,
208) -> Vec<u8> {
209 let ns_hash = fnv1a_32(namespace.as_bytes());
210 let et_hash = fnv1a_32(edge_type.as_bytes());
211 let to_key = to_id.to_key();
212 let mut key = Vec::with_capacity(1 + 4 + 4 + 2 + to_key.len());
213 key.push(TAG_REVERSE);
214 key.extend_from_slice(&ns_hash.to_be_bytes());
215 key.extend_from_slice(&et_hash.to_be_bytes());
216 write_length_prefixed(&mut key, &to_key);
217 key
218}
219
220pub fn reverse_prefix(namespace: &str) -> Vec<u8> {
224 let ns_hash = fnv1a_32(namespace.as_bytes());
225 let mut key = Vec::with_capacity(5);
226 key.push(TAG_REVERSE);
227 key.extend_from_slice(&ns_hash.to_be_bytes());
228 key
229}
230
231#[derive(Debug, Clone)]
233pub struct DecodedEdgeKey {
234 pub from_key: Vec<u8>,
235 pub edge_type_hash: u32,
236 pub to_key: Vec<u8>,
237}
238
239pub fn decode_edge_key(key: &[u8]) -> Option<DecodedEdgeKey> {
243 if key.is_empty() || key[0] != TAG_EDGE {
244 return None;
245 }
246 let rest = &key[1..]; if rest.len() < 4 {
248 return None;
249 }
250 let rest = &rest[4..]; let (from_key, rest) = read_length_prefixed(rest)?;
253 if rest.len() < 4 {
254 return None;
255 }
256 let et_hash = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
257 let rest = &rest[4..];
258 let (to_key, _rest) = read_length_prefixed(rest)?;
259
260 Some(DecodedEdgeKey {
261 from_key: from_key.to_vec(),
262 edge_type_hash: et_hash,
263 to_key: to_key.to_vec(),
264 })
265}
266
267#[derive(Debug, Clone)]
269pub struct DecodedReverseKey {
270 pub edge_type_hash: u32,
271 pub to_key: Vec<u8>,
272 pub from_key: Vec<u8>,
273}
274
275pub fn decode_reverse_key(key: &[u8]) -> Option<DecodedReverseKey> {
277 if key.is_empty() || key[0] != TAG_REVERSE {
278 return None;
279 }
280 let rest = &key[1..];
281 if rest.len() < 4 {
282 return None;
283 }
284 let rest = &rest[4..]; if rest.len() < 4 {
286 return None;
287 }
288 let et_hash = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
289 let rest = &rest[4..];
290 let (to_key, rest) = read_length_prefixed(rest)?;
291 let (from_key, _rest) = read_length_prefixed(rest)?;
292
293 Some(DecodedReverseKey {
294 edge_type_hash: et_hash,
295 to_key: to_key.to_vec(),
296 from_key: from_key.to_vec(),
297 })
298}
299
300pub fn encode_properties(props: &std::collections::HashMap<String, SochValue>) -> Vec<u8> {
308 let mut buf = Vec::new();
309 buf.extend_from_slice(&(props.len() as u32).to_be_bytes());
310 for (k, v) in props {
311 let key_bytes = k.as_bytes();
312 buf.extend_from_slice(&(key_bytes.len() as u16).to_be_bytes());
313 buf.extend_from_slice(key_bytes);
314 let val_json = serde_json::to_vec(v).unwrap_or_default();
316 buf.extend_from_slice(&(val_json.len() as u32).to_be_bytes());
317 buf.extend_from_slice(&val_json);
318 }
319 buf
320}
321
322pub fn decode_properties(data: &[u8]) -> Option<std::collections::HashMap<String, SochValue>> {
324 if data.len() < 4 {
325 return None;
326 }
327 let num = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
328 let mut offset = 4;
329 let mut map = std::collections::HashMap::with_capacity(num);
330
331 for _ in 0..num {
332 if offset + 2 > data.len() {
333 return None;
334 }
335 let key_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
336 offset += 2;
337 if offset + key_len > data.len() {
338 return None;
339 }
340 let key = std::str::from_utf8(&data[offset..offset + key_len]).ok()?.to_string();
341 offset += key_len;
342
343 if offset + 4 > data.len() {
344 return None;
345 }
346 let val_len = u32::from_be_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]]) as usize;
347 offset += 4;
348 if offset + val_len > data.len() {
349 return None;
350 }
351 let val: SochValue = serde_json::from_slice(&data[offset..offset + val_len]).ok()?;
352 offset += val_len;
353
354 map.insert(key, val);
355 }
356
357 Some(map)
358}
359
360pub fn encode_node_value(node_type: &str, props: &std::collections::HashMap<String, SochValue>) -> Vec<u8> {
364 let type_bytes = node_type.as_bytes();
365 let props_bytes = encode_properties(props);
366 let mut buf = Vec::with_capacity(2 + type_bytes.len() + props_bytes.len());
367 buf.extend_from_slice(&(type_bytes.len() as u16).to_be_bytes());
368 buf.extend_from_slice(type_bytes);
369 buf.extend_from_slice(&props_bytes);
370 buf
371}
372
373pub fn decode_node_value(data: &[u8]) -> Option<(String, std::collections::HashMap<String, SochValue>)> {
375 if data.len() < 2 {
376 return None;
377 }
378 let type_len = u16::from_be_bytes([data[0], data[1]]) as usize;
379 if data.len() < 2 + type_len {
380 return None;
381 }
382 let node_type = std::str::from_utf8(&data[2..2 + type_len]).ok()?.to_string();
383 let props = decode_properties(&data[2 + type_len..])?;
384 Some((node_type, props))
385}
386
387pub fn encode_edge_value(
394 from_id: &RecordId,
395 edge_type: &str,
396 to_id: &RecordId,
397 props: &std::collections::HashMap<String, SochValue>,
398) -> Vec<u8> {
399 let et_bytes = edge_type.as_bytes();
400 let from_str = from_id.to_string();
401 let from_bytes = from_str.as_bytes();
402 let to_str = to_id.to_string();
403 let to_bytes = to_str.as_bytes();
404 let props_bytes = encode_properties(props);
405
406 let mut buf = Vec::with_capacity(2 + et_bytes.len() + 2 + from_bytes.len() + 2 + to_bytes.len() + props_bytes.len());
407 buf.extend_from_slice(&(et_bytes.len() as u16).to_be_bytes());
408 buf.extend_from_slice(et_bytes);
409 buf.extend_from_slice(&(from_bytes.len() as u16).to_be_bytes());
410 buf.extend_from_slice(from_bytes);
411 buf.extend_from_slice(&(to_bytes.len() as u16).to_be_bytes());
412 buf.extend_from_slice(to_bytes);
413 buf.extend_from_slice(&props_bytes);
414 buf
415}
416
417#[derive(Debug, Clone)]
419pub struct DecodedEdgeValue {
420 pub edge_type: String,
421 pub from_id: RecordId,
422 pub to_id: RecordId,
423 pub properties: std::collections::HashMap<String, SochValue>,
424}
425
426pub fn decode_edge_value(data: &[u8]) -> Option<DecodedEdgeValue> {
428 let mut offset = 0;
429
430 if offset + 2 > data.len() { return None; }
432 let et_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
433 offset += 2;
434 if offset + et_len > data.len() { return None; }
435 let edge_type = std::str::from_utf8(&data[offset..offset + et_len]).ok()?.to_string();
436 offset += et_len;
437
438 if offset + 2 > data.len() { return None; }
440 let from_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
441 offset += 2;
442 if offset + from_len > data.len() { return None; }
443 let from_str = std::str::from_utf8(&data[offset..offset + from_len]).ok()?;
444 let from_id = RecordId::parse(from_str)?;
445 offset += from_len;
446
447 if offset + 2 > data.len() { return None; }
449 let to_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
450 offset += 2;
451 if offset + to_len > data.len() { return None; }
452 let to_str = std::str::from_utf8(&data[offset..offset + to_len]).ok()?;
453 let to_id = RecordId::parse(to_str)?;
454 offset += to_len;
455
456 let props = decode_properties(&data[offset..])?;
458
459 Some(DecodedEdgeValue {
460 edge_type,
461 from_id,
462 to_id,
463 properties: props,
464 })
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
474 fn test_node_key_format() {
475 let rid = RecordId::new("person", 42);
476 let key = node_key("agent_001", &rid);
477 assert_eq!(key[0], TAG_NODE);
478 assert!(key.len() > 5);
481 }
482
483 #[test]
484 fn test_node_prefix_is_prefix_of_node_key() {
485 let rid = RecordId::new("person", 42);
486 let key = node_key("agent_001", &rid);
487 let prefix = node_prefix("agent_001");
488 assert!(key.starts_with(&prefix));
489 }
490
491 #[test]
492 fn test_edge_key_roundtrip() {
493 let from = RecordId::new("user", 1);
494 let to = RecordId::new("conv", 100);
495 let key = edge_key("ns", &from, "STARTED", &to);
496 let decoded = decode_edge_key(&key).unwrap();
497 assert_eq!(decoded.from_key, from.to_key());
498 assert_eq!(decoded.to_key, to.to_key());
499 assert_eq!(decoded.edge_type_hash, fnv1a_32(b"STARTED"));
500 }
501
502 #[test]
503 fn test_edge_from_prefix_is_prefix() {
504 let from = RecordId::new("user", 1);
505 let to = RecordId::new("conv", 100);
506 let key = edge_key("ns", &from, "SENT", &to);
507 let prefix = edge_from_prefix("ns", &from);
508 assert!(key.starts_with(&prefix));
509 }
510
511 #[test]
512 fn test_edge_from_type_prefix_is_prefix() {
513 let from = RecordId::new("user", 1);
514 let to = RecordId::new("conv", 100);
515 let key = edge_key("ns", &from, "SENT", &to);
516 let prefix = edge_from_type_prefix("ns", &from, "SENT");
517 assert!(key.starts_with(&prefix));
518 }
519
520 #[test]
521 fn test_reverse_key_roundtrip() {
522 let from = RecordId::new("user", 1);
523 let to = RecordId::new("msg", 42);
524 let key = reverse_key("ns", "SENT", &to, &from);
525 let decoded = decode_reverse_key(&key).unwrap();
526 assert_eq!(decoded.from_key, from.to_key());
527 assert_eq!(decoded.to_key, to.to_key());
528 assert_eq!(decoded.edge_type_hash, fnv1a_32(b"SENT"));
529 }
530
531 #[test]
532 fn test_reverse_prefix_is_prefix() {
533 let from = RecordId::new("user", 1);
534 let to = RecordId::new("msg", 42);
535 let key = reverse_key("ns", "SENT", &to, &from);
536 let prefix = reverse_type_to_prefix("ns", "SENT", &to);
537 assert!(key.starts_with(&prefix));
538 }
539
540 #[test]
541 fn test_encode_decode_properties() {
542 let mut props = std::collections::HashMap::new();
543 props.insert("name".to_string(), SochValue::Text("Alice".to_string()));
544 props.insert("age".to_string(), SochValue::Int(30));
545 props.insert("active".to_string(), SochValue::Bool(true));
546
547 let encoded = encode_properties(&props);
548 let decoded = decode_properties(&encoded).unwrap();
549
550 assert_eq!(decoded.len(), 3);
551 assert_eq!(decoded.get("name"), Some(&SochValue::Text("Alice".to_string())));
552 assert_eq!(decoded.get("age"), Some(&SochValue::Int(30)));
553 assert_eq!(decoded.get("active"), Some(&SochValue::Bool(true)));
554 }
555
556 #[test]
557 fn test_encode_decode_node_value() {
558 let mut props = std::collections::HashMap::new();
559 props.insert("email".to_string(), SochValue::Text("a@b.com".to_string()));
560
561 let encoded = encode_node_value("User", &props);
562 let (node_type, decoded_props) = decode_node_value(&encoded).unwrap();
563 assert_eq!(node_type, "User");
564 assert_eq!(decoded_props.get("email"), Some(&SochValue::Text("a@b.com".to_string())));
565 }
566
567 #[test]
568 fn test_encode_decode_edge_value() {
569 let from = RecordId::new("user", 1);
570 let to = RecordId::from_string("conv", "abc");
571 let mut props = std::collections::HashMap::new();
572 props.insert("weight".to_string(), SochValue::Float(0.95));
573
574 let encoded = encode_edge_value(&from, "STARTED", &to, &props);
575 let decoded = decode_edge_value(&encoded).unwrap();
576 assert_eq!(decoded.edge_type, "STARTED");
577 assert_eq!(decoded.from_id, from);
578 assert_eq!(decoded.to_id, to);
579 assert_eq!(decoded.properties.get("weight"), Some(&SochValue::Float(0.95)));
580 }
581
582 #[test]
583 fn test_empty_properties() {
584 let props = std::collections::HashMap::new();
585 let encoded = encode_properties(&props);
586 let decoded = decode_properties(&encoded).unwrap();
587 assert!(decoded.is_empty());
588 }
589
590 #[test]
591 fn test_different_namespaces_produce_different_keys() {
592 let rid = RecordId::new("person", 1);
593 let key1 = node_key("ns_a", &rid);
594 let key2 = node_key("ns_b", &rid);
595 assert_ne!(key1, key2);
596 }
597}