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(namespace: &str, from_id: &RecordId, edge_type: &str, to_id: &RecordId) -> Vec<u8> {
118 let ns_hash = fnv1a_32(namespace.as_bytes());
119 let et_hash = fnv1a_32(edge_type.as_bytes());
120 let from_key = from_id.to_key();
121 let to_key = to_id.to_key();
122 let mut key = Vec::with_capacity(1 + 4 + 2 + from_key.len() + 4 + 2 + to_key.len());
123 key.push(TAG_EDGE);
124 key.extend_from_slice(&ns_hash.to_be_bytes());
125 write_length_prefixed(&mut key, &from_key);
126 key.extend_from_slice(&et_hash.to_be_bytes());
127 write_length_prefixed(&mut key, &to_key);
128 key
129}
130
131pub fn edge_from_prefix(namespace: &str, from_id: &RecordId) -> Vec<u8> {
135 let ns_hash = fnv1a_32(namespace.as_bytes());
136 let from_key = from_id.to_key();
137 let mut key = Vec::with_capacity(1 + 4 + 2 + from_key.len());
138 key.push(TAG_EDGE);
139 key.extend_from_slice(&ns_hash.to_be_bytes());
140 write_length_prefixed(&mut key, &from_key);
141 key
142}
143
144pub fn edge_from_type_prefix(namespace: &str, from_id: &RecordId, edge_type: &str) -> Vec<u8> {
148 let ns_hash = fnv1a_32(namespace.as_bytes());
149 let et_hash = fnv1a_32(edge_type.as_bytes());
150 let from_key = from_id.to_key();
151 let mut key = Vec::with_capacity(1 + 4 + 2 + from_key.len() + 4);
152 key.push(TAG_EDGE);
153 key.extend_from_slice(&ns_hash.to_be_bytes());
154 write_length_prefixed(&mut key, &from_key);
155 key.extend_from_slice(&et_hash.to_be_bytes());
156 key
157}
158
159pub fn edge_prefix(namespace: &str) -> Vec<u8> {
163 let ns_hash = fnv1a_32(namespace.as_bytes());
164 let mut key = Vec::with_capacity(5);
165 key.push(TAG_EDGE);
166 key.extend_from_slice(&ns_hash.to_be_bytes());
167 key
168}
169
170pub fn reverse_key(
174 namespace: &str,
175 edge_type: &str,
176 to_id: &RecordId,
177 from_id: &RecordId,
178) -> Vec<u8> {
179 let ns_hash = fnv1a_32(namespace.as_bytes());
180 let et_hash = fnv1a_32(edge_type.as_bytes());
181 let to_key = to_id.to_key();
182 let from_key = from_id.to_key();
183 let mut key = Vec::with_capacity(1 + 4 + 4 + 2 + to_key.len() + 2 + from_key.len());
184 key.push(TAG_REVERSE);
185 key.extend_from_slice(&ns_hash.to_be_bytes());
186 key.extend_from_slice(&et_hash.to_be_bytes());
187 write_length_prefixed(&mut key, &to_key);
188 write_length_prefixed(&mut key, &from_key);
189 key
190}
191
192pub fn reverse_type_to_prefix(namespace: &str, edge_type: &str, to_id: &RecordId) -> Vec<u8> {
196 let ns_hash = fnv1a_32(namespace.as_bytes());
197 let et_hash = fnv1a_32(edge_type.as_bytes());
198 let to_key = to_id.to_key();
199 let mut key = Vec::with_capacity(1 + 4 + 4 + 2 + to_key.len());
200 key.push(TAG_REVERSE);
201 key.extend_from_slice(&ns_hash.to_be_bytes());
202 key.extend_from_slice(&et_hash.to_be_bytes());
203 write_length_prefixed(&mut key, &to_key);
204 key
205}
206
207pub fn reverse_prefix(namespace: &str) -> Vec<u8> {
211 let ns_hash = fnv1a_32(namespace.as_bytes());
212 let mut key = Vec::with_capacity(5);
213 key.push(TAG_REVERSE);
214 key.extend_from_slice(&ns_hash.to_be_bytes());
215 key
216}
217
218#[derive(Debug, Clone)]
220pub struct DecodedEdgeKey {
221 pub from_key: Vec<u8>,
222 pub edge_type_hash: u32,
223 pub to_key: Vec<u8>,
224}
225
226pub fn decode_edge_key(key: &[u8]) -> Option<DecodedEdgeKey> {
230 if key.is_empty() || key[0] != TAG_EDGE {
231 return None;
232 }
233 let rest = &key[1..]; if rest.len() < 4 {
235 return None;
236 }
237 let rest = &rest[4..]; let (from_key, rest) = read_length_prefixed(rest)?;
240 if rest.len() < 4 {
241 return None;
242 }
243 let et_hash = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
244 let rest = &rest[4..];
245 let (to_key, _rest) = read_length_prefixed(rest)?;
246
247 Some(DecodedEdgeKey {
248 from_key: from_key.to_vec(),
249 edge_type_hash: et_hash,
250 to_key: to_key.to_vec(),
251 })
252}
253
254#[derive(Debug, Clone)]
256pub struct DecodedReverseKey {
257 pub edge_type_hash: u32,
258 pub to_key: Vec<u8>,
259 pub from_key: Vec<u8>,
260}
261
262pub fn decode_reverse_key(key: &[u8]) -> Option<DecodedReverseKey> {
264 if key.is_empty() || key[0] != TAG_REVERSE {
265 return None;
266 }
267 let rest = &key[1..];
268 if rest.len() < 4 {
269 return None;
270 }
271 let rest = &rest[4..]; if rest.len() < 4 {
273 return None;
274 }
275 let et_hash = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
276 let rest = &rest[4..];
277 let (to_key, rest) = read_length_prefixed(rest)?;
278 let (from_key, _rest) = read_length_prefixed(rest)?;
279
280 Some(DecodedReverseKey {
281 edge_type_hash: et_hash,
282 to_key: to_key.to_vec(),
283 from_key: from_key.to_vec(),
284 })
285}
286
287pub fn encode_properties(props: &std::collections::HashMap<String, SochValue>) -> Vec<u8> {
295 let mut buf = Vec::new();
296 buf.extend_from_slice(&(props.len() as u32).to_be_bytes());
297 for (k, v) in props {
298 let key_bytes = k.as_bytes();
299 buf.extend_from_slice(&(key_bytes.len() as u16).to_be_bytes());
300 buf.extend_from_slice(key_bytes);
301 let val_json = serde_json::to_vec(v).unwrap_or_default();
303 buf.extend_from_slice(&(val_json.len() as u32).to_be_bytes());
304 buf.extend_from_slice(&val_json);
305 }
306 buf
307}
308
309pub fn decode_properties(data: &[u8]) -> Option<std::collections::HashMap<String, SochValue>> {
311 if data.len() < 4 {
312 return None;
313 }
314 let num = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
315 let mut offset = 4;
316 let mut map = std::collections::HashMap::with_capacity(num);
317
318 for _ in 0..num {
319 if offset + 2 > data.len() {
320 return None;
321 }
322 let key_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
323 offset += 2;
324 if offset + key_len > data.len() {
325 return None;
326 }
327 let key = std::str::from_utf8(&data[offset..offset + key_len])
328 .ok()?
329 .to_string();
330 offset += key_len;
331
332 if offset + 4 > data.len() {
333 return None;
334 }
335 let val_len = u32::from_be_bytes([
336 data[offset],
337 data[offset + 1],
338 data[offset + 2],
339 data[offset + 3],
340 ]) as usize;
341 offset += 4;
342 if offset + val_len > data.len() {
343 return None;
344 }
345 let val: SochValue = serde_json::from_slice(&data[offset..offset + val_len]).ok()?;
346 offset += val_len;
347
348 map.insert(key, val);
349 }
350
351 Some(map)
352}
353
354pub fn encode_node_value(
358 node_type: &str,
359 props: &std::collections::HashMap<String, SochValue>,
360) -> Vec<u8> {
361 let type_bytes = node_type.as_bytes();
362 let props_bytes = encode_properties(props);
363 let mut buf = Vec::with_capacity(2 + type_bytes.len() + props_bytes.len());
364 buf.extend_from_slice(&(type_bytes.len() as u16).to_be_bytes());
365 buf.extend_from_slice(type_bytes);
366 buf.extend_from_slice(&props_bytes);
367 buf
368}
369
370pub fn decode_node_value(
372 data: &[u8],
373) -> Option<(String, std::collections::HashMap<String, SochValue>)> {
374 if data.len() < 2 {
375 return None;
376 }
377 let type_len = u16::from_be_bytes([data[0], data[1]]) as usize;
378 if data.len() < 2 + type_len {
379 return None;
380 }
381 let node_type = std::str::from_utf8(&data[2..2 + type_len])
382 .ok()?
383 .to_string();
384 let props = decode_properties(&data[2 + type_len..])?;
385 Some((node_type, props))
386}
387
388pub fn encode_edge_value(
395 from_id: &RecordId,
396 edge_type: &str,
397 to_id: &RecordId,
398 props: &std::collections::HashMap<String, SochValue>,
399) -> Vec<u8> {
400 let et_bytes = edge_type.as_bytes();
401 let from_str = from_id.to_string();
402 let from_bytes = from_str.as_bytes();
403 let to_str = to_id.to_string();
404 let to_bytes = to_str.as_bytes();
405 let props_bytes = encode_properties(props);
406
407 let mut buf = Vec::with_capacity(
408 2 + et_bytes.len() + 2 + from_bytes.len() + 2 + to_bytes.len() + props_bytes.len(),
409 );
410 buf.extend_from_slice(&(et_bytes.len() as u16).to_be_bytes());
411 buf.extend_from_slice(et_bytes);
412 buf.extend_from_slice(&(from_bytes.len() as u16).to_be_bytes());
413 buf.extend_from_slice(from_bytes);
414 buf.extend_from_slice(&(to_bytes.len() as u16).to_be_bytes());
415 buf.extend_from_slice(to_bytes);
416 buf.extend_from_slice(&props_bytes);
417 buf
418}
419
420#[derive(Debug, Clone)]
422pub struct DecodedEdgeValue {
423 pub edge_type: String,
424 pub from_id: RecordId,
425 pub to_id: RecordId,
426 pub properties: std::collections::HashMap<String, SochValue>,
427}
428
429pub fn decode_edge_value(data: &[u8]) -> Option<DecodedEdgeValue> {
431 let mut offset = 0;
432
433 if offset + 2 > data.len() {
435 return None;
436 }
437 let et_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
438 offset += 2;
439 if offset + et_len > data.len() {
440 return None;
441 }
442 let edge_type = std::str::from_utf8(&data[offset..offset + et_len])
443 .ok()?
444 .to_string();
445 offset += et_len;
446
447 if offset + 2 > data.len() {
449 return None;
450 }
451 let from_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
452 offset += 2;
453 if offset + from_len > data.len() {
454 return None;
455 }
456 let from_str = std::str::from_utf8(&data[offset..offset + from_len]).ok()?;
457 let from_id = RecordId::parse(from_str)?;
458 offset += from_len;
459
460 if offset + 2 > data.len() {
462 return None;
463 }
464 let to_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
465 offset += 2;
466 if offset + to_len > data.len() {
467 return None;
468 }
469 let to_str = std::str::from_utf8(&data[offset..offset + to_len]).ok()?;
470 let to_id = RecordId::parse(to_str)?;
471 offset += to_len;
472
473 let props = decode_properties(&data[offset..])?;
475
476 Some(DecodedEdgeValue {
477 edge_type,
478 from_id,
479 to_id,
480 properties: props,
481 })
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
491 fn test_node_key_format() {
492 let rid = RecordId::new("person", 42);
493 let key = node_key("agent_001", &rid);
494 assert_eq!(key[0], TAG_NODE);
495 assert!(key.len() > 5);
498 }
499
500 #[test]
501 fn test_node_prefix_is_prefix_of_node_key() {
502 let rid = RecordId::new("person", 42);
503 let key = node_key("agent_001", &rid);
504 let prefix = node_prefix("agent_001");
505 assert!(key.starts_with(&prefix));
506 }
507
508 #[test]
509 fn test_edge_key_roundtrip() {
510 let from = RecordId::new("user", 1);
511 let to = RecordId::new("conv", 100);
512 let key = edge_key("ns", &from, "STARTED", &to);
513 let decoded = decode_edge_key(&key).unwrap();
514 assert_eq!(decoded.from_key, from.to_key());
515 assert_eq!(decoded.to_key, to.to_key());
516 assert_eq!(decoded.edge_type_hash, fnv1a_32(b"STARTED"));
517 }
518
519 #[test]
520 fn test_edge_from_prefix_is_prefix() {
521 let from = RecordId::new("user", 1);
522 let to = RecordId::new("conv", 100);
523 let key = edge_key("ns", &from, "SENT", &to);
524 let prefix = edge_from_prefix("ns", &from);
525 assert!(key.starts_with(&prefix));
526 }
527
528 #[test]
529 fn test_edge_from_type_prefix_is_prefix() {
530 let from = RecordId::new("user", 1);
531 let to = RecordId::new("conv", 100);
532 let key = edge_key("ns", &from, "SENT", &to);
533 let prefix = edge_from_type_prefix("ns", &from, "SENT");
534 assert!(key.starts_with(&prefix));
535 }
536
537 #[test]
538 fn test_reverse_key_roundtrip() {
539 let from = RecordId::new("user", 1);
540 let to = RecordId::new("msg", 42);
541 let key = reverse_key("ns", "SENT", &to, &from);
542 let decoded = decode_reverse_key(&key).unwrap();
543 assert_eq!(decoded.from_key, from.to_key());
544 assert_eq!(decoded.to_key, to.to_key());
545 assert_eq!(decoded.edge_type_hash, fnv1a_32(b"SENT"));
546 }
547
548 #[test]
549 fn test_reverse_prefix_is_prefix() {
550 let from = RecordId::new("user", 1);
551 let to = RecordId::new("msg", 42);
552 let key = reverse_key("ns", "SENT", &to, &from);
553 let prefix = reverse_type_to_prefix("ns", "SENT", &to);
554 assert!(key.starts_with(&prefix));
555 }
556
557 #[test]
558 fn test_encode_decode_properties() {
559 let mut props = std::collections::HashMap::new();
560 props.insert("name".to_string(), SochValue::Text("Alice".to_string()));
561 props.insert("age".to_string(), SochValue::Int(30));
562 props.insert("active".to_string(), SochValue::Bool(true));
563
564 let encoded = encode_properties(&props);
565 let decoded = decode_properties(&encoded).unwrap();
566
567 assert_eq!(decoded.len(), 3);
568 assert_eq!(
569 decoded.get("name"),
570 Some(&SochValue::Text("Alice".to_string()))
571 );
572 assert_eq!(decoded.get("age"), Some(&SochValue::Int(30)));
573 assert_eq!(decoded.get("active"), Some(&SochValue::Bool(true)));
574 }
575
576 #[test]
577 fn test_encode_decode_node_value() {
578 let mut props = std::collections::HashMap::new();
579 props.insert("email".to_string(), SochValue::Text("a@b.com".to_string()));
580
581 let encoded = encode_node_value("User", &props);
582 let (node_type, decoded_props) = decode_node_value(&encoded).unwrap();
583 assert_eq!(node_type, "User");
584 assert_eq!(
585 decoded_props.get("email"),
586 Some(&SochValue::Text("a@b.com".to_string()))
587 );
588 }
589
590 #[test]
591 fn test_encode_decode_edge_value() {
592 let from = RecordId::new("user", 1);
593 let to = RecordId::from_string("conv", "abc");
594 let mut props = std::collections::HashMap::new();
595 props.insert("weight".to_string(), SochValue::Float(0.95));
596
597 let encoded = encode_edge_value(&from, "STARTED", &to, &props);
598 let decoded = decode_edge_value(&encoded).unwrap();
599 assert_eq!(decoded.edge_type, "STARTED");
600 assert_eq!(decoded.from_id, from);
601 assert_eq!(decoded.to_id, to);
602 assert_eq!(
603 decoded.properties.get("weight"),
604 Some(&SochValue::Float(0.95))
605 );
606 }
607
608 #[test]
609 fn test_empty_properties() {
610 let props = std::collections::HashMap::new();
611 let encoded = encode_properties(&props);
612 let decoded = decode_properties(&encoded).unwrap();
613 assert!(decoded.is_empty());
614 }
615
616 #[test]
617 fn test_different_namespaces_produce_different_keys() {
618 let rid = RecordId::new("person", 1);
619 let key1 = node_key("ns_a", &rid);
620 let key2 = node_key("ns_b", &rid);
621 assert_ne!(key1, key2);
622 }
623}