1use alloc::collections::{BTreeMap, VecDeque};
2use core::{marker::PhantomData, mem};
3
4use buggy::{bug, BugExt};
5
6use super::braiding;
7use crate::{
8 Address, ClientError, Command, CommandId, CommandRecall, Engine, EngineError, GraphId,
9 Location, MergeIds, Perspective, Policy, PolicyId, Prior, Revertable, Segment, Sink, Storage,
10 StorageError, StorageProvider, MAX_COMMAND_LENGTH,
11};
12
13pub struct Transaction<SP: StorageProvider, E> {
20 storage_id: GraphId,
22 perspective: Option<SP::Perspective>,
24 phead: Option<CommandId>,
26 heads: BTreeMap<Address, Location>,
28 _engine: PhantomData<E>,
30}
31
32impl<SP: StorageProvider, E> Transaction<SP, E> {
33 pub(super) const fn new(storage_id: GraphId) -> Self {
34 Self {
35 storage_id,
36 perspective: None,
37 phead: None,
38 heads: BTreeMap::new(),
39 _engine: PhantomData,
40 }
41 }
42}
43
44impl<SP: StorageProvider, E: Engine> Transaction<SP, E> {
45 pub fn storage_id(&self) -> GraphId {
47 self.storage_id
48 }
49
50 fn locate(
54 &self,
55 storage: &mut SP::Storage,
56 address: Address,
57 ) -> Result<Option<Location>, ClientError> {
58 if let Some(found) = storage.get_location(address)? {
60 return Ok(Some(found));
61 }
62 for &head in self.heads.values() {
64 if let Some(found) = storage.get_location_from(head, address)? {
65 return Ok(Some(found));
66 }
67 }
68 Ok(None)
69 }
70
71 pub(super) fn commit(
73 &mut self,
74 provider: &mut SP,
75 engine: &mut E,
76 sink: &mut impl Sink<E::Effect>,
77 ) -> Result<(), ClientError> {
78 let storage = provider.get_storage(self.storage_id)?;
79
80 if let Some(p) = Option::take(&mut self.perspective) {
82 self.phead = None;
83 let segment = storage.write(p)?;
84 let head = segment.head()?;
85 self.heads.insert(head.address()?, segment.head_location());
86 }
87
88 let mut heads: VecDeque<_> = mem::take(&mut self.heads).into_iter().collect();
91 let mut merging_head = false;
92 while let Some((left_id, mut left_loc)) = heads.pop_front() {
93 if let Some((right_id, mut right_loc)) = heads.pop_front() {
94 let (policy, policy_id) = choose_policy(storage, engine, left_loc, right_loc)?;
95
96 let mut buffer = [0u8; MAX_COMMAND_LENGTH];
97 let merge_ids = MergeIds::new(left_id, right_id).assume("merging different ids")?;
98 if left_id > right_id {
99 mem::swap(&mut left_loc, &mut right_loc);
100 }
101 let command = policy.merge(&mut buffer, merge_ids)?;
102
103 let (braid, last_common_ancestor) =
104 make_braid_segment::<_, E>(storage, left_loc, right_loc, sink, policy)?;
105
106 let mut perspective = storage
107 .new_merge_perspective(
108 left_loc,
109 right_loc,
110 last_common_ancestor,
111 policy_id,
112 braid,
113 )?
114 .assume("trx heads should exist in storage")?;
115 perspective.add_command(&command)?;
116
117 let segment = storage.write(perspective)?;
118 let head = segment.head()?;
119
120 heads.push_back((head.address()?, segment.head_location()));
121 } else {
122 let segment = storage.get_segment(left_loc)?;
123 match storage.commit(segment) {
126 Ok(()) => break,
127 Err(StorageError::HeadNotAncestor) => {
128 if merging_head {
129 bug!("merging with graph head again, would loop");
130 }
131
132 merging_head = true;
133
134 heads.push_back((left_id, left_loc));
135
136 let head_loc = storage.get_head()?;
137 let segment = storage.get_segment(head_loc)?;
138 let head = segment.head()?;
139 heads.push_back((head.address()?, segment.head_location()));
140 }
141 Err(e) => return Err(e.into()),
142 }
143 }
144 }
145
146 Ok(())
147 }
148
149 pub(super) fn add_commands(
153 &mut self,
154 commands: &[impl Command],
155 provider: &mut SP,
156 engine: &mut E,
157 sink: &mut impl Sink<E::Effect>,
158 ) -> Result<usize, ClientError> {
159 let mut commands = commands.iter();
160 let mut count: usize = 0;
161
162 let storage = match provider.get_storage(self.storage_id) {
164 Ok(s) => s,
165 Err(StorageError::NoSuchStorage) => {
166 let command = commands.next().ok_or(ClientError::InitError)?;
167 count = count.checked_add(1).assume("must not overflow")?;
168 self.init(command, engine, provider, sink)?
169 }
170 Err(e) => return Err(e.into()),
171 };
172
173 for command in commands {
175 if self
176 .perspective
177 .as_ref()
178 .is_some_and(|p| p.includes(command.id()))
179 {
180 continue;
182 }
183 if (self.locate(storage, command.address()?)?).is_some() {
184 continue;
186 }
187 match command.parent() {
188 Prior::None => {
189 if command.id().into_id() == self.storage_id.into_id() {
190 } else {
192 bug!("init command does not belong in graph");
193 }
194 }
195 Prior::Single(parent) => {
196 self.add_single(storage, engine, sink, command, parent)?;
197 count = count.checked_add(1).assume("must not overflow")?;
198 }
199 Prior::Merge(left, right) => {
200 self.add_merge(storage, engine, sink, command, left, right)?;
201 count = count.checked_add(1).assume("must not overflow")?;
202 }
203 };
204 }
205
206 Ok(count)
207 }
208
209 fn add_single(
210 &mut self,
211 storage: &mut <SP as StorageProvider>::Storage,
212 engine: &mut E,
213 sink: &mut impl Sink<E::Effect>,
214 command: &impl Command,
215 parent: Address,
216 ) -> Result<(), ClientError> {
217 let perspective = self.get_perspective(parent, storage)?;
218
219 let policy_id = perspective.policy();
220 let policy = engine.get_policy(policy_id)?;
221
222 sink.begin();
224 let checkpoint = perspective.checkpoint();
225 if let Err(e) = policy.call_rule(command, perspective, sink, CommandRecall::None) {
226 perspective.revert(checkpoint)?;
227 sink.rollback();
228 return Err(e.into());
229 }
230 perspective.add_command(command)?;
231 sink.commit();
232
233 self.phead = Some(command.id());
234
235 Ok(())
236 }
237
238 fn add_merge(
239 &mut self,
240 storage: &mut <SP as StorageProvider>::Storage,
241 engine: &mut E,
242 sink: &mut impl Sink<E::Effect>,
243 command: &impl Command,
244 left: Address,
245 right: Address,
246 ) -> Result<bool, ClientError> {
247 if let Some(p) = Option::take(&mut self.perspective) {
249 let seg = storage.write(p)?;
250 let head = seg.head()?;
251 self.heads.insert(head.address()?, seg.head_location());
252 }
253
254 let left_loc = self
255 .locate(storage, left)?
256 .ok_or(ClientError::NoSuchParent(left.id))?;
257 let right_loc = self
258 .locate(storage, right)?
259 .ok_or(ClientError::NoSuchParent(right.id))?;
260
261 let (policy, policy_id) = choose_policy(storage, engine, left_loc, right_loc)?;
262
263 let (braid, last_common_ancestor) =
265 make_braid_segment::<_, E>(storage, left_loc, right_loc, sink, policy)?;
266
267 let mut perspective = storage
268 .new_merge_perspective(left_loc, right_loc, last_common_ancestor, policy_id, braid)?
269 .assume(
270 "we already found left and right locations above and we only call this with merge command",
271 )?;
272 perspective.add_command(command)?;
273
274 self.heads.remove(&left);
276 self.heads.remove(&right);
277
278 self.perspective = Some(perspective);
279 self.phead = Some(command.id());
280
281 Ok(true)
282 }
283
284 fn get_perspective(
289 &mut self,
290 parent: Address,
291 storage: &mut <SP as StorageProvider>::Storage,
292 ) -> Result<&mut <SP as StorageProvider>::Perspective, ClientError> {
293 if self.phead == Some(parent.id) {
294 return Ok(self
296 .perspective
297 .as_mut()
298 .assume("trx has perspective when has phead")?);
299 }
300
301 if let Some(p) = Option::take(&mut self.perspective) {
303 self.phead = None;
304 let seg = storage.write(p)?;
305 let head = seg.head()?;
306 self.heads.insert(head.address()?, seg.head_location());
307 }
308
309 let loc = self
310 .locate(storage, parent)?
311 .ok_or(ClientError::NoSuchParent(parent.id))?;
312
313 let p = self.perspective.insert(
315 storage
316 .get_linear_perspective(loc)?
317 .assume("location should already be in storage")?,
318 );
319
320 self.phead = Some(parent.id);
321 self.heads.remove(&parent);
322
323 Ok(p)
324 }
325
326 fn init<'sp>(
327 &mut self,
328 command: &impl Command,
329 engine: &mut E,
330 provider: &'sp mut SP,
331 sink: &mut impl Sink<E::Effect>,
332 ) -> Result<&'sp mut <SP as StorageProvider>::Storage, ClientError> {
333 if self.storage_id.into_id() != command.id().into_id() {
335 return Err(ClientError::InitError);
336 }
337
338 if !matches!(command.parent(), Prior::None) {
340 return Err(ClientError::InitError);
341 }
342
343 let Some(policy_data) = command.policy() else {
345 return Err(ClientError::InitError);
346 };
347
348 let policy_id = engine.add_policy(policy_data)?;
349 let policy = engine.get_policy(policy_id)?;
350
351 let mut perspective = provider.new_perspective(policy_id);
353 sink.begin();
354 if let Err(e) = policy.call_rule(command, &mut perspective, sink, CommandRecall::None) {
355 sink.rollback();
356 return Err(e.into());
358 }
359 perspective.add_command(command)?;
360
361 let (_, storage) = provider.new_storage(perspective)?;
362
363 sink.commit();
365
366 Ok(storage)
367 }
368}
369
370fn make_braid_segment<S: Storage, E: Engine>(
372 storage: &mut S,
373 left: Location,
374 right: Location,
375 sink: &mut impl Sink<E::Effect>,
376 policy: &E::Policy,
377) -> Result<(S::FactIndex, (Location, usize)), ClientError> {
378 let order = braiding::braid(storage, left, right)?;
379 let last_common_ancestor = braiding::last_common_ancestor(storage, left, right)?;
380
381 let (&first, rest) = order.split_first().assume("braid is non-empty")?;
382
383 let mut braid_perspective = storage.get_fact_perspective(first)?;
384
385 sink.begin();
386
387 for &location in rest {
388 let segment = storage.get_segment(location)?;
389 let command = segment
390 .get_command(location)
391 .assume("braid only contains existing commands")?;
392
393 let result = policy.call_rule(
394 &command,
395 &mut braid_perspective,
396 sink,
397 CommandRecall::OnCheck,
398 );
399
400 if let Err(e) = result {
402 if e != EngineError::Check {
403 sink.rollback();
404 return Err(e.into());
405 }
406 }
407 }
408
409 let braid = storage.write_facts(braid_perspective)?;
410
411 sink.commit();
412
413 Ok((braid, last_common_ancestor))
414}
415
416fn choose_policy<'a, E: Engine>(
418 storage: &impl Storage,
419 engine: &'a E,
420 left: Location,
421 right: Location,
422) -> Result<(&'a E::Policy, PolicyId), ClientError> {
423 Ok(core::cmp::max_by_key(
424 get_policy(storage, engine, left)?,
425 get_policy(storage, engine, right)?,
426 |(p, _)| p.serial(),
427 ))
428}
429
430fn get_policy<'a, E: Engine>(
431 storage: &impl Storage,
432 engine: &'a E,
433 location: Location,
434) -> Result<(&'a E::Policy, PolicyId), ClientError> {
435 let segment = storage.get_segment(location)?;
436 let policy_id = segment.policy();
437 let policy = engine.get_policy(policy_id)?;
438 Ok((policy, policy_id))
439}
440
441#[cfg(test)]
442mod test {
443 use std::collections::HashMap;
444
445 use buggy::Bug;
446 use test_log::test;
447
448 use super::*;
449 use crate::{memory::MemStorageProvider, ClientState, Keys, MergeIds, Priority};
450
451 struct SeqEngine;
452
453 struct SeqPolicy;
458
459 struct SeqCommand {
460 id: CommandId,
461 prior: Prior<Address>,
462 finalize: bool,
463 data: Box<str>,
464 max_cut: usize,
465 }
466
467 impl Engine for SeqEngine {
468 type Policy = SeqPolicy;
469 type Effect = ();
470
471 fn add_policy(&mut self, _policy: &[u8]) -> Result<PolicyId, EngineError> {
472 Ok(PolicyId::new(0))
473 }
474
475 fn get_policy(&self, _id: PolicyId) -> Result<&Self::Policy, EngineError> {
476 Ok(&SeqPolicy)
477 }
478 }
479
480 impl Policy for SeqPolicy {
481 type Action<'a> = &'a str;
482 type Effect = ();
483 type Command<'a> = SeqCommand;
484
485 fn serial(&self) -> u32 {
486 0
487 }
488
489 fn call_rule(
490 &self,
491 command: &impl Command,
492 facts: &mut impl crate::FactPerspective,
493 _sink: &mut impl Sink<Self::Effect>,
494 _recall: CommandRecall,
495 ) -> Result<(), EngineError> {
496 assert!(
497 !matches!(command.parent(), Prior::Merge { .. }),
498 "merges shouldn't be evaluated"
499 );
500
501 let data = command.bytes();
503 if let Some(seq) = facts.query("seq", &Keys::default())?.as_deref() {
504 facts.insert(
505 "seq".into(),
506 Keys::default(),
507 [seq, b":", data].concat().into(),
508 );
509 } else {
510 facts.insert("seq".into(), Keys::default(), data.into());
511 };
512 Ok(())
513 }
514
515 fn call_action(
516 &self,
517 _action: Self::Action<'_>,
518 _facts: &mut impl Perspective,
519 _sink: &mut impl Sink<Self::Effect>,
520 ) -> Result<(), EngineError> {
521 unimplemented!()
522 }
523
524 fn merge<'a>(
525 &self,
526 _target: &'a mut [u8],
527 ids: MergeIds,
528 ) -> Result<Self::Command<'a>, EngineError> {
529 let (left, right): (Address, Address) = ids.into();
530 let parents = [*left.id.as_array(), *right.id.as_array()];
531 let id = CommandId::hash_for_testing_only(parents.as_flattened());
532
533 Ok(SeqCommand::new(
534 id,
535 Prior::Merge(left, right),
536 left.max_cut
537 .max(right.max_cut)
538 .checked_add(1)
539 .assume("must not overflow")?,
540 ))
541 }
542 }
543
544 impl SeqCommand {
545 fn new(id: CommandId, prior: Prior<Address>, max_cut: usize) -> Self {
546 let data = id.short_b58().into_boxed_str();
547 Self {
548 id,
549 prior,
550 finalize: false,
551 data,
552 max_cut,
553 }
554 }
555
556 fn finalize(id: CommandId, prev: Address, max_cut: usize) -> Self {
557 let data = id.short_b58().into_boxed_str();
558 Self {
559 id,
560 prior: Prior::Single(prev),
561 finalize: true,
562 data,
563 max_cut,
564 }
565 }
566 }
567
568 impl Command for SeqCommand {
569 fn priority(&self) -> Priority {
570 if self.finalize {
571 return Priority::Finalize;
572 }
573 match self.prior {
574 Prior::None => Priority::Init,
575 Prior::Single(_) => {
576 let id = self.id.as_bytes();
579 let priority = u32::from(*id.last().unwrap());
580 Priority::Basic(priority)
581 }
582 Prior::Merge(_, _) => Priority::Merge,
583 }
584 }
585
586 fn id(&self) -> CommandId {
587 self.id
588 }
589
590 fn parent(&self) -> Prior<Address> {
591 self.prior
592 }
593
594 fn policy(&self) -> Option<&[u8]> {
595 match self.prior {
598 Prior::None { .. } => Some(b""),
599 _ => None,
600 }
601 }
602
603 fn bytes(&self) -> &[u8] {
604 self.data.as_bytes()
605 }
606
607 fn max_cut(&self) -> Result<usize, Bug> {
608 Ok(self.max_cut)
609 }
610 }
611
612 struct NullSink;
613 impl<E> Sink<E> for NullSink {
614 fn begin(&mut self) {}
615 fn consume(&mut self, _: E) {}
616 fn rollback(&mut self) {}
617 fn commit(&mut self) {}
618 }
619
620 struct GraphBuilder<SP: StorageProvider> {
623 client: ClientState<SeqEngine, SP>,
624 trx: Transaction<SP, SeqEngine>,
625 max_cuts: HashMap<CommandId, usize>,
626 }
627
628 impl<SP: StorageProvider> GraphBuilder<SP> {
629 pub fn init(
630 mut client: ClientState<SeqEngine, SP>,
631 ids: &[CommandId],
632 ) -> Result<Self, ClientError> {
633 let mut trx = Transaction::new(GraphId::from(ids[0].into_id()));
634 let mut prior: Prior<Address> = Prior::None;
635 let mut max_cuts = HashMap::new();
636 for (max_cut, &id) in ids.iter().enumerate() {
637 let cmd = SeqCommand::new(id, prior, max_cut);
638 trx.add_commands(
639 &[cmd],
640 &mut client.provider,
641 &mut client.engine,
642 &mut NullSink,
643 )?;
644 max_cuts.insert(id, max_cut);
645 prior = Prior::Single(Address { id, max_cut });
646 }
647 Ok(Self {
648 client,
649 trx,
650 max_cuts,
651 })
652 }
653
654 fn get_addr(&self, id: CommandId) -> Address {
655 Address {
656 id,
657 max_cut: self.max_cuts[&id],
658 }
659 }
660
661 pub fn line(&mut self, prev: CommandId, ids: &[CommandId]) -> Result<(), ClientError> {
662 let mut prev = self.get_addr(prev);
663 for &id in ids {
664 let max_cut = prev.max_cut.checked_add(1).unwrap();
665 let cmd = SeqCommand::new(id, Prior::Single(prev), max_cut);
666 self.trx.add_commands(
667 &[cmd],
668 &mut self.client.provider,
669 &mut self.client.engine,
670 &mut NullSink,
671 )?;
672 self.max_cuts.insert(id, max_cut);
673 prev = Address { id, max_cut };
674 }
675 Ok(())
676 }
677
678 pub fn finalize(&mut self, prev: CommandId, id: CommandId) -> Result<(), ClientError> {
679 let prev = self.get_addr(prev);
680 let max_cut = prev.max_cut.checked_add(1).unwrap();
681 let cmd = SeqCommand::finalize(id, prev, max_cut);
682 self.trx.add_commands(
683 &[cmd],
684 &mut self.client.provider,
685 &mut self.client.engine,
686 &mut NullSink,
687 )?;
688 self.max_cuts.insert(id, max_cut);
689 Ok(())
690 }
691
692 pub fn merge(
693 &mut self,
694 (left, right): (CommandId, CommandId),
695 ids: &[CommandId],
696 ) -> Result<(), ClientError> {
697 let prior = Prior::Merge(self.get_addr(left), self.get_addr(right));
698 let mergecmd = SeqCommand::new(ids[0], prior, prior.next_max_cut().unwrap());
699 let mut prev = Address {
700 id: mergecmd.id,
701 max_cut: mergecmd.max_cut,
702 };
703 self.max_cuts.insert(mergecmd.id, mergecmd.max_cut);
704 self.trx.add_commands(
705 &[mergecmd],
706 &mut self.client.provider,
707 &mut self.client.engine,
708 &mut NullSink,
709 )?;
710 for &id in &ids[1..] {
711 let cmd = SeqCommand::new(
712 id,
713 Prior::Single(prev),
714 prev.max_cut.checked_add(1).expect("must not overflow"),
715 );
716 prev = Address {
717 id: cmd.id,
718 max_cut: cmd.max_cut,
719 };
720 self.max_cuts.insert(cmd.id, cmd.max_cut);
721 self.trx.add_commands(
722 &[cmd],
723 &mut self.client.provider,
724 &mut self.client.engine,
725 &mut NullSink,
726 )?;
727 }
728 Ok(())
729 }
730
731 pub fn flush(&mut self) {
732 if let Some(p) = Option::take(&mut self.trx.perspective) {
733 self.trx.phead = None;
734 let seg = self
735 .client
736 .provider
737 .get_storage(self.trx.storage_id)
738 .unwrap()
739 .write(p)
740 .unwrap();
741 let head = seg.head().unwrap();
742 self.trx.heads.insert(
743 head.address().expect("address must exist"),
744 seg.head_location(),
745 );
746 }
747 }
748
749 pub fn commit(&mut self) -> Result<(), ClientError> {
750 self.trx.commit(
751 &mut self.client.provider,
752 &mut self.client.engine,
753 &mut NullSink,
754 )
755 }
756 }
757
758 fn mkid<T>(x: &str) -> T
759 where
760 aranya_crypto::Id: Into<T>,
761 {
762 x.parse::<aranya_crypto::Id>().unwrap().into()
763 }
764
765 macro_rules! graph {
767 ( $client:expr ; $init:literal $($inits:literal )* ; $($rest:tt)*) => {{
768 let mut gb = GraphBuilder::init($client, &[mkid($init), $(mkid($inits)),*]).unwrap();
769 graph!(@ gb, $($rest)*);
770 gb
771 }};
772 (@ $gb:ident, $prev:literal < $($id:literal)+; $($rest:tt)*) => {
773 $gb.line(mkid($prev), &[$(mkid($id)),+]).unwrap();
774 graph!(@ $gb, $($rest)*);
775 };
776 (@ $gb:ident, $l:literal $r:literal < $($id:literal)+; $($rest:tt)*) => {
777 $gb.merge((mkid($l), mkid($r)), &[$(mkid($id)),+]).unwrap();
778 graph!(@ $gb, $($rest)*);
779 };
780 (@ $gb:ident, $prev:literal < finalize $id:literal; $($rest:tt)*) => {
781 $gb.finalize(mkid($prev), mkid($id)).unwrap();
782 graph!(@ $gb, $($rest)*);
783 };
784 (@ $gb:ident, commit; $($rest:tt)*) => {
785 $gb.commit().unwrap();
786 graph!(@ $gb, $($rest)*);
787 };
788 (@ $gb:ident, ) => {
789 $gb.flush();
790 };
791 }
792
793 fn lookup(storage: &impl Storage, name: &str) -> Option<Box<[u8]>> {
794 use crate::Query;
795 let head = storage.get_head().unwrap();
796 let p = storage.get_fact_perspective(head).unwrap();
797 p.query(name, &Keys::default()).unwrap()
798 }
799
800 #[test]
801 fn test_simple() -> Result<(), StorageError> {
802 let mut gb = graph! {
803 ClientState::new(SeqEngine, MemStorageProvider::new());
804 "a";
805 "a" < "b";
806 "a" < "c";
807 "b" "c" < "ma";
808 "b" < "d";
809 "ma" "d" < "mb";
810 commit;
811 };
812 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
813
814 #[cfg(feature = "graphviz")]
815 crate::storage::memory::graphviz::dot(g, "simple");
816
817 assert_eq!(g.get_head().unwrap(), Location::new(5, 0));
818
819 let seq = lookup(g, "seq").unwrap();
820 let seq = std::str::from_utf8(&seq).unwrap();
821 assert_eq!(seq, "a:b:d:c");
822
823 Ok(())
824 }
825
826 #[test]
827 fn test_complex() -> Result<(), StorageError> {
828 let mut gb = graph! {
829 ClientState::new(SeqEngine, MemStorageProvider::new());
830 "a";
831 "a" < "1" "2" "3";
832 "3" < "4" "6" "7";
833 "3" < "5" "8";
834 "6" "8" < "9" "aa"; commit;
835 "7" < "a1" "a2";
836 "aa" "a2" < "a3";
837 "a3" < "a6" "a4";
838 "a3" < "a7" "a5";
839 "a4" "a5" < "a8";
840 "9" < "42" "43";
841 "42" < "45" "46";
842 "45" < "47" "48";
843 commit;
844 };
845
846 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
847
848 #[cfg(feature = "graphviz")]
849 crate::storage::memory::graphviz::dot(g, "complex");
850
851 assert_eq!(g.get_head().unwrap(), Location::new(15, 0));
852
853 let seq = lookup(g, "seq").unwrap();
854 let seq = std::str::from_utf8(&seq).unwrap();
855 assert_eq!(
856 seq,
857 "a:1:2:3:5:8:4:6:42:45:47:48:46:43:aa:7:a1:a2:a7:a6:a5:a4"
858 );
859
860 Ok(())
861 }
862
863 #[test]
864 fn test_duplicates() {
865 let mut gb = graph! {
866 ClientState::new(SeqEngine, MemStorageProvider::new());
867 "a";
868 "a" < "b" "c";
869 "a" < "b";
870 "b" < "c";
871 "c" < "d";
872 commit;
873 "a" < "b";
874 "b" < "c";
875 "d" < "e";
876 commit;
877 };
878
879 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
880
881 #[cfg(feature = "graphviz")]
882 crate::storage::memory::graphviz::dot(g, "duplicates");
883
884 assert_eq!(g.get_head().unwrap(), Location::new(2, 0));
885
886 let seq = lookup(g, "seq").unwrap();
887 let seq = std::str::from_utf8(&seq).unwrap();
888 assert_eq!(seq, "a:b:c:d:e");
889 }
890
891 #[test]
892 fn test_mid_braid_1() {
893 let mut gb = graph! {
894 ClientState::new(SeqEngine, MemStorageProvider::new());
895 "a";
896 commit;
897 "a" < "b" "c" "d" "e" "f" "g";
898 "d" < "h" "i" "j";
899 commit;
900 };
901
902 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
903
904 #[cfg(feature = "graphviz")]
905 crate::storage::memory::graphviz::dot(g, "mid_braid_1");
906
907 assert_eq!(g.get_head().unwrap(), Location::new(3, 0));
908
909 let seq = lookup(g, "seq").unwrap();
910 let seq = std::str::from_utf8(&seq).unwrap();
911 assert_eq!(seq, "a:b:c:d:h:i:j:e:f:g");
912 }
913
914 #[test]
915 fn test_mid_braid_2() {
916 let mut gb = graph! {
917 ClientState::new(SeqEngine, MemStorageProvider::new());
918 "a";
919 commit;
920 "a" < "b" "c" "d" "h" "i" "j";
921 "d" < "e" "f" "g";
922 commit;
923 };
924
925 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
926
927 #[cfg(feature = "graphviz")]
928 crate::storage::memory::graphviz::dot(g, "mid_braid_2");
929
930 assert_eq!(g.get_head().unwrap(), Location::new(3, 0));
931
932 let seq = lookup(g, "seq").unwrap();
933 let seq = std::str::from_utf8(&seq).unwrap();
934 assert_eq!(seq, "a:b:c:d:h:i:j:e:f:g");
935 }
936
937 #[test]
938 fn test_sequential_finalize() {
939 let mut gb = graph! {
940 ClientState::new(SeqEngine, MemStorageProvider::new());
941 "a";
942 commit;
943 "a" < "b" "c" "d" "e" "f" "g";
944 "d" < "h" "i" "j";
945 "e" < finalize "fff1";
946 "fff1" < "x" "y";
947 "y" < finalize "fff2";
948 commit;
949 };
950
951 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
952
953 #[cfg(feature = "graphviz")]
954 crate::storage::memory::graphviz::dot(g, "finalize_success");
955
956 assert_eq!(g.get_head().unwrap(), Location::new(5, 0));
957
958 let seq = lookup(g, "seq").unwrap();
959 let seq = std::str::from_utf8(&seq).unwrap();
960 assert_eq!(seq, "a:b:c:d:e:fff1:x:y:fff2:h:i:j:f:g");
961 }
962
963 #[test]
964 fn test_parallel_finalize() {
965 let mut gb = graph! {
966 ClientState::new(SeqEngine, MemStorageProvider::new());
967 "a";
968 commit;
969 "a" < "b" "c" "d" "e" "f" "g";
970 "d" < "h" "i" "j";
971 "e" < finalize "fff1";
972 "i" < finalize "fff2";
973 };
974 let err = gb.commit().expect_err("merge should fail");
975 assert!(matches!(err, ClientError::ParallelFinalize), "{err:?}")
976 }
977}