graph_r/key.rs
1// Author: spicyzhug@gmail.com
2// Copyright: lituus-io, all rights reserved
3//
4//! Identity types. The durable foreign key everywhere is [`UrlKey`] — the
5//! 64-bit xxh3 of a canonical URL, byte-compatible with link-r's key of the
6//! same name — because link-r's internal document ids are unstable across
7//! removals. [`NodeId`]s here are dense per-snapshot indices assigned at
8//! compaction; they are stable *within* one base generation and never
9//! persisted across it, exactly link-r's `DocId` philosophy.
10
11use crate::hash::xxh3_64;
12
13/// Durable identity of a document node: xxh3 of its canonical URL.
14///
15/// When the `bridge` feature is enabled this is produced from link-r's
16/// canonicalization; standalone callers must pass an already-canonical URL
17/// string to [`UrlKey::of`] (graph-r deliberately does not re-implement URL
18/// canonicalization — one recipe, one owner).
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
20pub struct UrlKey(pub u64);
21
22impl UrlKey {
23 /// Key a canonical URL string.
24 #[must_use]
25 pub fn of(canonical_url: &str) -> Self {
26 Self(xxh3_64(canonical_url.as_bytes()))
27 }
28
29 /// The raw 64-bit key.
30 #[must_use]
31 pub fn raw(self) -> u64 {
32 self.0
33 }
34}
35
36/// Durable identity of a sub-document segment: xxh3 of the owning key plus
37/// the segment's heading path, so a re-crawl that keeps a heading keeps the
38/// segment's identity (and any enrichment attached to it).
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
40pub struct SegKey(pub u64);
41
42impl SegKey {
43 /// Derive the segment key for `heading_path` under `owner`.
44 #[must_use]
45 pub fn derive(owner: UrlKey, heading_path: &str) -> Self {
46 let mut buf = Vec::with_capacity(8 + 1 + heading_path.len());
47 buf.extend_from_slice(&owner.0.to_le_bytes());
48 buf.push(0);
49 buf.extend_from_slice(heading_path.as_bytes());
50 Self(xxh3_64(&buf))
51 }
52}
53
54/// Dense per-snapshot node index (position in the base `Nodes` section).
55/// Invalidated by compaction; cross-snapshot APIs use [`UrlKey`].
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
57pub struct NodeId(pub u32);
58
59/// Typed edges. The discriminants are burned into the on-disk format; new
60/// kinds are additive (the enum is non-exhaustive and unknown values decode
61/// to [`EdgeType::Unknown`] rather than failing the file).
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
63#[non_exhaustive]
64pub enum EdgeType {
65 /// An outbound hyperlink observed in the crawled document (crawl tier).
66 Link,
67 /// A semantic-similarity edge proposed from dense vectors or enrichment.
68 Related,
69 /// A redirect observed at fetch time.
70 Redirect,
71 /// Structural containment: a table contains its columns.
72 Contains,
73 /// Value lineage: the source computes from the destination (a column
74 /// from the columns it is calculated from). With `eflags::EXACT`, an
75 /// unchanged copy along which a description carries over verbatim.
76 Derives,
77 /// Row lineage: the destination decides which rows the source holds (a
78 /// filter, join key, grouping or match condition of its producing SQL).
79 Conditions,
80 /// Forward-compatibility catch-all for tags this build does not know.
81 Unknown(u8),
82}
83
84impl EdgeType {
85 /// On-disk tag.
86 #[must_use]
87 pub fn as_tag(self) -> u8 {
88 match self {
89 Self::Link => 0,
90 Self::Related => 2,
91 Self::Redirect => 3,
92 Self::Contains => 4,
93 Self::Derives => 5,
94 Self::Conditions => 6,
95 Self::Unknown(t) => t,
96 }
97 }
98
99 /// Decode an on-disk tag (never fails; unknown tags are preserved).
100 #[must_use]
101 pub fn from_tag(tag: u8) -> Self {
102 match tag {
103 0 => Self::Link,
104 2 => Self::Related,
105 3 => Self::Redirect,
106 4 => Self::Contains,
107 5 => Self::Derives,
108 6 => Self::Conditions,
109 t => Self::Unknown(t),
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn url_key_matches_xxh3_of_string() {
120 let k = UrlKey::of("https://example.com/a");
121 assert_eq!(k.raw(), xxh3_64(b"https://example.com/a"));
122 }
123
124 #[test]
125 fn seg_key_is_stable_and_distinct() {
126 let owner = UrlKey::of("https://example.com/a");
127 let a = SegKey::derive(owner, "Install > Linux");
128 let b = SegKey::derive(owner, "Install > Linux");
129 let c = SegKey::derive(owner, "Install > macOS");
130 assert_eq!(a, b);
131 assert_ne!(a, c);
132 assert_ne!(a, SegKey::derive(UrlKey::of("https://example.com/b"), "Install > Linux"));
133 }
134
135 #[test]
136 fn edge_type_tags_round_trip() {
137 for t in [EdgeType::Link, EdgeType::Related, EdgeType::Redirect, EdgeType::Unknown(9)] {
138 assert_eq!(EdgeType::from_tag(t.as_tag()), t);
139 }
140 }
141}