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 {
311 if let Some(&idx) = self.context_indices.get(context) {
312 idx
313 } else {
314 self.add_context_id(context.root_id);
316 for edge in &context.edges {
317 self.add_context_id(edge.type_id);
318 self.add_context_id(edge.to_entity_id);
319 }
320
321 let idx = self.contexts.len();
323 self.contexts.push(context.clone());
324 self.context_indices.insert(context.clone(), idx);
325 idx
326 }
327 }
328
329 pub fn get_context_index(&self, context: &Context) -> Option<usize> {
331 self.context_indices.get(context).copied()
332 }
333
334 pub fn build(self) -> WireDictionaries {
336 WireDictionaries {
337 properties: self.properties,
338 relation_types: self.relation_types,
339 languages: self.languages,
340 units: self.units,
341 objects: self.objects,
342 context_ids: self.context_ids,
343 contexts: self.contexts,
344 }
345 }
346
347 pub fn as_wire_dicts(&self) -> WireDictionaries {
350 WireDictionaries {
351 properties: self.properties.clone(),
352 relation_types: self.relation_types.clone(),
353 languages: self.languages.clone(),
354 units: self.units.clone(),
355 objects: self.objects.clone(),
356 context_ids: self.context_ids.clone(),
357 contexts: self.contexts.clone(),
358 }
359 }
360
361 pub fn get_property_index(&self, id: &Id) -> Option<usize> {
363 self.property_indices.get(id).copied()
364 }
365
366 pub fn get_relation_type_index(&self, id: &Id) -> Option<usize> {
368 self.relation_type_indices.get(id).copied()
369 }
370
371 pub fn get_language_index(&self, id: Option<&Id>) -> Option<usize> {
374 match id {
375 None => Some(0),
376 Some(lang_id) => self.language_indices.get(lang_id).map(|idx| idx + 1),
377 }
378 }
379
380 pub fn get_object_index(&self, id: &Id) -> Option<usize> {
382 self.object_indices.get(id).copied()
383 }
384
385 pub fn get_context_id_index(&self, id: &Id) -> Option<usize> {
387 self.context_id_indices.get(id).copied()
388 }
389
390 pub fn write_dictionaries(&self, writer: &mut Writer) {
392 writer.write_varint(self.properties.len() as u64);
394 for (id, data_type) in &self.properties {
395 writer.write_id(id);
396 writer.write_byte(*data_type as u8);
397 }
398
399 writer.write_id_vec(&self.relation_types);
401
402 writer.write_id_vec(&self.languages);
404
405 writer.write_id_vec(&self.units);
407
408 writer.write_id_vec(&self.objects);
410
411 writer.write_id_vec(&self.context_ids);
413 }
414
415 pub fn write_contexts(&self, writer: &mut Writer) {
422 writer.write_varint(self.contexts.len() as u64);
423 for ctx in &self.contexts {
424 let root_idx = self.context_id_indices.get(&ctx.root_id)
426 .copied()
427 .expect("context root_id must be in context_ids dictionary");
428 writer.write_varint(root_idx as u64);
429
430 writer.write_varint(ctx.edges.len() as u64);
432 for edge in &ctx.edges {
433 let type_idx = self.context_id_indices.get(&edge.type_id)
434 .copied()
435 .expect("context edge type_id must be in context_ids dictionary");
436 let to_idx = self.context_id_indices.get(&edge.to_entity_id)
437 .copied()
438 .expect("context edge to_entity_id must be in context_ids dictionary");
439 writer.write_varint(type_idx as u64);
440 writer.write_varint(to_idx as u64);
441 }
442 }
443 }
444
445 pub fn into_sorted(self) -> Self {
452 let mut properties = self.properties;
454 properties.sort_by(|a, b| a.0.cmp(&b.0));
455 let property_indices: FxHashMap<Id, usize> = properties
456 .iter()
457 .enumerate()
458 .map(|(i, (id, _))| (*id, i))
459 .collect();
460
461 let mut relation_types = self.relation_types;
463 relation_types.sort();
464 let relation_type_indices: FxHashMap<Id, usize> = relation_types
465 .iter()
466 .enumerate()
467 .map(|(i, id)| (*id, i))
468 .collect();
469
470 let mut languages = self.languages;
472 languages.sort();
473 let language_indices: FxHashMap<Id, usize> = languages
474 .iter()
475 .enumerate()
476 .map(|(i, id)| (*id, i))
477 .collect();
478
479 let mut units = self.units;
481 units.sort();
482 let unit_indices: FxHashMap<Id, usize> = units
483 .iter()
484 .enumerate()
485 .map(|(i, id)| (*id, i))
486 .collect();
487
488 let mut objects = self.objects;
490 objects.sort();
491 let object_indices: FxHashMap<Id, usize> = objects
492 .iter()
493 .enumerate()
494 .map(|(i, id)| (*id, i))
495 .collect();
496
497 let mut context_ids = self.context_ids;
499 context_ids.sort();
500 let context_id_indices: FxHashMap<Id, usize> = context_ids
501 .iter()
502 .enumerate()
503 .map(|(i, id)| (*id, i))
504 .collect();
505
506 let mut contexts = self.contexts;
508 contexts.sort_by(|a, b| {
509 match a.root_id.cmp(&b.root_id) {
511 std::cmp::Ordering::Equal => {
512 let a_edges: Vec<_> = a.edges.iter().map(|e| (e.type_id, e.to_entity_id)).collect();
514 let b_edges: Vec<_> = b.edges.iter().map(|e| (e.type_id, e.to_entity_id)).collect();
515 a_edges.cmp(&b_edges)
516 }
517 other => other,
518 }
519 });
520 let context_indices: FxHashMap<Context, usize> = contexts
521 .iter()
522 .enumerate()
523 .map(|(i, ctx)| (ctx.clone(), i))
524 .collect();
525
526 Self {
527 properties,
528 property_indices,
529 relation_types,
530 relation_type_indices,
531 languages,
532 language_indices,
533 units,
534 unit_indices,
535 objects,
536 object_indices,
537 context_ids,
538 context_id_indices,
539 contexts,
540 context_indices,
541 }
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn test_edit_new() {
551 let id = [1u8; 16];
552 let edit = Edit::new(id);
553 assert_eq!(edit.id, id);
554 assert!(edit.name.is_empty());
555 assert!(edit.authors.is_empty());
556 assert!(edit.ops.is_empty());
557 }
558
559 #[test]
560 fn test_dictionary_builder() {
561 let mut builder = DictionaryBuilder::new();
562
563 let prop1 = [1u8; 16];
564 let prop2 = [2u8; 16];
565
566 assert_eq!(builder.add_property(prop1, DataType::Text), 0);
568 assert_eq!(builder.add_property(prop1, DataType::Text), 0);
570 assert_eq!(builder.add_property(prop2, DataType::Int64), 1);
572
573 let dicts = builder.build();
574 assert_eq!(dicts.properties.len(), 2);
575 assert_eq!(dicts.properties[0], (prop1, DataType::Text));
576 assert_eq!(dicts.properties[1], (prop2, DataType::Int64));
577 }
578
579 #[test]
580 fn test_language_indexing() {
581 let mut builder = DictionaryBuilder::new();
582
583 let lang1 = [10u8; 16];
584 let lang2 = [20u8; 16];
585
586 assert_eq!(builder.add_language(None), 0);
588 assert_eq!(builder.add_language(Some(lang1)), 1);
590 assert_eq!(builder.add_language(Some(lang1)), 1);
592 assert_eq!(builder.add_language(Some(lang2)), 2);
594
595 let dicts = builder.build();
596 assert_eq!(dicts.languages.len(), 2);
597
598 assert!(dicts.get_language(0).is_none());
600 assert_eq!(dicts.get_language(1), Some(&lang1));
602 assert_eq!(dicts.get_language(2), Some(&lang2));
604 }
605}