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 Ok(Op::DeleteEntity(DeleteEntity { id }))
234}
235
236fn decode_restore_entity<'a>(
237 reader: &mut Reader<'a>,
238 dicts: &WireDictionaries,
239) -> Result<Op<'a>, DecodeError> {
240 let id_index = reader.read_varint("entity_id")? as usize;
241 if id_index >= dicts.objects.len() {
242 return Err(DecodeError::IndexOutOfBounds {
243 dict: "objects",
244 index: id_index,
245 size: dicts.objects.len(),
246 });
247 }
248 let id = dicts.objects[id_index];
249 Ok(Op::RestoreEntity(RestoreEntity { id }))
250}
251
252fn decode_create_relation<'a>(
253 reader: &mut Reader<'a>,
254 dicts: &WireDictionaries,
255) -> Result<Op<'a>, DecodeError> {
256 let id = reader.read_id("relation_id")?;
257
258 let type_index = reader.read_varint("relation_type")? as usize;
259 if type_index >= dicts.relation_types.len() {
260 return Err(DecodeError::IndexOutOfBounds {
261 dict: "relation_types",
262 index: type_index,
263 size: dicts.relation_types.len(),
264 });
265 }
266 let relation_type = dicts.relation_types[type_index];
267
268 let flags = reader.read_byte("relation_flags")?;
269 let from_is_value_ref = flags & FLAG_FROM_IS_VALUE_REF != 0;
270 let to_is_value_ref = flags & FLAG_TO_IS_VALUE_REF != 0;
271
272 let from = if from_is_value_ref {
274 reader.read_id("from")?
275 } else {
276 let from_index = reader.read_varint("from")? as usize;
277 if from_index >= dicts.objects.len() {
278 return Err(DecodeError::IndexOutOfBounds {
279 dict: "objects",
280 index: from_index,
281 size: dicts.objects.len(),
282 });
283 }
284 dicts.objects[from_index]
285 };
286
287 let to = if to_is_value_ref {
289 reader.read_id("to")?
290 } else {
291 let to_index = reader.read_varint("to")? as usize;
292 if to_index >= dicts.objects.len() {
293 return Err(DecodeError::IndexOutOfBounds {
294 dict: "objects",
295 index: to_index,
296 size: dicts.objects.len(),
297 });
298 }
299 dicts.objects[to_index]
300 };
301
302 let from_space = if flags & FLAG_HAS_FROM_SPACE != 0 {
304 Some(reader.read_id("from_space")?)
305 } else {
306 None
307 };
308
309 let from_version = if flags & FLAG_HAS_FROM_VERSION != 0 {
310 Some(reader.read_id("from_version")?)
311 } else {
312 None
313 };
314
315 let to_space = if flags & FLAG_HAS_TO_SPACE != 0 {
316 Some(reader.read_id("to_space")?)
317 } else {
318 None
319 };
320
321 let to_version = if flags & FLAG_HAS_TO_VERSION != 0 {
322 Some(reader.read_id("to_version")?)
323 } else {
324 None
325 };
326
327 let entity = if flags & FLAG_HAS_ENTITY != 0 {
328 Some(reader.read_id("entity_id")?)
329 } else {
330 None
331 };
332
333 let position = if flags & FLAG_HAS_POSITION != 0 {
334 Some(decode_position(reader)?)
335 } else {
336 None
337 };
338
339 let context_ref_raw = reader.read_varint("context_ref")? as u32;
341 let context = if context_ref_raw == NO_CONTEXT_REF {
342 None
343 } else {
344 let idx = context_ref_raw as usize;
345 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
346 dict: "contexts",
347 index: idx,
348 size: dicts.contexts.len(),
349 })?.clone())
350 };
351
352 Ok(Op::CreateRelation(CreateRelation {
353 id,
354 relation_type,
355 from,
356 from_is_value_ref,
357 to,
358 to_is_value_ref,
359 entity,
360 position,
361 from_space,
362 from_version,
363 to_space,
364 to_version,
365 context,
366 }))
367}
368
369fn decode_update_relation<'a>(
370 reader: &mut Reader<'a>,
371 dicts: &WireDictionaries,
372) -> Result<Op<'a>, DecodeError> {
373 let id_index = reader.read_varint("relation_id")? as usize;
374 if id_index >= dicts.objects.len() {
375 return Err(DecodeError::IndexOutOfBounds {
376 dict: "objects",
377 index: id_index,
378 size: dicts.objects.len(),
379 });
380 }
381 let id = dicts.objects[id_index];
382
383 let set_flags = reader.read_byte("set_flags")?;
384 let unset_flags = reader.read_byte("unset_flags")?;
385
386 if set_flags & UPDATE_SET_RESERVED_MASK != 0 {
388 return Err(DecodeError::ReservedBitsSet {
389 context: "UpdateRelation set_flags",
390 });
391 }
392 if unset_flags & UPDATE_UNSET_RESERVED_MASK != 0 {
393 return Err(DecodeError::ReservedBitsSet {
394 context: "UpdateRelation unset_flags",
395 });
396 }
397
398 let from_space = if set_flags & UPDATE_SET_FROM_SPACE != 0 {
400 Some(reader.read_id("from_space")?)
401 } else {
402 None
403 };
404
405 let from_version = if set_flags & UPDATE_SET_FROM_VERSION != 0 {
406 Some(reader.read_id("from_version")?)
407 } else {
408 None
409 };
410
411 let to_space = if set_flags & UPDATE_SET_TO_SPACE != 0 {
412 Some(reader.read_id("to_space")?)
413 } else {
414 None
415 };
416
417 let to_version = if set_flags & UPDATE_SET_TO_VERSION != 0 {
418 Some(reader.read_id("to_version")?)
419 } else {
420 None
421 };
422
423 let position = if set_flags & UPDATE_SET_POSITION != 0 {
424 Some(decode_position(reader)?)
425 } else {
426 None
427 };
428
429 let mut unset = Vec::new();
431 if unset_flags & UPDATE_UNSET_FROM_SPACE != 0 {
432 unset.push(UnsetRelationField::FromSpace);
433 }
434 if unset_flags & UPDATE_UNSET_FROM_VERSION != 0 {
435 unset.push(UnsetRelationField::FromVersion);
436 }
437 if unset_flags & UPDATE_UNSET_TO_SPACE != 0 {
438 unset.push(UnsetRelationField::ToSpace);
439 }
440 if unset_flags & UPDATE_UNSET_TO_VERSION != 0 {
441 unset.push(UnsetRelationField::ToVersion);
442 }
443 if unset_flags & UPDATE_UNSET_POSITION != 0 {
444 unset.push(UnsetRelationField::Position);
445 }
446
447 let context_ref_raw = reader.read_varint("context_ref")? as u32;
449 let context = if context_ref_raw == NO_CONTEXT_REF {
450 None
451 } else {
452 let idx = context_ref_raw as usize;
453 Some(dicts.get_context(idx).ok_or_else(|| DecodeError::IndexOutOfBounds {
454 dict: "contexts",
455 index: idx,
456 size: dicts.contexts.len(),
457 })?.clone())
458 };
459
460 Ok(Op::UpdateRelation(UpdateRelation {
461 id,
462 from_space,
463 from_version,
464 to_space,
465 to_version,
466 position,
467 unset,
468 context,
469 }))
470}
471
472fn decode_delete_relation<'a>(
473 reader: &mut Reader<'a>,
474 dicts: &WireDictionaries,
475) -> Result<Op<'a>, DecodeError> {
476 let id_index = reader.read_varint("relation_id")? as usize;
477 if id_index >= dicts.objects.len() {
478 return Err(DecodeError::IndexOutOfBounds {
479 dict: "objects",
480 index: id_index,
481 size: dicts.objects.len(),
482 });
483 }
484 let id = dicts.objects[id_index];
485 Ok(Op::DeleteRelation(DeleteRelation { id }))
486}
487
488fn decode_restore_relation<'a>(
489 reader: &mut Reader<'a>,
490 dicts: &WireDictionaries,
491) -> Result<Op<'a>, DecodeError> {
492 let id_index = reader.read_varint("relation_id")? as usize;
493 if id_index >= dicts.objects.len() {
494 return Err(DecodeError::IndexOutOfBounds {
495 dict: "objects",
496 index: id_index,
497 size: dicts.objects.len(),
498 });
499 }
500 let id = dicts.objects[id_index];
501 Ok(Op::RestoreRelation(RestoreRelation { id }))
502}
503
504fn decode_create_value_ref<'a>(
505 reader: &mut Reader<'a>,
506 dicts: &WireDictionaries,
507) -> Result<Op<'a>, DecodeError> {
508 let id = reader.read_id("value_ref_id")?;
509
510 let entity_index = reader.read_varint("entity")? as usize;
511 if entity_index >= dicts.objects.len() {
512 return Err(DecodeError::IndexOutOfBounds {
513 dict: "objects",
514 index: entity_index,
515 size: dicts.objects.len(),
516 });
517 }
518 let entity = dicts.objects[entity_index];
519
520 let property_index = reader.read_varint("property")? as usize;
521 if property_index >= dicts.properties.len() {
522 return Err(DecodeError::IndexOutOfBounds {
523 dict: "properties",
524 index: property_index,
525 size: dicts.properties.len(),
526 });
527 }
528 let property = dicts.properties[property_index].0;
529 let data_type = dicts.properties[property_index].1;
530
531 let flags = reader.read_byte("value_ref_flags")?;
532
533 if flags & CREATE_VALUE_REF_RESERVED_MASK != 0 {
535 return Err(DecodeError::ReservedBitsSet {
536 context: "CreateValueRef flags",
537 });
538 }
539
540 let language = if flags & FLAG_HAS_LANGUAGE != 0 {
541 if data_type != DataType::Text {
543 return Err(DecodeError::MalformedEncoding {
544 context: "CreateValueRef has_language=1 but property DataType is not TEXT",
545 });
546 }
547 let lang_index = reader.read_varint("language")? as usize;
548 if lang_index == 0 {
550 None } else {
552 let idx = lang_index - 1;
553 if idx >= dicts.languages.len() {
554 return Err(DecodeError::IndexOutOfBounds {
555 dict: "languages",
556 index: lang_index,
557 size: dicts.languages.len() + 1,
558 });
559 }
560 Some(dicts.languages[idx])
561 }
562 } else {
563 None
564 };
565
566 let space = if flags & FLAG_HAS_SPACE != 0 {
567 Some(reader.read_id("space")?)
568 } else {
569 None
570 };
571
572 Ok(Op::CreateValueRef(CreateValueRef {
573 id,
574 entity,
575 property,
576 language,
577 space,
578 }))
579}
580
581pub fn encode_op(
590 writer: &mut Writer,
591 op: &Op<'_>,
592 dict_builder: &mut DictionaryBuilder,
593 property_types: &rustc_hash::FxHashMap<crate::model::Id, DataType>,
594) -> Result<(), EncodeError> {
595 match op {
596 Op::CreateEntity(ce) => encode_create_entity(writer, ce, dict_builder, property_types),
597 Op::UpdateEntity(ue) => encode_update_entity(writer, ue, dict_builder, property_types),
598 Op::DeleteEntity(de) => encode_delete_entity(writer, de, dict_builder),
599 Op::RestoreEntity(re) => encode_restore_entity(writer, re, dict_builder),
600 Op::CreateRelation(cr) => encode_create_relation(writer, cr, dict_builder),
601 Op::UpdateRelation(ur) => encode_update_relation(writer, ur, dict_builder),
602 Op::DeleteRelation(dr) => encode_delete_relation(writer, dr, dict_builder),
603 Op::RestoreRelation(rr) => encode_restore_relation(writer, rr, dict_builder),
604 Op::CreateValueRef(cvr) => encode_create_value_ref(writer, cvr, dict_builder),
605 }
606}
607
608fn encode_create_entity(
609 writer: &mut Writer,
610 ce: &CreateEntity<'_>,
611 dict_builder: &mut DictionaryBuilder,
612 property_types: &rustc_hash::FxHashMap<crate::model::Id, DataType>,
613) -> Result<(), EncodeError> {
614 writer.write_byte(OP_CREATE_ENTITY);
615 writer.write_id(&ce.id);
616 writer.write_varint(ce.values.len() as u64);
617
618 for pv in &ce.values {
619 let data_type = property_types.get(&pv.property)
620 .copied()
621 .unwrap_or_else(|| pv.value.data_type());
622 encode_property_value(writer, pv, dict_builder, data_type)?;
623 }
624
625 let context_ref = match &ce.context {
627 Some(ctx) => dict_builder.add_context(ctx) as u32,
628 None => NO_CONTEXT_REF,
629 };
630 writer.write_varint(context_ref as u64);
631
632 Ok(())
633}
634
635fn encode_update_entity(
636 writer: &mut Writer,
637 ue: &UpdateEntity<'_>,
638 dict_builder: &mut DictionaryBuilder,
639 property_types: &rustc_hash::FxHashMap<crate::model::Id, DataType>,
640) -> Result<(), EncodeError> {
641 writer.write_byte(OP_UPDATE_ENTITY);
642
643 let id_index = dict_builder.add_object(ue.id);
644 writer.write_varint(id_index as u64);
645
646 let mut flags = 0u8;
647 if !ue.set_properties.is_empty() {
648 flags |= FLAG_HAS_SET_PROPERTIES;
649 }
650 if !ue.unset_values.is_empty() {
651 flags |= FLAG_HAS_UNSET_VALUES;
652 }
653 writer.write_byte(flags);
654
655 if !ue.set_properties.is_empty() {
656 writer.write_varint(ue.set_properties.len() as u64);
657 for pv in &ue.set_properties {
658 let data_type = property_types.get(&pv.property)
659 .copied()
660 .unwrap_or_else(|| pv.value.data_type());
661 encode_property_value(writer, pv, dict_builder, data_type)?;
662 }
663 }
664
665 if !ue.unset_values.is_empty() {
666 writer.write_varint(ue.unset_values.len() as u64);
667 for unset in &ue.unset_values {
668 let idx = dict_builder.add_property(unset.property, DataType::Bool);
670 writer.write_varint(idx as u64);
671 let lang_value: u32 = match &unset.language {
673 UnsetLanguage::All => 0xFFFFFFFF,
674 UnsetLanguage::English => 0,
675 UnsetLanguage::Specific(lang_id) => {
676 let lang_index = dict_builder.add_language(Some(*lang_id));
677 lang_index as u32
678 }
679 };
680 writer.write_varint(lang_value as u64);
681 }
682 }
683
684 let context_ref = match &ue.context {
686 Some(ctx) => dict_builder.add_context(ctx) as u32,
687 None => NO_CONTEXT_REF,
688 };
689 writer.write_varint(context_ref as u64);
690
691 Ok(())
692}
693
694fn encode_delete_entity(
695 writer: &mut Writer,
696 de: &DeleteEntity,
697 dict_builder: &mut DictionaryBuilder,
698) -> Result<(), EncodeError> {
699 writer.write_byte(OP_DELETE_ENTITY);
700 let id_index = dict_builder.add_object(de.id);
701 writer.write_varint(id_index as u64);
702 Ok(())
703}
704
705fn encode_restore_entity(
706 writer: &mut Writer,
707 re: &RestoreEntity,
708 dict_builder: &mut DictionaryBuilder,
709) -> Result<(), EncodeError> {
710 writer.write_byte(OP_RESTORE_ENTITY);
711 let id_index = dict_builder.add_object(re.id);
712 writer.write_varint(id_index as u64);
713 Ok(())
714}
715
716fn encode_create_relation(
717 writer: &mut Writer,
718 cr: &CreateRelation<'_>,
719 dict_builder: &mut DictionaryBuilder,
720) -> Result<(), EncodeError> {
721 writer.write_byte(OP_CREATE_RELATION);
722 writer.write_id(&cr.id);
723
724 let type_index = dict_builder.add_relation_type(cr.relation_type);
725 writer.write_varint(type_index as u64);
726
727 let mut flags = 0u8;
729 if cr.from_space.is_some() {
730 flags |= FLAG_HAS_FROM_SPACE;
731 }
732 if cr.from_version.is_some() {
733 flags |= FLAG_HAS_FROM_VERSION;
734 }
735 if cr.to_space.is_some() {
736 flags |= FLAG_HAS_TO_SPACE;
737 }
738 if cr.to_version.is_some() {
739 flags |= FLAG_HAS_TO_VERSION;
740 }
741 if cr.entity.is_some() {
742 flags |= FLAG_HAS_ENTITY;
743 }
744 if cr.position.is_some() {
745 flags |= FLAG_HAS_POSITION;
746 }
747 if cr.from_is_value_ref {
748 flags |= FLAG_FROM_IS_VALUE_REF;
749 }
750 if cr.to_is_value_ref {
751 flags |= FLAG_TO_IS_VALUE_REF;
752 }
753 writer.write_byte(flags);
754
755 if cr.from_is_value_ref {
757 writer.write_id(&cr.from);
758 } else {
759 let from_index = dict_builder.add_object(cr.from);
760 writer.write_varint(from_index as u64);
761 }
762
763 if cr.to_is_value_ref {
765 writer.write_id(&cr.to);
766 } else {
767 let to_index = dict_builder.add_object(cr.to);
768 writer.write_varint(to_index as u64);
769 }
770
771 if let Some(space) = &cr.from_space {
773 writer.write_id(space);
774 }
775
776 if let Some(version) = &cr.from_version {
777 writer.write_id(version);
778 }
779
780 if let Some(space) = &cr.to_space {
781 writer.write_id(space);
782 }
783
784 if let Some(version) = &cr.to_version {
785 writer.write_id(version);
786 }
787
788 if let Some(entity) = &cr.entity {
789 writer.write_id(entity);
790 }
791
792 if let Some(pos) = &cr.position {
793 validate_position(pos)?;
794 writer.write_string(pos);
795 }
796
797 let context_ref = match &cr.context {
799 Some(ctx) => dict_builder.add_context(ctx) as u32,
800 None => NO_CONTEXT_REF,
801 };
802 writer.write_varint(context_ref as u64);
803
804 Ok(())
805}
806
807fn encode_update_relation(
808 writer: &mut Writer,
809 ur: &UpdateRelation<'_>,
810 dict_builder: &mut DictionaryBuilder,
811) -> Result<(), EncodeError> {
812 writer.write_byte(OP_UPDATE_RELATION);
813
814 let id_index = dict_builder.add_object(ur.id);
815 writer.write_varint(id_index as u64);
816
817 let mut set_flags = 0u8;
819 if ur.from_space.is_some() {
820 set_flags |= UPDATE_SET_FROM_SPACE;
821 }
822 if ur.from_version.is_some() {
823 set_flags |= UPDATE_SET_FROM_VERSION;
824 }
825 if ur.to_space.is_some() {
826 set_flags |= UPDATE_SET_TO_SPACE;
827 }
828 if ur.to_version.is_some() {
829 set_flags |= UPDATE_SET_TO_VERSION;
830 }
831 if ur.position.is_some() {
832 set_flags |= UPDATE_SET_POSITION;
833 }
834 writer.write_byte(set_flags);
835
836 let mut unset_flags = 0u8;
838 for field in &ur.unset {
839 match field {
840 UnsetRelationField::FromSpace => unset_flags |= UPDATE_UNSET_FROM_SPACE,
841 UnsetRelationField::FromVersion => unset_flags |= UPDATE_UNSET_FROM_VERSION,
842 UnsetRelationField::ToSpace => unset_flags |= UPDATE_UNSET_TO_SPACE,
843 UnsetRelationField::ToVersion => unset_flags |= UPDATE_UNSET_TO_VERSION,
844 UnsetRelationField::Position => unset_flags |= UPDATE_UNSET_POSITION,
845 }
846 }
847 writer.write_byte(unset_flags);
848
849 if let Some(space) = &ur.from_space {
851 writer.write_id(space);
852 }
853 if let Some(version) = &ur.from_version {
854 writer.write_id(version);
855 }
856 if let Some(space) = &ur.to_space {
857 writer.write_id(space);
858 }
859 if let Some(version) = &ur.to_version {
860 writer.write_id(version);
861 }
862 if let Some(pos) = &ur.position {
863 validate_position(pos)?;
864 writer.write_string(pos);
865 }
866
867 let context_ref = match &ur.context {
869 Some(ctx) => dict_builder.add_context(ctx) as u32,
870 None => NO_CONTEXT_REF,
871 };
872 writer.write_varint(context_ref as u64);
873
874 Ok(())
875}
876
877fn encode_delete_relation(
878 writer: &mut Writer,
879 dr: &DeleteRelation,
880 dict_builder: &mut DictionaryBuilder,
881) -> Result<(), EncodeError> {
882 writer.write_byte(OP_DELETE_RELATION);
883 let id_index = dict_builder.add_object(dr.id);
884 writer.write_varint(id_index as u64);
885 Ok(())
886}
887
888fn encode_restore_relation(
889 writer: &mut Writer,
890 rr: &RestoreRelation,
891 dict_builder: &mut DictionaryBuilder,
892) -> Result<(), EncodeError> {
893 writer.write_byte(OP_RESTORE_RELATION);
894 let id_index = dict_builder.add_object(rr.id);
895 writer.write_varint(id_index as u64);
896 Ok(())
897}
898
899fn encode_create_value_ref(
900 writer: &mut Writer,
901 cvr: &CreateValueRef,
902 dict_builder: &mut DictionaryBuilder,
903) -> Result<(), EncodeError> {
904 writer.write_byte(OP_CREATE_VALUE_REF);
905 writer.write_id(&cvr.id);
906
907 let entity_index = dict_builder.add_object(cvr.entity);
908 writer.write_varint(entity_index as u64);
909
910 let data_type = if cvr.language.is_some() { DataType::Text } else { DataType::Bool };
914 let property_index = dict_builder.add_property(cvr.property, data_type);
915 writer.write_varint(property_index as u64);
916
917 let mut flags = 0u8;
918 if cvr.language.is_some() {
919 flags |= FLAG_HAS_LANGUAGE;
920 }
921 if cvr.space.is_some() {
922 flags |= FLAG_HAS_SPACE;
923 }
924 writer.write_byte(flags);
925
926 if let Some(lang_id) = cvr.language {
927 let lang_index = dict_builder.add_language(Some(lang_id));
928 writer.write_varint(lang_index as u64);
929 }
930
931 if let Some(space) = &cvr.space {
932 writer.write_id(space);
933 }
934
935 Ok(())
936}
937
938fn encode_property_value(
939 writer: &mut Writer,
940 pv: &PropertyValue<'_>,
941 dict_builder: &mut DictionaryBuilder,
942 data_type: DataType,
943) -> Result<(), EncodeError> {
944 let prop_index = dict_builder.add_property(pv.property, data_type);
945 writer.write_varint(prop_index as u64);
946 crate::codec::value::encode_value(writer, &pv.value, dict_builder)?;
947 Ok(())
948}
949
950#[cfg(test)]
951mod tests {
952 use std::borrow::Cow;
953
954 use super::*;
955 use crate::model::Value;
956
957 #[test]
958 fn test_create_entity_roundtrip() {
959 let op = Op::CreateEntity(CreateEntity {
960 id: [1u8; 16],
961 values: vec![PropertyValue {
962 property: [2u8; 16],
963 value: Value::Text {
964 value: Cow::Owned("test".to_string()),
965 language: None,
966 },
967 }],
968 context: None,
969 });
970
971 let mut dict_builder = DictionaryBuilder::new();
972 let mut property_types = rustc_hash::FxHashMap::default();
973 property_types.insert([2u8; 16], DataType::Text);
974
975 let mut writer = Writer::new();
976 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
977
978 let dicts = dict_builder.build();
979 let mut reader = Reader::new(writer.as_bytes());
980 let decoded = decode_op(&mut reader, &dicts).unwrap();
981
982 match (&op, &decoded) {
984 (Op::CreateEntity(e1), Op::CreateEntity(e2)) => {
985 assert_eq!(e1.id, e2.id);
986 assert_eq!(e1.values.len(), e2.values.len());
987 for (v1, v2) in e1.values.iter().zip(e2.values.iter()) {
988 assert_eq!(v1.property, v2.property);
989 match (&v1.value, &v2.value) {
990 (Value::Text { value: s1, language: l1 }, Value::Text { value: s2, language: l2 }) => {
991 assert_eq!(s1.as_ref(), s2.as_ref());
992 assert_eq!(l1, l2);
993 }
994 _ => panic!("expected Text values"),
995 }
996 }
997 }
998 _ => panic!("expected CreateEntity"),
999 }
1000 }
1001
1002 #[test]
1003 fn test_create_relation_roundtrip() {
1004 let op = Op::CreateRelation(CreateRelation {
1006 id: [10u8; 16],
1007 relation_type: [1u8; 16],
1008 from: [2u8; 16],
1009 from_is_value_ref: false,
1010 to: [3u8; 16],
1011 to_is_value_ref: false,
1012 entity: Some([4u8; 16]),
1013 position: Some(Cow::Owned("abc".to_string())),
1014 from_space: None,
1015 from_version: None,
1016 to_space: None,
1017 to_version: None,
1018 context: None,
1019 });
1020
1021 let mut dict_builder = DictionaryBuilder::new();
1022 let property_types = rustc_hash::FxHashMap::default();
1023
1024 let mut writer = Writer::new();
1025 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1026
1027 let dicts = dict_builder.build();
1028 let mut reader = Reader::new(writer.as_bytes());
1029 let decoded = decode_op(&mut reader, &dicts).unwrap();
1030
1031 match (&op, &decoded) {
1033 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1034 assert_eq!(r1.id, r2.id);
1035 assert_eq!(r1.relation_type, r2.relation_type);
1036 assert_eq!(r1.from, r2.from);
1037 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1038 assert_eq!(r1.to, r2.to);
1039 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1040 assert_eq!(r1.entity, r2.entity);
1041 match (&r1.position, &r2.position) {
1042 (Some(p1), Some(p2)) => assert_eq!(p1.as_ref(), p2.as_ref()),
1043 (None, None) => {}
1044 _ => panic!("position mismatch"),
1045 }
1046 }
1047 _ => panic!("expected CreateRelation"),
1048 }
1049 }
1050
1051 #[test]
1052 fn test_create_relation_auto_entity_roundtrip() {
1053 let op = Op::CreateRelation(CreateRelation {
1055 id: [10u8; 16],
1056 relation_type: [1u8; 16],
1057 from: [2u8; 16],
1058 from_is_value_ref: false,
1059 to: [3u8; 16],
1060 to_is_value_ref: false,
1061 entity: None,
1062 position: Some(Cow::Owned("abc".to_string())),
1063 from_space: None,
1064 from_version: None,
1065 to_space: None,
1066 to_version: None,
1067 context: None,
1068 });
1069
1070 let mut dict_builder = DictionaryBuilder::new();
1071 let property_types = rustc_hash::FxHashMap::default();
1072
1073 let mut writer = Writer::new();
1074 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1075
1076 let dicts = dict_builder.build();
1077 let mut reader = Reader::new(writer.as_bytes());
1078 let decoded = decode_op(&mut reader, &dicts).unwrap();
1079
1080 match (&op, &decoded) {
1081 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1082 assert_eq!(r1.id, r2.id);
1083 assert_eq!(r1.relation_type, r2.relation_type);
1084 assert_eq!(r1.from, r2.from);
1085 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1086 assert_eq!(r1.to, r2.to);
1087 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1088 assert_eq!(r1.entity, r2.entity);
1089 assert!(r1.entity.is_none());
1090 assert!(r2.entity.is_none());
1091 }
1092 _ => panic!("expected CreateRelation"),
1093 }
1094 }
1095
1096 #[test]
1097 fn test_create_relation_with_versions() {
1098 let op = Op::CreateRelation(CreateRelation {
1099 id: [10u8; 16],
1100 relation_type: [1u8; 16],
1101 from: [2u8; 16],
1102 from_is_value_ref: false,
1103 to: [3u8; 16],
1104 to_is_value_ref: false,
1105 entity: Some([4u8; 16]),
1106 position: Some(Cow::Owned("abc".to_string())),
1107 from_space: Some([5u8; 16]),
1108 from_version: Some([6u8; 16]),
1109 to_space: Some([7u8; 16]),
1110 to_version: Some([8u8; 16]),
1111 context: None,
1112 });
1113
1114 let mut dict_builder = DictionaryBuilder::new();
1115 let property_types = rustc_hash::FxHashMap::default();
1116
1117 let mut writer = Writer::new();
1118 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1119
1120 let dicts = dict_builder.build();
1121 let mut reader = Reader::new(writer.as_bytes());
1122 let decoded = decode_op(&mut reader, &dicts).unwrap();
1123
1124 match (&op, &decoded) {
1125 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1126 assert_eq!(r1.id, r2.id);
1127 assert_eq!(r1.relation_type, r2.relation_type);
1128 assert_eq!(r1.from, r2.from);
1129 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1130 assert_eq!(r1.to, r2.to);
1131 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1132 assert_eq!(r1.entity, r2.entity);
1133 assert_eq!(r1.from_space, r2.from_space);
1134 assert_eq!(r1.from_version, r2.from_version);
1135 assert_eq!(r1.to_space, r2.to_space);
1136 assert_eq!(r1.to_version, r2.to_version);
1137 }
1138 _ => panic!("expected CreateRelation"),
1139 }
1140 }
1141
1142 #[test]
1143 fn test_create_relation_with_value_ref_endpoint() {
1144 let op = Op::CreateRelation(CreateRelation {
1146 id: [10u8; 16],
1147 relation_type: [1u8; 16],
1148 from: [2u8; 16],
1149 from_is_value_ref: false,
1150 to: [99u8; 16], to_is_value_ref: true,
1152 entity: None,
1153 position: None,
1154 from_space: None,
1155 from_version: None,
1156 to_space: None,
1157 to_version: None,
1158 context: None,
1159 });
1160
1161 let mut dict_builder = DictionaryBuilder::new();
1162 let property_types = rustc_hash::FxHashMap::default();
1163
1164 let mut writer = Writer::new();
1165 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1166
1167 let dicts = dict_builder.build();
1168 let mut reader = Reader::new(writer.as_bytes());
1169 let decoded = decode_op(&mut reader, &dicts).unwrap();
1170
1171 match (&op, &decoded) {
1172 (Op::CreateRelation(r1), Op::CreateRelation(r2)) => {
1173 assert_eq!(r1.id, r2.id);
1174 assert_eq!(r1.from, r2.from);
1175 assert_eq!(r1.from_is_value_ref, r2.from_is_value_ref);
1176 assert!(!r1.from_is_value_ref);
1177 assert_eq!(r1.to, r2.to);
1178 assert_eq!(r1.to_is_value_ref, r2.to_is_value_ref);
1179 assert!(r1.to_is_value_ref);
1180 }
1181 _ => panic!("expected CreateRelation"),
1182 }
1183 }
1184
1185 #[test]
1186 fn test_create_value_ref_roundtrip() {
1187 let op = Op::CreateValueRef(CreateValueRef {
1188 id: [1u8; 16],
1189 entity: [2u8; 16],
1190 property: [3u8; 16],
1191 language: None,
1192 space: None,
1193 });
1194
1195 let mut dict_builder = DictionaryBuilder::new();
1196 let property_types = rustc_hash::FxHashMap::default();
1197
1198 let mut writer = Writer::new();
1199 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1200
1201 let dicts = dict_builder.build();
1202 let mut reader = Reader::new(writer.as_bytes());
1203 let decoded = decode_op(&mut reader, &dicts).unwrap();
1204
1205 match (&op, &decoded) {
1206 (Op::CreateValueRef(v1), Op::CreateValueRef(v2)) => {
1207 assert_eq!(v1.id, v2.id);
1208 assert_eq!(v1.entity, v2.entity);
1209 assert_eq!(v1.property, v2.property);
1210 assert_eq!(v1.language, v2.language);
1211 assert_eq!(v1.space, v2.space);
1212 }
1213 _ => panic!("expected CreateValueRef"),
1214 }
1215 }
1216
1217 #[test]
1218 fn test_create_value_ref_with_language_and_space() {
1219 let op = Op::CreateValueRef(CreateValueRef {
1220 id: [1u8; 16],
1221 entity: [2u8; 16],
1222 property: [3u8; 16],
1223 language: Some([4u8; 16]),
1224 space: Some([5u8; 16]),
1225 });
1226
1227 let mut dict_builder = DictionaryBuilder::new();
1228 let property_types = rustc_hash::FxHashMap::default();
1229
1230 let mut writer = Writer::new();
1231 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1232
1233 let dicts = dict_builder.build();
1234 let mut reader = Reader::new(writer.as_bytes());
1235 let decoded = decode_op(&mut reader, &dicts).unwrap();
1236
1237 match (&op, &decoded) {
1238 (Op::CreateValueRef(v1), Op::CreateValueRef(v2)) => {
1239 assert_eq!(v1.id, v2.id);
1240 assert_eq!(v1.entity, v2.entity);
1241 assert_eq!(v1.property, v2.property);
1242 assert_eq!(v1.language, v2.language);
1243 assert_eq!(v1.space, v2.space);
1244 }
1245 _ => panic!("expected CreateValueRef"),
1246 }
1247 }
1248
1249 #[test]
1250 fn test_update_relation_roundtrip() {
1251 let op = Op::UpdateRelation(UpdateRelation {
1253 id: [1u8; 16],
1254 from_space: Some([2u8; 16]),
1255 from_version: Some([3u8; 16]),
1256 to_space: Some([4u8; 16]),
1257 to_version: Some([5u8; 16]),
1258 position: Some(Cow::Owned("xyz".to_string())),
1259 unset: vec![],
1260 context: None,
1261 });
1262
1263 let mut dict_builder = DictionaryBuilder::new();
1264 dict_builder.add_object([1u8; 16]); let property_types = rustc_hash::FxHashMap::default();
1266
1267 let mut writer = Writer::new();
1268 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1269
1270 let dicts = dict_builder.build();
1271 let mut reader = Reader::new(writer.as_bytes());
1272 let decoded = decode_op(&mut reader, &dicts).unwrap();
1273
1274 match (&op, &decoded) {
1275 (Op::UpdateRelation(r1), Op::UpdateRelation(r2)) => {
1276 assert_eq!(r1.id, r2.id);
1277 assert_eq!(r1.from_space, r2.from_space);
1278 assert_eq!(r1.from_version, r2.from_version);
1279 assert_eq!(r1.to_space, r2.to_space);
1280 assert_eq!(r1.to_version, r2.to_version);
1281 match (&r1.position, &r2.position) {
1282 (Some(p1), Some(p2)) => assert_eq!(p1.as_ref(), p2.as_ref()),
1283 (None, None) => {}
1284 _ => panic!("position mismatch"),
1285 }
1286 assert_eq!(r1.unset, r2.unset);
1287 }
1288 _ => panic!("expected UpdateRelation"),
1289 }
1290 }
1291
1292 #[test]
1293 fn test_update_relation_with_unset() {
1294 let op = Op::UpdateRelation(UpdateRelation {
1296 id: [1u8; 16],
1297 from_space: None,
1298 from_version: None,
1299 to_space: None,
1300 to_version: None,
1301 position: None,
1302 unset: vec![
1303 UnsetRelationField::FromSpace,
1304 UnsetRelationField::ToVersion,
1305 UnsetRelationField::Position,
1306 ],
1307 context: None,
1308 });
1309
1310 let mut dict_builder = DictionaryBuilder::new();
1311 dict_builder.add_object([1u8; 16]); let property_types = rustc_hash::FxHashMap::default();
1313
1314 let mut writer = Writer::new();
1315 encode_op(&mut writer, &op, &mut dict_builder, &property_types).unwrap();
1316
1317 let dicts = dict_builder.build();
1318 let mut reader = Reader::new(writer.as_bytes());
1319 let decoded = decode_op(&mut reader, &dicts).unwrap();
1320
1321 match (&op, &decoded) {
1322 (Op::UpdateRelation(r1), Op::UpdateRelation(r2)) => {
1323 assert_eq!(r1.id, r2.id);
1324 assert_eq!(r1.unset.len(), r2.unset.len());
1326 for field in &r1.unset {
1327 assert!(r2.unset.contains(field), "missing unset field: {:?}", field);
1328 }
1329 }
1330 _ => panic!("expected UpdateRelation"),
1331 }
1332 }
1333
1334}