1use std::collections::HashSet;
11
12use crate::dict::{ChunkedSection, DictSectionBuilder};
13use crate::terms::{NodeId, ObjectId, PredicateId, SubjectId};
14
15#[cfg(feature = "parallel")]
19fn sort_terms(v: &mut [String]) {
20 use rayon::slice::ParallelSliceMut;
21 v.par_sort_unstable();
22}
23#[cfg(not(feature = "parallel"))]
24fn sort_terms(v: &mut [String]) {
25 v.sort_unstable();
26}
27
28#[derive(Default)]
36pub struct DictionaryBuilder {
37 subjects: HashSet<String>,
38 objects: HashSet<String>,
39 predicates: HashSet<String>,
40}
41
42impl DictionaryBuilder {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn observe(&mut self, subject: &str, predicate: &str, object: &str) {
52 if !self.subjects.contains(subject) {
53 self.subjects.insert(subject.to_string());
54 }
55 if !self.objects.contains(object) {
56 self.objects.insert(object.to_string());
57 }
58 if !self.predicates.contains(predicate) {
59 self.predicates.insert(predicate.to_string());
60 }
61 }
62
63 pub fn build(self) -> Dictionary {
64 let mut subjects: Vec<String> = self.subjects.into_iter().collect();
67 let mut predicates: Vec<String> = self.predicates.into_iter().collect();
68 let object_set = self.objects; sort_terms(&mut subjects);
70 sort_terms(&mut predicates);
71
72 let subject_set: HashSet<&str> = subjects.iter().map(String::as_str).collect();
74
75 let mut shared_b = DictSectionBuilder::new();
76 let mut subj_b = DictSectionBuilder::new();
77 let mut obj_b = DictSectionBuilder::new();
78 let mut pred_b = DictSectionBuilder::new();
79
80 for s in &subjects {
82 if object_set.contains(s) {
83 shared_b.push(s.clone());
84 } else {
85 subj_b.push(s.clone());
86 }
87 }
88 let mut objects: Vec<String> = object_set.into_iter().collect();
90 sort_terms(&mut objects);
91 for o in &objects {
92 if !subject_set.contains(o.as_str()) {
93 obj_b.push(o.clone());
94 }
95 }
96 for p in &predicates {
97 pred_b.push(p.clone());
98 }
99
100 let has_quoted_triples = subjects.iter().any(|s| s.starts_with("<<"))
103 || objects.iter().any(|o| o.starts_with("<<"));
104
105 let shared = shared_b.build();
106 let mut dict =
107 Dictionary::from_sections([shared, subj_b.build(), obj_b.build(), pred_b.build()]);
108 dict.has_quoted_triples = has_quoted_triples;
109 dict
110 }
111}
112
113pub struct Dictionary {
119 sections: [ChunkedSection; 4],
121 shared_len: u32,
122 has_quoted_triples: bool,
126}
127
128impl Dictionary {
129 pub fn from_sections(sections: [Vec<u8>; 4]) -> Self {
132 Self::from_chunked_sections(sections.map(ChunkedSection::local))
133 }
134
135 pub fn from_chunked_sections(sections: [ChunkedSection; 4]) -> Self {
137 let shared_len = sections[0].term_count();
138 Dictionary {
139 sections,
140 shared_len,
141 has_quoted_triples: false,
142 }
143 }
144
145 pub fn term_count(&self) -> u32 {
147 self.sections.iter().map(|s| s.term_count()).sum()
148 }
149
150 pub fn has_quoted_triples(&self) -> bool {
154 self.has_quoted_triples
155 }
156
157 pub fn load_incomplete(&self) -> bool {
160 self.sections.iter().any(|s| s.load_incomplete())
161 }
162
163 pub fn reset_load_failure(&self) {
167 self.sections.iter().for_each(|s| s.reset_load_failure());
168 }
169
170 pub fn prefetch_all(&self) {
175 for s in &self.sections {
176 s.prefetch_all();
177 }
178 }
179
180 pub fn prefetch_terms(&self, node_ids: &[u32], predicate_ids: &[u32]) {
187 let mut want: [std::collections::BTreeSet<usize>; 4] = Default::default();
188 for &n in node_ids {
189 if let Some((si, ci)) = self.node_chunk(n) {
190 want[si].insert(ci);
191 }
192 }
193 for &p in predicate_ids {
194 if let Some(ci) = self.sections[3].chunk_of_id(p) {
195 want[3].insert(ci);
196 }
197 }
198 for (si, set) in want.iter().enumerate() {
199 if set.len() >= 2 {
200 let cis: Vec<usize> = set.iter().copied().collect();
201 self.sections[si].prefetch_chunks(&cis);
202 }
203 }
204 }
205
206 fn node_chunk(&self, node: u32) -> Option<(usize, usize)> {
209 let su = self.subject_only_count();
210 let (si, local) = if node < self.shared_len + su {
211 let id = node + 1; if id <= self.shared_len {
213 (0, id)
214 } else {
215 (1, id - self.shared_len)
216 }
217 } else {
218 let id = node + 1 - su; if id <= self.shared_len {
220 (0, id)
221 } else {
222 (2, id - self.shared_len)
223 }
224 };
225 Some((si, self.sections[si].chunk_of_id(local)?))
226 }
227
228 pub fn shared_count(&self) -> u32 {
230 self.shared_len
231 }
232
233 pub fn subject_only_count(&self) -> u32 {
235 self.sections[1].term_count()
236 }
237
238 pub fn object_only_count(&self) -> u32 {
240 self.sections[2].term_count()
241 }
242
243 pub fn node_count(&self) -> u32 {
253 self.shared_len + self.subject_only_count() + self.object_only_count()
254 }
255
256 pub fn subject_node(&self, sid: SubjectId) -> NodeId {
259 sid.saturating_sub(1)
262 }
263
264 pub fn object_node(&self, oid: ObjectId) -> NodeId {
267 if oid <= self.shared_len {
268 oid.saturating_sub(1)
269 } else {
270 oid.saturating_sub(1) + self.subject_only_count()
271 }
272 }
273
274 pub fn node_term(&self, node: NodeId) -> Option<String> {
276 let su = self.subject_only_count();
277 if node < self.shared_len + su {
278 self.subject_term(node + 1)
279 } else {
280 self.object_term(node + 1 - su)
281 }
282 }
283
284 pub fn prefetch_subject_terms(&self, ids: &[SubjectId]) {
288 let mut per_section: [Vec<usize>; 2] = Default::default();
289 for &id in ids {
290 let (si, local) = if id <= self.shared_len {
291 (0usize, id)
292 } else {
293 (1, id - self.shared_len)
294 };
295 if let Some(ci) = self.sections[si].chunk_of_term(local) {
296 per_section[si].push(ci);
297 }
298 }
299 for (si, cis) in per_section.iter_mut().enumerate() {
300 cis.sort_unstable();
301 cis.dedup();
302 self.sections[si].prefetch_chunks(cis);
303 }
304 }
305
306 pub fn prefetch_node_terms(&self, nodes: &[NodeId]) {
311 let su = self.subject_only_count();
312 let mut per_section: [Vec<usize>; 3] = Default::default();
313 for &node in nodes {
314 let (si, local) = if node < self.shared_len + su {
315 let sid = node + 1;
316 if sid <= self.shared_len {
317 (0usize, sid)
318 } else {
319 (1, sid - self.shared_len)
320 }
321 } else {
322 let oid = node + 1 - su;
323 if oid <= self.shared_len {
324 (0, oid)
325 } else {
326 (2, oid - self.shared_len)
327 }
328 };
329 if let Some(ci) = self.sections[si].chunk_of_term(local) {
330 per_section[si].push(ci);
331 }
332 }
333 for (si, cis) in per_section.iter_mut().enumerate() {
334 cis.sort_unstable();
335 cis.dedup();
336 self.sections[si].prefetch_chunks(cis);
337 }
338 }
339
340 pub fn node_of_term(&self, term: &str) -> Option<NodeId> {
342 if let Some(sid) = self.subject_id(term) {
343 return Some(self.subject_node(sid));
344 }
345 self.object_id(term).map(|oid| self.object_node(oid))
346 }
347
348 pub fn node_as_subject_id(&self, node: NodeId) -> Option<SubjectId> {
351 if node < self.shared_len + self.subject_only_count() {
352 Some(node + 1)
353 } else {
354 None
355 }
356 }
357
358 pub fn node_as_object_id(&self, node: NodeId) -> Option<ObjectId> {
361 let (s, su) = (self.shared_len, self.subject_only_count());
362 if node < s {
363 Some(node + 1) } else if node >= s + su {
365 Some(node + 1 - su) } else {
367 None }
369 }
370
371 pub fn subject_id(&self, term: &str) -> Option<SubjectId> {
375 if let Some(id) = self.sections[0].id(term) {
376 return Some(id);
377 }
378 self.sections[1].id(term).map(|id| self.shared_len + id)
379 }
380
381 pub fn object_id(&self, term: &str) -> Option<ObjectId> {
383 if let Some(id) = self.sections[0].id(term) {
384 return Some(id);
385 }
386 self.sections[2].id(term).map(|id| self.shared_len + id)
387 }
388
389 pub fn predicate_id(&self, term: &str) -> Option<PredicateId> {
391 self.sections[3].id(term)
392 }
393
394 pub fn subject_term(&self, id: SubjectId) -> Option<String> {
397 if id <= self.shared_len {
398 self.sections[0].term(id)
399 } else {
400 self.sections[1].term(id - self.shared_len)
401 }
402 }
403
404 pub fn object_term(&self, id: ObjectId) -> Option<String> {
405 if id <= self.shared_len {
406 self.sections[0].term(id)
407 } else {
408 self.sections[2].term(id - self.shared_len)
409 }
410 }
411
412 pub fn predicate_term(&self, id: PredicateId) -> Option<String> {
413 self.sections[3].term(id)
414 }
415
416 pub fn encode(&self, s: &str, p: &str, o: &str) -> Option<(SubjectId, PredicateId, ObjectId)> {
419 Some((
420 self.subject_id(s)?,
421 self.predicate_id(p)?,
422 self.object_id(o)?,
423 ))
424 }
425
426 pub fn sections(&self) -> [Vec<u8>; 4] {
428 [
429 self.sections[0].raw_section_bytes(),
430 self.sections[1].raw_section_bytes(),
431 self.sections[2].raw_section_bytes(),
432 self.sections[3].raw_section_bytes(),
433 ]
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 fn dict() -> Dictionary {
442 let mut b = DictionaryBuilder::new();
443 b.observe("Alice", "knows", "Bob");
446 b.observe("Bob", "knows", "Carol");
447 b.build()
448 }
449
450 #[test]
451 fn shared_term_has_same_id_in_both_roles() {
452 let d = dict();
453 assert_eq!(d.shared_count(), 1); let sid = d.subject_id("Bob").unwrap();
455 let oid = d.object_id("Bob").unwrap();
456 assert_eq!(sid, oid, "shared term must share its ID across roles");
457 assert_eq!(sid, 1, "shared terms get the lowest IDs");
458 }
459
460 #[test]
461 fn role_specific_terms_round_trip() {
462 let d = dict();
463 let a = d.subject_id("Alice").unwrap();
465 assert!(a > d.shared_count());
466 assert_eq!(d.subject_term(a).as_deref(), Some("Alice"));
467 let c = d.object_id("Carol").unwrap();
469 assert!(c > d.shared_count());
470 assert_eq!(d.object_term(c).as_deref(), Some("Carol"));
471 let k = d.predicate_id("knows").unwrap();
473 assert_eq!(d.predicate_term(k).as_deref(), Some("knows"));
474 assert_eq!(d.object_id("Alice"), None);
476 assert_eq!(d.subject_id("Carol"), None);
477 }
478
479 #[test]
480 fn encode_full_triples() {
481 let d = dict();
482 let t1 = d.encode("Alice", "knows", "Bob").unwrap();
483 let t2 = d.encode("Bob", "knows", "Carol").unwrap();
484 assert_eq!(t1.2, t2.0);
486 assert!(d.encode("Nobody", "knows", "Bob").is_none());
487 }
488}