1use crate::codec::primitives::{Reader, Writer};
6use crate::codec::value::{decode_position, decode_property_value, validate_position};
7use crate::error::{DecodeError, EncodeError};
8use crate::limits::MAX_VALUES_PER_ENTITY;
9use crate::model::{
10 CreateEntity, CreateRelation, CreateValueRef, DataType, DeleteEntity, DeleteRelation,
11 DictionaryBuilder, Op, PropertyValue, RestoreEntity, RestoreRelation,
12 UnsetLanguage, UnsetValue, UnsetRelationField, UpdateEntity, UpdateRelation, WireDictionaries,
13};
14
15const OP_CREATE_ENTITY: u8 = 1;
17const OP_UPDATE_ENTITY: u8 = 2;
18const OP_DELETE_ENTITY: u8 = 3;
19const OP_RESTORE_ENTITY: u8 = 4;
20const OP_CREATE_RELATION: u8 = 5;
21const OP_UPDATE_RELATION: u8 = 6;
22const OP_DELETE_RELATION: u8 = 7;
23const OP_RESTORE_RELATION: u8 = 8;
24const OP_CREATE_VALUE_REF: u8 = 9;
25
26const FLAG_HAS_SET_PROPERTIES: u8 = 0x01;
28const FLAG_HAS_UNSET_VALUES: u8 = 0x02;
29const UPDATE_ENTITY_RESERVED_MASK: u8 = 0xFC;
30
31const FLAG_HAS_FROM_SPACE: u8 = 0x01;
33const FLAG_HAS_FROM_VERSION: u8 = 0x02;
34const FLAG_HAS_TO_SPACE: u8 = 0x04;
35const FLAG_HAS_TO_VERSION: u8 = 0x08;
36const FLAG_HAS_ENTITY: u8 = 0x10;
37const FLAG_HAS_POSITION: u8 = 0x20;
38const FLAG_FROM_IS_VALUE_REF: u8 = 0x40;
39const FLAG_TO_IS_VALUE_REF: u8 = 0x80;
40
41const FLAG_HAS_LANGUAGE: u8 = 0x01;
43const FLAG_HAS_SPACE: u8 = 0x02;
44const CREATE_VALUE_REF_RESERVED_MASK: u8 = 0xFC;
45
46const NO_CONTEXT_REF: u32 = 0xFFFFFFFF;
48
49const UPDATE_SET_FROM_SPACE: u8 = 0x01;
51const UPDATE_SET_FROM_VERSION: u8 = 0x02;
52const UPDATE_SET_TO_SPACE: u8 = 0x04;
53const UPDATE_SET_TO_VERSION: u8 = 0x08;
54const UPDATE_SET_POSITION: u8 = 0x10;
55const UPDATE_SET_RESERVED_MASK: u8 = 0xE0;
56
57const UPDATE_UNSET_FROM_SPACE: u8 = 0x01;
59const UPDATE_UNSET_FROM_VERSION: u8 = 0x02;
60const UPDATE_UNSET_TO_SPACE: u8 = 0x04;
61const UPDATE_UNSET_TO_VERSION: u8 = 0x08;
62const UPDATE_UNSET_POSITION: u8 = 0x10;
63const UPDATE_UNSET_RESERVED_MASK: u8 = 0xE0;
64
65pub fn decode_op<'a>(reader: &mut Reader<'a>, dicts: &WireDictionaries) -> Result<Op<'a>, DecodeError> {
71 let op_type = reader.read_byte("op_type")?;
72
73 match op_type {
74 OP_CREATE_ENTITY => decode_create_entity(reader, dicts),
75 OP_UPDATE_ENTITY => decode_update_entity(reader, dicts),
76 OP_DELETE_ENTITY => decode_delete_entity(reader, dicts),
77 OP_RESTORE_ENTITY => decode_restore_entity(reader, dicts),
78 OP_CREATE_RELATION => decode_create_relation(reader, dicts),
79 OP_UPDATE_RELATION => decode_update_relation(reader, dicts),
80 OP_DELETE_RELATION => decode_delete_relation(reader, dicts),
81 OP_RESTORE_RELATION => decode_restore_relation(reader, dicts),
82 OP_CREATE_VALUE_REF => decode_create_value_ref(reader, dicts),
83 _ => Err(DecodeError::InvalidOpType { op_type }),
84 }
85}
86
87fn decode_create_entity<'a>(
88 reader: &mut Reader<'a>,
89 dicts: &WireDictionaries,
90) -> Result<Op<'a>, DecodeError> {
91 let id = reader.read_id("entity_id")?;
92 let value_count = reader.read_varint("value_count")? as usize;
93
94 if value_count > MAX_VALUES_PER_ENTITY {
95 return Err(DecodeError::LengthExceedsLimit {
96 field: "values",
97 len: value_count,
98 max: MAX_VALUES_PER_ENTITY,
99 });
100 }
101
102 let mut values = Vec::with_capacity(value_count);
103 for _ in 0..value_count {
104 values.push(decode_property_value(reader, dicts)?);
105 }
106
107 let context_ref_raw = reader.read_varint("context_ref")? as u32;
109 let context = if context_ref_raw == NO_CONTEXT_REF {
110 None
111 } else {
112 let idx = context_ref_raw as usize;
113 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
114 dict: "contexts",
115 index: idx,
116 size: dicts.contexts.len(),
117 })?.clone())
118 };
119
120 Ok(Op::CreateEntity(CreateEntity { id, values, context }))
121}
122
123fn decode_update_entity<'a>(
124 reader: &mut Reader<'a>,
125 dicts: &WireDictionaries,
126) -> Result<Op<'a>, DecodeError> {
127 let id_index = reader.read_varint("entity_id")? as usize;
128 if id_index >= dicts.objects.len() {
129 return Err(DecodeError::IndexOutOfBounds {
130 dict: "objects",
131 index: id_index,
132 size: dicts.objects.len(),
133 });
134 }
135 let id = dicts.objects[id_index];
136
137 let flags = reader.read_byte("update_flags")?;
138
139 if flags & UPDATE_ENTITY_RESERVED_MASK != 0 {
141 return Err(DecodeError::ReservedBitsSet {
142 context: "UpdateEntity flags",
143 });
144 }
145
146 let mut update = UpdateEntity::new(id);
147
148 if flags & FLAG_HAS_SET_PROPERTIES != 0 {
149 let count = reader.read_varint("set_properties_count")? as usize;
150 if count > MAX_VALUES_PER_ENTITY {
151 return Err(DecodeError::LengthExceedsLimit {
152 field: "set_properties",
153 len: count,
154 max: MAX_VALUES_PER_ENTITY,
155 });
156 }
157 for _ in 0..count {
158 update.set_properties.push(decode_property_value(reader, dicts)?);
159 }
160 }
161
162 if flags & FLAG_HAS_UNSET_VALUES != 0 {
163 let count = reader.read_varint("unset_values_count")? as usize;
164 if count > MAX_VALUES_PER_ENTITY {
165 return Err(DecodeError::LengthExceedsLimit {
166 field: "unset_values",
167 len: count,
168 max: MAX_VALUES_PER_ENTITY,
169 });
170 }
171 for _ in 0..count {
172 let prop_index = reader.read_varint("property")? as usize;
173 if prop_index >= dicts.properties.len() {
174 return Err(DecodeError::IndexOutOfBounds {
175 dict: "properties",
176 index: prop_index,
177 size: dicts.properties.len(),
178 });
179 }
180 let property = dicts.properties[prop_index].0;
181
182 let lang_value = reader.read_varint("unset.language")? as u32;
184 let language = if lang_value == 0xFFFFFFFF {
185 UnsetLanguage::All
186 } else if lang_value == 0 {
187 UnsetLanguage::English
188 } else {
189 let idx = (lang_value - 1) as usize;
190 if idx >= dicts.languages.len() {
191 return Err(DecodeError::IndexOutOfBounds {
192 dict: "languages",
193 index: lang_value as usize,
194 size: dicts.languages.len() + 1,
195 });
196 }
197 UnsetLanguage::Specific(dicts.languages[idx])
198 };
199
200 update.unset_values.push(UnsetValue { property, language });
201 }
202 }
203
204 let context_ref_raw = reader.read_varint("context_ref")? as u32;
206 update.context = if context_ref_raw == NO_CONTEXT_REF {
207 None
208 } else {
209 let idx = context_ref_raw as usize;
210 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
211 dict: "contexts",
212 index: idx,
213 size: dicts.contexts.len(),
214 })?.clone())
215 };
216
217 Ok(Op::UpdateEntity(update))
218}
219
220fn decode_delete_entity<'a>(
221 reader: &mut Reader<'a>,
222 dicts: &WireDictionaries,
223) -> Result<Op<'a>, DecodeError> {
224 let id_index = reader.read_varint("entity_id")? as usize;
225 if id_index >= dicts.objects.len() {
226 return Err(DecodeError::IndexOutOfBounds {
227 dict: "objects",
228 index: id_index,
229 size: dicts.objects.len(),
230 });
231 }
232 let id = dicts.objects[id_index];
233
234 let context_ref_raw = reader.read_varint("context_ref")? as u32;
236 let context = if context_ref_raw == NO_CONTEXT_REF {
237 None
238 } else {
239 let idx = context_ref_raw as usize;
240 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
241 dict: "contexts",
242 index: idx,
243 size: dicts.contexts.len(),
244 })?.clone())
245 };
246
247 Ok(Op::DeleteEntity(DeleteEntity { id, context }))
248}
249
250fn decode_restore_entity<'a>(
251 reader: &mut Reader<'a>,
252 dicts: &WireDictionaries,
253) -> Result<Op<'a>, DecodeError> {
254 let id_index = reader.read_varint("entity_id")? as usize;
255 if id_index >= dicts.objects.len() {
256 return Err(DecodeError::IndexOutOfBounds {
257 dict: "objects",
258 index: id_index,
259 size: dicts.objects.len(),
260 });
261 }
262 let id = dicts.objects[id_index];
263
264 let context_ref_raw = reader.read_varint("context_ref")? as u32;
266 let context = if context_ref_raw == NO_CONTEXT_REF {
267 None
268 } else {
269 let idx = context_ref_raw as usize;
270 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
271 dict: "contexts",
272 index: idx,
273 size: dicts.contexts.len(),
274 })?.clone())
275 };
276
277 Ok(Op::RestoreEntity(RestoreEntity { id, context }))
278}
279
280fn decode_create_relation<'a>(
281 reader: &mut Reader<'a>,
282 dicts: &WireDictionaries,
283) -> Result<Op<'a>, DecodeError> {
284 let id = reader.read_id("relation_id")?;
285
286 let type_index = reader.read_varint("relation_type")? as usize;
287 if type_index >= dicts.relation_types.len() {
288 return Err(DecodeError::IndexOutOfBounds {
289 dict: "relation_types",
290 index: type_index,
291 size: dicts.relation_types.len(),
292 });
293 }
294 let relation_type = dicts.relation_types[type_index];
295
296 let flags = reader.read_byte("relation_flags")?;
297 let from_is_value_ref = flags & FLAG_FROM_IS_VALUE_REF != 0;
298 let to_is_value_ref = flags & FLAG_TO_IS_VALUE_REF != 0;
299
300 let from = if from_is_value_ref {
302 reader.read_id("from")?
303 } else {
304 let from_index = reader.read_varint("from")? as usize;
305 if from_index >= dicts.objects.len() {
306 return Err(DecodeError::IndexOutOfBounds {
307 dict: "objects",
308 index: from_index,
309 size: dicts.objects.len(),
310 });
311 }
312 dicts.objects[from_index]
313 };
314
315 let to = if to_is_value_ref {
317 reader.read_id("to")?
318 } else {
319 let to_index = reader.read_varint("to")? as usize;
320 if to_index >= dicts.objects.len() {
321 return Err(DecodeError::IndexOutOfBounds {
322 dict: "objects",
323 index: to_index,
324 size: dicts.objects.len(),
325 });
326 }
327 dicts.objects[to_index]
328 };
329
330 let from_space = if flags & FLAG_HAS_FROM_SPACE != 0 {
332 Some(reader.read_id("from_space")?)
333 } else {
334 None
335 };
336
337 let from_version = if flags & FLAG_HAS_FROM_VERSION != 0 {
338 Some(reader.read_id("from_version")?)
339 } else {
340 None
341 };
342
343 let to_space = if flags & FLAG_HAS_TO_SPACE != 0 {
344 Some(reader.read_id("to_space")?)
345 } else {
346 None
347 };
348
349 let to_version = if flags & FLAG_HAS_TO_VERSION != 0 {
350 Some(reader.read_id("to_version")?)
351 } else {
352 None
353 };
354
355 let entity = if flags & FLAG_HAS_ENTITY != 0 {
356 Some(reader.read_id("entity_id")?)
357 } else {
358 None
359 };
360
361 let position = if flags & FLAG_HAS_POSITION != 0 {
362 Some(decode_position(reader)?)
363 } else {
364 None
365 };
366
367 let context_ref_raw = reader.read_varint("context_ref")? as u32;
369 let context = if context_ref_raw == NO_CONTEXT_REF {
370 None
371 } else {
372 let idx = context_ref_raw as usize;
373 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
374 dict: "contexts",
375 index: idx,
376 size: dicts.contexts.len(),
377 })?.clone())
378 };
379
380 Ok(Op::CreateRelation(CreateRelation {
381 id,
382 relation_type,
383 from,
384 from_is_value_ref,
385 to,
386 to_is_value_ref,
387 entity,
388 position,
389 from_space,
390 from_version,
391 to_space,
392 to_version,
393 context,
394 }))
395}
396
397fn decode_update_relation<'a>(
398 reader: &mut Reader<'a>,
399 dicts: &WireDictionaries,
400) -> Result<Op<'a>, DecodeError> {
401 let id_index = reader.read_varint("relation_id")? as usize;
402 if id_index >= dicts.objects.len() {
403 return Err(DecodeError::IndexOutOfBounds {
404 dict: "objects",
405 index: id_index,
406 size: dicts.objects.len(),
407 });
408 }
409 let id = dicts.objects[id_index];
410
411 let set_flags = reader.read_byte("set_flags")?;
412 let unset_flags = reader.read_byte("unset_flags")?;
413
414 if set_flags & UPDATE_SET_RESERVED_MASK != 0 {
416 return Err(DecodeError::ReservedBitsSet {
417 context: "UpdateRelation set_flags",
418 });
419 }
420 if unset_flags & UPDATE_UNSET_RESERVED_MASK != 0 {
421 return Err(DecodeError::ReservedBitsSet {
422 context: "UpdateRelation unset_flags",
423 });
424 }
425
426 let from_space = if set_flags & UPDATE_SET_FROM_SPACE != 0 {
428 Some(reader.read_id("from_space")?)
429 } else {
430 None
431 };
432
433 let from_version = if set_flags & UPDATE_SET_FROM_VERSION != 0 {
434 Some(reader.read_id("from_version")?)
435 } else {
436 None
437 };
438
439 let to_space = if set_flags & UPDATE_SET_TO_SPACE != 0 {
440 Some(reader.read_id("to_space")?)
441 } else {
442 None
443 };
444
445 let to_version = if set_flags & UPDATE_SET_TO_VERSION != 0 {
446 Some(reader.read_id("to_version")?)
447 } else {
448 None
449 };
450
451 let position = if set_flags & UPDATE_SET_POSITION != 0 {
452 Some(decode_position(reader)?)
453 } else {
454 None
455 };
456
457 let mut unset = Vec::new();
459 if unset_flags & UPDATE_UNSET_FROM_SPACE != 0 {
460 unset.push(UnsetRelationField::FromSpace);
461 }
462 if unset_flags & UPDATE_UNSET_FROM_VERSION != 0 {
463 unset.push(UnsetRelationField::FromVersion);
464 }
465 if unset_flags & UPDATE_UNSET_TO_SPACE != 0 {
466 unset.push(UnsetRelationField::ToSpace);
467 }
468 if unset_flags & UPDATE_UNSET_TO_VERSION != 0 {
469 unset.push(UnsetRelationField::ToVersion);
470 }
471 if unset_flags & UPDATE_UNSET_POSITION != 0 {
472 unset.push(UnsetRelationField::Position);
473 }
474
475 let context_ref_raw = reader.read_varint("context_ref")? as u32;
477 let context = if context_ref_raw == NO_CONTEXT_REF {
478 None
479 } else {
480 let idx = context_ref_raw as usize;
481 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
482 dict: "contexts",
483 index: idx,
484 size: dicts.contexts.len(),
485 })?.clone())
486 };
487
488 Ok(Op::UpdateRelation(UpdateRelation {
489 id,
490 from_space,
491 from_version,
492 to_space,
493 to_version,
494 position,
495 unset,
496 context,
497 }))
498}
499
500fn decode_delete_relation<'a>(
501 reader: &mut Reader<'a>,
502 dicts: &WireDictionaries,
503) -> Result<Op<'a>, DecodeError> {
504 let id_index = reader.read_varint("relation_id")? as usize;
505 if id_index >= dicts.objects.len() {
506 return Err(DecodeError::IndexOutOfBounds {
507 dict: "objects",
508 index: id_index,
509 size: dicts.objects.len(),
510 });
511 }
512 let id = dicts.objects[id_index];
513
514 let context_ref_raw = reader.read_varint("context_ref")? as u32;
516 let context = if context_ref_raw == NO_CONTEXT_REF {
517 None
518 } else {
519 let idx = context_ref_raw as usize;
520 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
521 dict: "contexts",
522 index: idx,
523 size: dicts.contexts.len(),
524 })?.clone())
525 };
526
527 Ok(Op::DeleteRelation(DeleteRelation { id, context }))
528}
529
530fn decode_restore_relation<'a>(
531 reader: &mut Reader<'a>,
532 dicts: &WireDictionaries,
533) -> Result<Op<'a>, DecodeError> {
534 let id_index = reader.read_varint("relation_id")? as usize;
535 if id_index >= dicts.objects.len() {
536 return Err(DecodeError::IndexOutOfBounds {
537 dict: "objects",
538 index: id_index,
539 size: dicts.objects.len(),
540 });
541 }
542 let id = dicts.objects[id_index];
543
544 let context_ref_raw = reader.read_varint("context_ref")? as u32;
546 let context = if context_ref_raw == NO_CONTEXT_REF {
547 None
548 } else {
549 let idx = context_ref_raw as usize;
550 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
551 dict: "contexts",
552 index: idx,
553 size: dicts.contexts.len(),
554 })?.clone())
555 };
556
557 Ok(Op::RestoreRelation(RestoreRelation { id, context }))
558}
559
560fn decode_create_value_ref<'a>(
561 reader: &mut Reader<'a>,
562 dicts: &WireDictionaries,
563) -> Result<Op<'a>, DecodeError> {
564 let id = reader.read_id("value_ref_id")?;
565
566 let entity_index = reader.read_varint("entity")? as usize;
567 if entity_index >= dicts.objects.len() {
568 return Err(DecodeError::IndexOutOfBounds {
569 dict: "objects",
570 index: entity_index,
571 size: dicts.objects.len(),
572 });
573 }
574 let entity = dicts.objects[entity_index];
575
576 let property_index = reader.read_varint("property")? as usize;
577 if property_index >= dicts.properties.len() {
578 return Err(DecodeError::IndexOutOfBounds {
579 dict: "properties",
580 index: property_index,
581 size: dicts.properties.len(),
582 });
583 }
584 let property = dicts.properties[property_index].0;
585 let data_type = dicts.properties[property_index].1;
586
587 let flags = reader.read_byte("value_ref_flags")?;
588
589 if flags & CREATE_VALUE_REF_RESERVED_MASK != 0 {
591 return Err(DecodeError::ReservedBitsSet {
592 context: "CreateValueRef flags",
593 });
594 }
595
596 let language = if flags & FLAG_HAS_LANGUAGE != 0 {
597 if data_type != DataType::Text {
599 return Err(DecodeError::MalformedEncoding {
600 context: "CreateValueRef has_language=1 but property DataType is not TEXT",
601 });
602 }
603 let lang_index = reader.read_varint("language")? as usize;
604 if lang_index == 0 {
606 None } else {
608 let idx = lang_index - 1;
609 if idx >= dicts.languages.len() {
610 return Err(DecodeError::IndexOutOfBounds {
611 dict: "languages",
612 index: lang_index,
613 size: dicts.languages.len() + 1,
614 });
615 }
616 Some(dicts.languages[idx])
617 }
618 } else {
619 None
620 };
621
622 let space = if flags & FLAG_HAS_SPACE != 0 {
623 Some(reader.read_id("space")?)
624 } else {
625 None
626 };
627
628 Ok(Op::CreateValueRef(CreateValueRef {
629 id,
630 entity,
631 property,
632 language,
633 space,
634 }))
635}
636
637pub fn encode_op(
646 writer: &mut Writer,
647 op: &Op<'_>,
648 dict_builder: &mut DictionaryBuilder,
649 property_types: &rustc_hash::FxHashMap<crate::model::Id, DataType>,
650) -> Result<(), EncodeError> {
651 match op {
652 Op::CreateEntity(ce) => encode_create_entity(writer, ce, dict_builder, property_types),
653 Op::UpdateEntity(ue) => encode_update_entity(writer, ue, dict_builder, property_types),
654 Op::DeleteEntity(de) => encode_delete_entity(writer, de, dict_builder),
655 Op::RestoreEntity(re) => encode_restore_entity(writer, re, dict_builder),
656 Op::CreateRelation(cr) => encode_create_relation(writer, cr, dict_builder),
657 Op::UpdateRelation(ur) => encode_update_relation(writer, ur, dict_builder),
658 Op::DeleteRelation(dr) => encode_delete_relation(writer, dr, dict_builder),
659 Op::RestoreRelation(rr) => encode_restore_relation(writer, rr, dict_builder),
660 Op::CreateValueRef(cvr) => encode_create_value_ref(writer, cvr, dict_builder),
661 }
662}
663
664fn encode_create_entity(
665 writer: &mut Writer,
666 ce: &CreateEntity<'_>,
667 dict_builder: &mut DictionaryBuilder,
668 property_types: &rustc_hash::FxHashMap<crate::model::Id, DataType>,
669) -> Result<(), EncodeError> {
670 writer.write_byte(OP_CREATE_ENTITY);
671 writer.write_id(&ce.id);
672 writer.write_varint(ce.values.len() as u64);
673
674 for pv in &ce.values {
675 let data_type = property_types.get(&pv.property)
676 .copied()
677 .unwrap_or_else(|| pv.value.data_type());
678 encode_property_value(writer, pv, dict_builder, data_type)?;
679 }
680
681 let context_ref = match &ce.context {
683 Some(ctx) => dict_builder.add_context(ctx) as u32,
684 None => NO_CONTEXT_REF,
685 };
686 writer.write_varint(context_ref as u64);
687
688 Ok(())
689}
690
691fn encode_update_entity(
692 writer: &mut Writer,
693 ue: &UpdateEntity<'_>,
694 dict_builder: &mut DictionaryBuilder,
695 property_types: &rustc_hash::FxHashMap<crate::model::Id, DataType>,
696) -> Result<(), EncodeError> {
697 writer.write_byte(OP_UPDATE_ENTITY);
698
699 let id_index = dict_builder.add_object(ue.id);
700 writer.write_varint(id_index as u64);
701
702 let mut flags = 0u8;
703 if !ue.set_properties.is_empty() {
704 flags |= FLAG_HAS_SET_PROPERTIES;
705 }
706 if !ue.unset_values.is_empty() {
707 flags |= FLAG_HAS_UNSET_VALUES;
708 }
709 writer.write_byte(flags);
710
711 if !ue.set_properties.is_empty() {
712 writer.write_varint(ue.set_properties.len() as u64);
713 for pv in &ue.set_properties {
714 let data_type = property_types.get(&pv.property)
715 .copied()
716 .unwrap_or_else(|| pv.value.data_type());
717 encode_property_value(writer, pv, dict_builder, data_type)?;
718 }
719 }
720
721 if !ue.unset_values.is_empty() {
722 writer.write_varint(ue.unset_values.len() as u64);
723 for unset in &ue.unset_values {
724 let idx = dict_builder.add_property(unset.property, DataType::Bool);
726 writer.write_varint(idx as u64);
727 let lang_value: u32 = match &unset.language {
729 UnsetLanguage::All => 0xFFFFFFFF,
730 UnsetLanguage::English => 0,
731 UnsetLanguage::Specific(lang_id) => {
732 let lang_index = dict_builder.add_language(Some(*lang_id));
733 lang_index as u32
734 }
735 };
736 writer.write_varint(lang_value as u64);
737 }
738 }
739
740 let context_ref = match &ue.context {
742 Some(ctx) => dict_builder.add_context(ctx) as u32,
743 None => NO_CONTEXT_REF,
744 };
745 writer.write_varint(context_ref as u64);
746
747 Ok(())
748}
749
750fn encode_delete_entity(
751 writer: &mut Writer,
752 de: &DeleteEntity,
753 dict_builder: &mut DictionaryBuilder,
754) -> Result<(), EncodeError> {
755 writer.write_byte(OP_DELETE_ENTITY);
756 let id_index = dict_builder.add_object(de.id);
757 writer.write_varint(id_index as u64);
758
759 let context_ref = match &de.context {
761 Some(ctx) => dict_builder.add_context(ctx) as u32,
762 None => NO_CONTEXT_REF,
763 };
764 writer.write_varint(context_ref as u64);
765
766 Ok(())
767}
768
769fn encode_restore_entity(
770 writer: &mut Writer,
771 re: &RestoreEntity,
772 dict_builder: &mut DictionaryBuilder,
773) -> Result<(), EncodeError> {
774 writer.write_byte(OP_RESTORE_ENTITY);
775 let id_index = dict_builder.add_object(re.id);
776 writer.write_varint(id_index as u64);
777
778 let context_ref = match &re.context {
780 Some(ctx) => dict_builder.add_context(ctx) as u32,
781 None => NO_CONTEXT_REF,
782 };
783 writer.write_varint(context_ref as u64);
784
785 Ok(())
786}
787
788fn encode_create_relation(
789 writer: &mut Writer,
790 cr: &CreateRelation<'_>,
791 dict_builder: &mut DictionaryBuilder,
792) -> Result<(), EncodeError> {
793 writer.write_byte(OP_CREATE_RELATION);
794 writer.write_id(&cr.id);
795
796 let type_index = dict_builder.add_relation_type(cr.relation_type);
797 writer.write_varint(type_index as u64);
798
799 let mut flags = 0u8;
801 if cr.from_space.is_some() {
802 flags |= FLAG_HAS_FROM_SPACE;
803 }
804 if cr.from_version.is_some() {
805 flags |= FLAG_HAS_FROM_VERSION;
806 }
807 if cr.to_space.is_some() {
808 flags |= FLAG_HAS_TO_SPACE;
809 }
810 if cr.to_version.is_some() {
811 flags |= FLAG_HAS_TO_VERSION;
812 }
813 if cr.entity.is_some() {
814 flags |= FLAG_HAS_ENTITY;
815 }
816 if cr.position.is_some() {
817 flags |= FLAG_HAS_POSITION;
818 }
819 if cr.from_is_value_ref {
820 flags |= FLAG_FROM_IS_VALUE_REF;
821 }
822 if cr.to_is_value_ref {
823 flags |= FLAG_TO_IS_VALUE_REF;
824 }
825 writer.write_byte(flags);
826
827 if cr.from_is_value_ref {
829 writer.write_id(&cr.from);
830 } else {
831 let from_index = dict_builder.add_object(cr.from);
832 writer.write_varint(from_index as u64);
833 }
834
835 if cr.to_is_value_ref {
837 writer.write_id(&cr.to);
838 } else {
839 let to_index = dict_builder.add_object(cr.to);
840 writer.write_varint(to_index as u64);
841 }
842
843 if let Some(space) = &cr.from_space {
845 writer.write_id(space);
846 }
847
848 if let Some(version) = &cr.from_version {
849 writer.write_id(version);
850 }
851
852 if let Some(space) = &cr.to_space {
853 writer.write_id(space);
854 }
855
856 if let Some(version) = &cr.to_version {
857 writer.write_id(version);
858 }
859
860 if let Some(entity) = &cr.entity {
861 writer.write_id(entity);
862 }
863
864 if let Some(pos) = &cr.position {
865 validate_position(pos)?;
866 writer.write_string(pos);
867 }
868
869 let context_ref = match &cr.context {
871 Some(ctx) => dict_builder.add_context(ctx) as u32,
872 None => NO_CONTEXT_REF,
873 };
874 writer.write_varint(context_ref as u64);
875
876 Ok(())
877}
878
879fn encode_update_relation(
880 writer: &mut Writer,
881 ur: &UpdateRelation<'_>,
882 dict_builder: &mut DictionaryBuilder,
883) -> Result<(), EncodeError> {
884 writer.write_byte(OP_UPDATE_RELATION);
885
886 let id_index = dict_builder.add_object(ur.id);
887 writer.write_varint(id_index as u64);
888
889 let mut set_flags = 0u8;
891 if ur.from_space.is_some() {
892 set_flags |= UPDATE_SET_FROM_SPACE;
893 }
894 if ur.from_version.is_some() {
895 set_flags |= UPDATE_SET_FROM_VERSION;
896 }
897 if ur.to_space.is_some() {
898 set_flags |= UPDATE_SET_TO_SPACE;
899 }
900 if ur.to_version.is_some() {
901 set_flags |= UPDATE_SET_TO_VERSION;
902 }
903 if ur.position.is_some() {
904 set_flags |= UPDATE_SET_POSITION;
905 }
906 writer.write_byte(set_flags);
907
908 let mut unset_flags = 0u8;
910 for field in &ur.unset {
911 match field {
912 UnsetRelationField::FromSpace => unset_flags |= UPDATE_UNSET_FROM_SPACE,
913 UnsetRelationField::FromVersion => unset_flags |= UPDATE_UNSET_FROM_VERSION,
914 UnsetRelationField::ToSpace => unset_flags |= UPDATE_UNSET_TO_SPACE,
915 UnsetRelationField::ToVersion => unset_flags |= UPDATE_UNSET_TO_VERSION,
916 UnsetRelationField::Position => unset_flags |= UPDATE_UNSET_POSITION,
917 }
918 }
919 writer.write_byte(unset_flags);
920
921 if let Some(space) = &ur.from_space {
923 writer.write_id(space);
924 }
925 if let Some(version) = &ur.from_version {
926 writer.write_id(version);
927 }
928 if let Some(space) = &ur.to_space {
929 writer.write_id(space);
930 }
931 if let Some(version) = &ur.to_version {
932 writer.write_id(version);
933 }
934 if let Some(pos) = &ur.position {
935 validate_position(pos)?;
936 writer.write_string(pos);
937 }
938
939 let context_ref = match &ur.context {
941 Some(ctx) => dict_builder.add_context(ctx) as u32,
942 None => NO_CONTEXT_REF,
943 };
944 writer.write_varint(context_ref as u64);
945
946 Ok(())
947}
948
949fn encode_delete_relation(
950 writer: &mut Writer,
951 dr: &DeleteRelation,
952 dict_builder: &mut DictionaryBuilder,
953) -> Result<(), EncodeError> {
954 writer.write_byte(OP_DELETE_RELATION);
955 let id_index = dict_builder.add_object(dr.id);
956 writer.write_varint(id_index as u64);
957
958 let context_ref = match &dr.context {
960 Some(ctx) => dict_builder.add_context(ctx) as u32,
961 None => NO_CONTEXT_REF,
962 };
963 writer.write_varint(context_ref as u64);
964
965 Ok(())
966}
967
968fn encode_restore_relation(
969 writer: &mut Writer,
970 rr: &RestoreRelation,
971 dict_builder: &mut DictionaryBuilder,
972) -> Result<(), EncodeError> {
973 writer.write_byte(OP_RESTORE_RELATION);
974 let id_index = dict_builder.add_object(rr.id);
975 writer.write_varint(id_index as u64);
976
977 let context_ref = match &rr.context {
979 Some(ctx) => dict_builder.add_context(ctx) as u32,
980 None => NO_CONTEXT_REF,
981 };
982 writer.write_varint(context_ref as u64);
983
984 Ok(())
985}
986
987fn encode_create_value_ref(
988 writer: &mut Writer,
989 cvr: &CreateValueRef,
990 dict_builder: &mut DictionaryBuilder,
991) -> Result<(), EncodeError> {
992 writer.write_byte(OP_CREATE_VALUE_REF);
993 writer.write_id(&cvr.id);
994
995 let entity_index = dict_builder.add_object(cvr.entity);
996 writer.write_varint(entity_index as u64);
997
998 let data_type = if cvr.language.is_some() { DataType::Text } else { DataType::Bool };
1002 let property_index = dict_builder.add_property(cvr.property, data_type);
1003 writer.write_varint(property_index as u64);
1004
1005 let mut flags = 0u8;
1006 if cvr.language.is_some() {
1007 flags |= FLAG_HAS_LANGUAGE;
1008 }
1009 if cvr.space.is_some() {
1010 flags |= FLAG_HAS_SPACE;
1011 }
1012 writer.write_byte(flags);
1013
1014 if let Some(lang_id) = cvr.language {
1015 let lang_index = dict_builder.add_language(Some(lang_id));
1016 writer.write_varint(lang_index as u64);
1017 }
1018
1019 if let Some(space) = &cvr.space {
1020 writer.write_id(space);
1021 }
1022
1023 Ok(())
1024}
1025
1026fn encode_property_value(
1027 writer: &mut Writer,
1028 pv: &PropertyValue<'_>,
1029 dict_builder: &mut DictionaryBuilder,
1030 data_type: DataType,
1031) -> Result<(), EncodeError> {
1032 let prop_index = dict_builder.add_property(pv.property, data_type);
1033 writer.write_varint(prop_index as u64);
1034 crate::codec::value::encode_value(writer, &pv.value, dict_builder)?;
1035 Ok(())
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040 use std::borrow::Cow;
1041
1042 use super::*;
1043 use crate::model::Value;
1044
1045 #[test]
1046 fn test_create_entity_roundtrip() {
1047 let op = Op::CreateEntity(CreateEntity {
1048 id: [1u8; 16],
1049 values: vec![PropertyValue {
1050 property: [2u8; 16],
1051 value: Value::Text {
1052 value: Cow::Owned("test".to_string()),
1053 language: None,
1054 },
1055 }],
1056 context: None,
1057 });
1058
1059 let mut dict_builder = DictionaryBuilder::new();
1060 let mut property_types = rustc_hash::FxHashMap::default();
1061 property_types.insert([2u8; 16], DataType::Text);
1062
1063 let mut writer = Writer::new();
1064 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1065
1066 let dicts = dict_builder.build();
1067 let mut reader = Reader::new(writer.as_bytes());
1068 let decoded = decode_op(&mut reader, &dicts).unwrap();
1069
1070 match (&op, &decoded) {
1072 (Op::CreateEntity(e1), Op::CreateEntity(e2)) => {
1073 assert_eq!(e1.id, e2.id);
1074 assert_eq!(e1.values.len(), e2.values.len());
1075 for (v1, v2) in e1.values.iter().zip(e2.values.iter()) {
1076 assert_eq!(v1.property, v2.property);
1077 match (&v1.value, &v2.value) {
1078 (Value::Text { value: s1, language: l1 }, Value::Text { value: s2, language: l2 }) => {
1079 assert_eq!(s1.as_ref(), s2.as_ref());
1080 assert_eq!(l1, l2);
1081 }
1082 _ => panic!("expected Text values"),
1083 }
1084 }
1085 }
1086 _ => panic!("expected CreateEntity"),
1087 }
1088 }
1089
1090 #[test]
1091 fn test_create_relation_roundtrip() {
1092 let op = Op::CreateRelation(CreateRelation {
1094 id: [10u8; 16],
1095 relation_type: [1u8; 16],
1096 from: [2u8; 16],
1097 from_is_value_ref: false,
1098 to: [3u8; 16],
1099 to_is_value_ref: false,
1100 entity: Some([4u8; 16]),
1101 position: Some(Cow::Owned("abc".to_string())),
1102 from_space: None,
1103 from_version: None,
1104 to_space: None,
1105 to_version: None,
1106 context: None,
1107 });
1108
1109 let mut dict_builder = DictionaryBuilder::new();
1110 let property_types = rustc_hash::FxHashMap::default();
1111
1112 let mut writer = Writer::new();
1113 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1114
1115 let dicts = dict_builder.build();
1116 let mut reader = Reader::new(writer.as_bytes());
1117 let decoded = decode_op(&mut reader, &dicts).unwrap();
1118
1119 match (&op, &decoded) {
1121 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1122 assert_eq!(r1.id, r2.id);
1123 assert_eq!(r1.relation_type, r2.relation_type);
1124 assert_eq!(r1.from, r2.from);
1125 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1126 assert_eq!(r1.to, r2.to);
1127 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1128 assert_eq!(r1.entity, r2.entity);
1129 match (&r1.position, &r2.position) {
1130 (Some(p1), Some(p2)) => assert_eq!(p1.as_ref(), p2.as_ref()),
1131 (None, None) => {}
1132 _ => panic!("position mismatch"),
1133 }
1134 }
1135 _ => panic!("expected CreateRelation"),
1136 }
1137 }
1138
1139 #[test]
1140 fn test_create_relation_auto_entity_roundtrip() {
1141 let op = Op::CreateRelation(CreateRelation {
1143 id: [10u8; 16],
1144 relation_type: [1u8; 16],
1145 from: [2u8; 16],
1146 from_is_value_ref: false,
1147 to: [3u8; 16],
1148 to_is_value_ref: false,
1149 entity: None,
1150 position: Some(Cow::Owned("abc".to_string())),
1151 from_space: None,
1152 from_version: None,
1153 to_space: None,
1154 to_version: None,
1155 context: None,
1156 });
1157
1158 let mut dict_builder = DictionaryBuilder::new();
1159 let property_types = rustc_hash::FxHashMap::default();
1160
1161 let mut writer = Writer::new();
1162 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1163
1164 let dicts = dict_builder.build();
1165 let mut reader = Reader::new(writer.as_bytes());
1166 let decoded = decode_op(&mut reader, &dicts).unwrap();
1167
1168 match (&op, &decoded) {
1169 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1170 assert_eq!(r1.id, r2.id);
1171 assert_eq!(r1.relation_type, r2.relation_type);
1172 assert_eq!(r1.from, r2.from);
1173 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1174 assert_eq!(r1.to, r2.to);
1175 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1176 assert_eq!(r1.entity, r2.entity);
1177 assert!(r1.entity.is_none());
1178 assert!(r2.entity.is_none());
1179 }
1180 _ => panic!("expected CreateRelation"),
1181 }
1182 }
1183
1184 #[test]
1185 fn test_create_relation_with_versions() {
1186 let op = Op::CreateRelation(CreateRelation {
1187 id: [10u8; 16],
1188 relation_type: [1u8; 16],
1189 from: [2u8; 16],
1190 from_is_value_ref: false,
1191 to: [3u8; 16],
1192 to_is_value_ref: false,
1193 entity: Some([4u8; 16]),
1194 position: Some(Cow::Owned("abc".to_string())),
1195 from_space: Some([5u8; 16]),
1196 from_version: Some([6u8; 16]),
1197 to_space: Some([7u8; 16]),
1198 to_version: Some([8u8; 16]),
1199 context: None,
1200 });
1201
1202 let mut dict_builder = DictionaryBuilder::new();
1203 let property_types = rustc_hash::FxHashMap::default();
1204
1205 let mut writer = Writer::new();
1206 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1207
1208 let dicts = dict_builder.build();
1209 let mut reader = Reader::new(writer.as_bytes());
1210 let decoded = decode_op(&mut reader, &dicts).unwrap();
1211
1212 match (&op, &decoded) {
1213 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1214 assert_eq!(r1.id, r2.id);
1215 assert_eq!(r1.relation_type, r2.relation_type);
1216 assert_eq!(r1.from, r2.from);
1217 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1218 assert_eq!(r1.to, r2.to);
1219 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1220 assert_eq!(r1.entity, r2.entity);
1221 assert_eq!(r1.from_space, r2.from_space);
1222 assert_eq!(r1.from_version, r2.from_version);
1223 assert_eq!(r1.to_space, r2.to_space);
1224 assert_eq!(r1.to_version, r2.to_version);
1225 }
1226 _ => panic!("expected CreateRelation"),
1227 }
1228 }
1229
1230 #[test]
1231 fn test_create_relation_with_value_ref_endpoint() {
1232 let op = Op::CreateRelation(CreateRelation {
1234 id: [10u8; 16],
1235 relation_type: [1u8; 16],
1236 from: [2u8; 16],
1237 from_is_value_ref: false,
1238 to: [99u8; 16], to_is_value_ref: true,
1240 entity: None,
1241 position: None,
1242 from_space: None,
1243 from_version: None,
1244 to_space: None,
1245 to_version: None,
1246 context: None,
1247 });
1248
1249 let mut dict_builder = DictionaryBuilder::new();
1250 let property_types = rustc_hash::FxHashMap::default();
1251
1252 let mut writer = Writer::new();
1253 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1254
1255 let dicts = dict_builder.build();
1256 let mut reader = Reader::new(writer.as_bytes());
1257 let decoded = decode_op(&mut reader, &dicts).unwrap();
1258
1259 match (&op, &decoded) {
1260 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1261 assert_eq!(r1.id, r2.id);
1262 assert_eq!(r1.from, r2.from);
1263 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1264 assert!(!r1.from_is_value_ref);
1265 assert_eq!(r1.to, r2.to);
1266 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1267 assert!(r1.to_is_value_ref);
1268 }
1269 _ => panic!("expected CreateRelation"),
1270 }
1271 }
1272
1273 #[test]
1274 fn test_create_value_ref_roundtrip() {
1275 let op = Op::CreateValueRef(CreateValueRef {
1276 id: [1u8; 16],
1277 entity: [2u8; 16],
1278 property: [3u8; 16],
1279 language: None,
1280 space: None,
1281 });
1282
1283 let mut dict_builder = DictionaryBuilder::new();
1284 let property_types = rustc_hash::FxHashMap::default();
1285
1286 let mut writer = Writer::new();
1287 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1288
1289 let dicts = dict_builder.build();
1290 let mut reader = Reader::new(writer.as_bytes());
1291 let decoded = decode_op(&mut reader, &dicts).unwrap();
1292
1293 match (&op, &decoded) {
1294 (Op::CreateValueRef(v1), Op::CreateValueRef(v2)) => {
1295 assert_eq!(v1.id, v2.id);
1296 assert_eq!(v1.entity, v2.entity);
1297 assert_eq!(v1.property, v2.property);
1298 assert_eq!(v1.language, v2.language);
1299 assert_eq!(v1.space, v2.space);
1300 }
1301 _ => panic!("expected CreateValueRef"),
1302 }
1303 }
1304
1305 #[test]
1306 fn test_create_value_ref_with_language_and_space() {
1307 let op = Op::CreateValueRef(CreateValueRef {
1308 id: [1u8; 16],
1309 entity: [2u8; 16],
1310 property: [3u8; 16],
1311 language: Some([4u8; 16]),
1312 space: Some([5u8; 16]),
1313 });
1314
1315 let mut dict_builder = DictionaryBuilder::new();
1316 let property_types = rustc_hash::FxHashMap::default();
1317
1318 let mut writer = Writer::new();
1319 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1320
1321 let dicts = dict_builder.build();
1322 let mut reader = Reader::new(writer.as_bytes());
1323 let decoded = decode_op(&mut reader, &dicts).unwrap();
1324
1325 match (&op, &decoded) {
1326 (Op::CreateValueRef(v1), Op::CreateValueRef(v2)) => {
1327 assert_eq!(v1.id, v2.id);
1328 assert_eq!(v1.entity, v2.entity);
1329 assert_eq!(v1.property, v2.property);
1330 assert_eq!(v1.language, v2.language);
1331 assert_eq!(v1.space, v2.space);
1332 }
1333 _ => panic!("expected CreateValueRef"),
1334 }
1335 }
1336
1337 #[test]
1338 fn test_update_relation_roundtrip() {
1339 let op = Op::UpdateRelation(UpdateRelation {
1341 id: [1u8; 16],
1342 from_space: Some([2u8; 16]),
1343 from_version: Some([3u8; 16]),
1344 to_space: Some([4u8; 16]),
1345 to_version: Some([5u8; 16]),
1346 position: Some(Cow::Owned("xyz".to_string())),
1347 unset: vec![],
1348 context: None,
1349 });
1350
1351 let mut dict_builder = DictionaryBuilder::new();
1352 dict_builder.add_object([1u8; 16]); let property_types = rustc_hash::FxHashMap::default();
1354
1355 let mut writer = Writer::new();
1356 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1357
1358 let dicts = dict_builder.build();
1359 let mut reader = Reader::new(writer.as_bytes());
1360 let decoded = decode_op(&mut reader, &dicts).unwrap();
1361
1362 match (&op, &decoded) {
1363 (Op::UpdateRelation(r1), Op::UpdateRelation(r2)) => {
1364 assert_eq!(r1.id, r2.id);
1365 assert_eq!(r1.from_space, r2.from_space);
1366 assert_eq!(r1.from_version, r2.from_version);
1367 assert_eq!(r1.to_space, r2.to_space);
1368 assert_eq!(r1.to_version, r2.to_version);
1369 match (&r1.position, &r2.position) {
1370 (Some(p1), Some(p2)) => assert_eq!(p1.as_ref(), p2.as_ref()),
1371 (None, None) => {}
1372 _ => panic!("position mismatch"),
1373 }
1374 assert_eq!(r1.unset, r2.unset);
1375 }
1376 _ => panic!("expected UpdateRelation"),
1377 }
1378 }
1379
1380 #[test]
1381 fn test_update_relation_with_unset() {
1382 let op = Op::UpdateRelation(UpdateRelation {
1384 id: [1u8; 16],
1385 from_space: None,
1386 from_version: None,
1387 to_space: None,
1388 to_version: None,
1389 position: None,
1390 unset: vec![
1391 UnsetRelationField::FromSpace,
1392 UnsetRelationField::ToVersion,
1393 UnsetRelationField::Position,
1394 ],
1395 context: None,
1396 });
1397
1398 let mut dict_builder = DictionaryBuilder::new();
1399 dict_builder.add_object([1u8; 16]); let property_types = rustc_hash::FxHashMap::default();
1401
1402 let mut writer = Writer::new();
1403 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1404
1405 let dicts = dict_builder.build();
1406 let mut reader = Reader::new(writer.as_bytes());
1407 let decoded = decode_op(&mut reader, &dicts).unwrap();
1408
1409 match (&op, &decoded) {
1410 (Op::UpdateRelation(r1), Op::UpdateRelation(r2)) => {
1411 assert_eq!(r1.id, r2.id);
1412 assert_eq!(r1.unset.len(), r2.unset.len());
1414 for field in &r1.unset {
1415 assert!(r2.unset.contains(field), "missing unset field: {:?}", field);
1416 }
1417 }
1418 _ => panic!("expected UpdateRelation"),
1419 }
1420 }
1421
1422}