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