1use std::borrow::Cow;
6
7use rustc_hash::FxHashMap;
8
9use crate::codec::primitives::Writer;
10use crate::model::{DataType, Id, Op};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct ContextEdge {
17 pub type_id: Id,
19 pub to_entity_id: Id,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub struct Context {
30 pub root_id: Id,
32 pub edges: Vec<ContextEdge>,
34}
35
36#[derive(Debug, Clone, PartialEq)]
41pub struct Edit<'a> {
42 pub id: Id,
44 pub name: Cow<'a, str>,
46 pub authors: Vec<Id>,
48 pub created_at: i64,
50 pub ops: Vec<Op<'a>>,
52}
53
54impl<'a> Edit<'a> {
55 pub fn new(id: Id) -> Self {
57 Self {
58 id,
59 name: Cow::Borrowed(""),
60 authors: Vec::new(),
61 created_at: 0,
62 ops: Vec::new(),
63 }
64 }
65
66 pub fn with_name(id: Id, name: impl Into<Cow<'a, str>>) -> Self {
68 Self {
69 id,
70 name: name.into(),
71 authors: Vec::new(),
72 created_at: 0,
73 ops: Vec::new(),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Default)]
83pub struct WireDictionaries {
84 pub properties: Vec<(Id, DataType)>,
86 pub relation_types: Vec<Id>,
88 pub languages: Vec<Id>,
90 pub units: Vec<Id>,
92 pub objects: Vec<Id>,
94 pub context_ids: Vec<Id>,
96 pub contexts: Vec<Context>,
98}
99
100impl WireDictionaries {
101 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn get_property(&self, index: usize) -> Option<&(Id, DataType)> {
108 self.properties.get(index)
109 }
110
111 pub fn get_relation_type(&self, index: usize) -> Option<&Id> {
113 self.relation_types.get(index)
114 }
115
116 pub fn get_language(&self, index: usize) -> Option<&Id> {
121 if index == 0 {
122 None
123 } else {
124 self.languages.get(index - 1)
125 }
126 }
127
128 pub fn get_unit(&self, index: usize) -> Option<&Id> {
133 if index == 0 {
134 None
135 } else {
136 self.units.get(index - 1)
137 }
138 }
139
140 pub fn get_object(&self, index: usize) -> Option<&Id> {
142 self.objects.get(index)
143 }
144
145 pub fn get_context_id(&self, index: usize) -> Option<&Id> {
147 self.context_ids.get(index)
148 }
149
150 pub fn get_context(&self, index: usize) -> Option<&Context> {
152 self.contexts.get(index)
153 }
154}
155
156#[derive(Debug, Clone, Default)]
160pub struct DictionaryBuilder {
161 properties: Vec<(Id, DataType)>,
162 property_indices: FxHashMap<Id, usize>,
163 relation_types: Vec<Id>,
164 relation_type_indices: FxHashMap<Id, usize>,
165 languages: Vec<Id>,
166 language_indices: FxHashMap<Id, usize>,
167 units: Vec<Id>,
168 unit_indices: FxHashMap<Id, usize>,
169 objects: Vec<Id>,
170 object_indices: FxHashMap<Id, usize>,
171 context_ids: Vec<Id>,
172 context_id_indices: FxHashMap<Id, usize>,
173 contexts: Vec<Context>,
174 context_indices: FxHashMap<Context, usize>,
175}
176
177impl DictionaryBuilder {
178 pub fn new() -> Self {
180 Self::default()
181 }
182
183 pub fn with_capacity(estimated_ops: usize) -> Self {
194 let prop_cap = estimated_ops / 4 + 1;
195 let rel_cap = estimated_ops / 20 + 1;
196 let lang_cap = 4;
197 let unit_cap = 4;
198 let obj_cap = estimated_ops / 2 + 1;
199 let ctx_id_cap = 8;
200 let ctx_cap = 4;
201
202 Self {
203 properties: Vec::with_capacity(prop_cap),
204 property_indices: FxHashMap::with_capacity_and_hasher(prop_cap, Default::default()),
205 relation_types: Vec::with_capacity(rel_cap),
206 relation_type_indices: FxHashMap::with_capacity_and_hasher(rel_cap, Default::default()),
207 languages: Vec::with_capacity(lang_cap),
208 language_indices: FxHashMap::with_capacity_and_hasher(lang_cap, Default::default()),
209 units: Vec::with_capacity(unit_cap),
210 unit_indices: FxHashMap::with_capacity_and_hasher(unit_cap, Default::default()),
211 objects: Vec::with_capacity(obj_cap),
212 object_indices: FxHashMap::with_capacity_and_hasher(obj_cap, Default::default()),
213 context_ids: Vec::with_capacity(ctx_id_cap),
214 context_id_indices: FxHashMap::with_capacity_and_hasher(ctx_id_cap, Default::default()),
215 contexts: Vec::with_capacity(ctx_cap),
216 context_indices: FxHashMap::with_capacity_and_hasher(ctx_cap, Default::default()),
217 }
218 }
219
220 pub fn add_property(&mut self, id: Id, data_type: DataType) -> usize {
222 if let Some(&idx) = self.property_indices.get(&id) {
223 idx
224 } else {
225 let idx = self.properties.len();
226 self.properties.push((id, data_type));
227 self.property_indices.insert(id, idx);
228 idx
229 }
230 }
231
232 pub fn add_relation_type(&mut self, id: Id) -> usize {
234 if let Some(&idx) = self.relation_type_indices.get(&id) {
235 idx
236 } else {
237 let idx = self.relation_types.len();
238 self.relation_types.push(id);
239 self.relation_type_indices.insert(id, idx);
240 idx
241 }
242 }
243
244 pub fn add_language(&mut self, id: Option<Id>) -> usize {
248 match id {
249 None => 0,
250 Some(lang_id) => {
251 if let Some(&idx) = self.language_indices.get(&lang_id) {
252 idx + 1
253 } else {
254 let idx = self.languages.len();
255 self.languages.push(lang_id);
256 self.language_indices.insert(lang_id, idx);
257 idx + 1
258 }
259 }
260 }
261 }
262
263 pub fn add_unit(&mut self, id: Option<Id>) -> usize {
267 match id {
268 None => 0,
269 Some(unit_id) => {
270 if let Some(&idx) = self.unit_indices.get(&unit_id) {
271 idx + 1
272 } else {
273 let idx = self.units.len();
274 self.units.push(unit_id);
275 self.unit_indices.insert(unit_id, idx);
276 idx + 1
277 }
278 }
279 }
280 }
281
282 pub fn add_object(&mut self, id: Id) -> usize {
284 if let Some(&idx) = self.object_indices.get(&id) {
285 idx
286 } else {
287 let idx = self.objects.len();
288 self.objects.push(id);
289 self.object_indices.insert(id, idx);
290 idx
291 }
292 }
293
294 pub fn add_context_id(&mut self, id: Id) -> usize {
296 if let Some(&idx) = self.context_id_indices.get(&id) {
297 idx
298 } else {
299 let idx = self.context_ids.len();
300 self.context_ids.push(id);
301 self.context_id_indices.insert(id, idx);
302 idx
303 }
304 }
305
306 pub fn add_context(&mut self, context: &Context) -> usize {
313 if let Some(&idx) = self.context_indices.get(context) {
314 idx
315 } else {
316 self.add_context_id(context.root_id);
318 for edge in &context.edges {
319 self.add_relation_type(edge.type_id);
321 self.add_context_id(edge.to_entity_id);
322 }
323
324 let idx = self.contexts.len();
326 self.contexts.push(context.clone());
327 self.context_indices.insert(context.clone(), idx);
328 idx
329 }
330 }
331
332 pub fn get_context_index(&self, context: &Context) -> Option<usize> {
334 self.context_indices.get(context).copied()
335 }
336
337 pub fn build(self) -> WireDictionaries {
339 WireDictionaries {
340 properties: self.properties,
341 relation_types: self.relation_types,
342 languages: self.languages,
343 units: self.units,
344 objects: self.objects,
345 context_ids: self.context_ids,
346 contexts: self.contexts,
347 }
348 }
349
350 pub fn as_wire_dicts(&self) -> WireDictionaries {
353 WireDictionaries {
354 properties: self.properties.clone(),
355 relation_types: self.relation_types.clone(),
356 languages: self.languages.clone(),
357 units: self.units.clone(),
358 objects: self.objects.clone(),
359 context_ids: self.context_ids.clone(),
360 contexts: self.contexts.clone(),
361 }
362 }
363
364 pub fn get_property_index(&self, id: &Id) -> Option<usize> {
366 self.property_indices.get(id).copied()
367 }
368
369 pub fn get_relation_type_index(&self, id: &Id) -> Option<usize> {
371 self.relation_type_indices.get(id).copied()
372 }
373
374 pub fn get_language_index(&self, id: Option<&Id>) -> Option<usize> {
377 match id {
378 None => Some(0),
379 Some(lang_id) => self.language_indices.get(lang_id).map(|idx| idx + 1),
380 }
381 }
382
383 pub fn get_object_index(&self, id: &Id) -> Option<usize> {
385 self.object_indices.get(id).copied()
386 }
387
388 pub fn get_context_id_index(&self, id: &Id) -> Option<usize> {
390 self.context_id_indices.get(id).copied()
391 }
392
393 pub fn write_dictionaries(&self, writer: &mut Writer) {
395 writer.write_varint(self.properties.len() as u64);
397 for (id, data_type) in &self.properties {
398 writer.write_id(id);
399 writer.write_byte(*data_type as u8);
400 }
401
402 writer.write_id_vec(&self.relation_types);
404
405 writer.write_id_vec(&self.languages);
407
408 writer.write_id_vec(&self.units);
410
411 writer.write_id_vec(&self.objects);
413
414 writer.write_id_vec(&self.context_ids);
416 }
417
418 pub fn write_contexts(&self, writer: &mut Writer) {
425 writer.write_varint(self.contexts.len() as u64);
426 for ctx in &self.contexts {
427 let root_idx = self.context_id_indices.get(&ctx.root_id)
429 .copied()
430 .expect("context root_id must be in context_ids dictionary");
431 writer.write_varint(root_idx as u64);
432
433 writer.write_varint(ctx.edges.len() as u64);
435 for edge in &ctx.edges {
436 let type_idx = self.relation_type_indices.get(&edge.type_id)
438 .copied()
439 .expect("context edge type_id must be in relation_types dictionary");
440 let to_idx = self.context_id_indices.get(&edge.to_entity_id)
442 .copied()
443 .expect("context edge to_entity_id must be in context_ids dictionary");
444 writer.write_varint(type_idx as u64);
445 writer.write_varint(to_idx as u64);
446 }
447 }
448 }
449
450 pub fn into_sorted(self) -> Self {
457 let mut properties = self.properties;
459 properties.sort_by(|a, b| a.0.cmp(&b.0));
460 let property_indices: FxHashMap<Id, usize> = properties
461 .iter()
462 .enumerate()
463 .map(|(i, (id, _))| (*id, i))
464 .collect();
465
466 let mut relation_types = self.relation_types;
468 relation_types.sort();
469 let relation_type_indices: FxHashMap<Id, usize> = relation_types
470 .iter()
471 .enumerate()
472 .map(|(i, id)| (*id, i))
473 .collect();
474
475 let mut languages = self.languages;
477 languages.sort();
478 let language_indices: FxHashMap<Id, usize> = languages
479 .iter()
480 .enumerate()
481 .map(|(i, id)| (*id, i))
482 .collect();
483
484 let mut units = self.units;
486 units.sort();
487 let unit_indices: FxHashMap<Id, usize> = units
488 .iter()
489 .enumerate()
490 .map(|(i, id)| (*id, i))
491 .collect();
492
493 let mut objects = self.objects;
495 objects.sort();
496 let object_indices: FxHashMap<Id, usize> = objects
497 .iter()
498 .enumerate()
499 .map(|(i, id)| (*id, i))
500 .collect();
501
502 let mut context_ids = self.context_ids;
504 context_ids.sort();
505 let context_id_indices: FxHashMap<Id, usize> = context_ids
506 .iter()
507 .enumerate()
508 .map(|(i, id)| (*id, i))
509 .collect();
510
511 let mut contexts = self.contexts;
513 contexts.sort_by(|a, b| {
514 match a.root_id.cmp(&b.root_id) {
516 std::cmp::Ordering::Equal => {
517 let a_edges: Vec<_> = a.edges.iter().map(|e| (e.type_id, e.to_entity_id)).collect();
519 let b_edges: Vec<_> = b.edges.iter().map(|e| (e.type_id, e.to_entity_id)).collect();
520 a_edges.cmp(&b_edges)
521 }
522 other => other,
523 }
524 });
525 let context_indices: FxHashMap<Context, usize> = contexts
526 .iter()
527 .enumerate()
528 .map(|(i, ctx)| (ctx.clone(), i))
529 .collect();
530
531 Self {
532 properties,
533 property_indices,
534 relation_types,
535 relation_type_indices,
536 languages,
537 language_indices,
538 units,
539 unit_indices,
540 objects,
541 object_indices,
542 context_ids,
543 context_id_indices,
544 contexts,
545 context_indices,
546 }
547 }
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553
554 #[test]
555 fn test_edit_new() {
556 let id = [1u8; 16];
557 let edit = Edit::new(id);
558 assert_eq!(edit.id, id);
559 assert!(edit.name.is_empty());
560 assert!(edit.authors.is_empty());
561 assert!(edit.ops.is_empty());
562 }
563
564 #[test]
565 fn test_dictionary_builder() {
566 let mut builder = DictionaryBuilder::new();
567
568 let prop1 = [1u8; 16];
569 let prop2 = [2u8; 16];
570
571 assert_eq!(builder.add_property(prop1, DataType::Text), 0);
573 assert_eq!(builder.add_property(prop1, DataType::Text), 0);
575 assert_eq!(builder.add_property(prop2, DataType::Int64), 1);
577
578 let dicts = builder.build();
579 assert_eq!(dicts.properties.len(), 2);
580 assert_eq!(dicts.properties[0], (prop1, DataType::Text));
581 assert_eq!(dicts.properties[1], (prop2, DataType::Int64));
582 }
583
584 #[test]
585 fn test_language_indexing() {
586 let mut builder = DictionaryBuilder::new();
587
588 let lang1 = [10u8; 16];
589 let lang2 = [20u8; 16];
590
591 assert_eq!(builder.add_language(None), 0);
593 assert_eq!(builder.add_language(Some(lang1)), 1);
595 assert_eq!(builder.add_language(Some(lang1)), 1);
597 assert_eq!(builder.add_language(Some(lang2)), 2);
599
600 let dicts = builder.build();
601 assert_eq!(dicts.languages.len(), 2);
602
603 assert!(dicts.get_language(0).is_none());
605 assert_eq!(dicts.get_language(1), Some(&lang1));
607 assert_eq!(dicts.get_language(2), Some(&lang2));
609 }
610}