aranya_runtime/client/
transaction.rs

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
12/// Transaction used to receive many commands at once.
13///
14/// The transaction allows us to have many temporary heads at once, so we don't
15/// need as many merges when adding commands. When the transaction is committed,
16/// we will merge all temporary heads and the storage head, and then commit the
17/// result as the new storage head.
18pub struct Transaction<SP: StorageProvider, E> {
19    /// The ID of the associated storage
20    storage_id: GraphId,
21    /// Current working perspective
22    perspective: Option<SP::Perspective>,
23    /// Head of the current perspective
24    phead: Option<CommandId>,
25    /// Written but not committed heads
26    heads: BTreeMap<Address, Location>,
27    /// Tag for associated engine
28    _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    /// Returns the transaction's storage id.
45    pub fn storage_id(&self) -> GraphId {
46        self.storage_id
47    }
48
49    /// Find a given id if reachable within this transaction.
50    ///
51    /// Does not search `self.perspective`, which should be written out beforehand.
52    fn locate(
53        &self,
54        storage: &mut SP::Storage,
55        address: Address,
56    ) -> Result<Option<Location>, ClientError> {
57        // Search from committed head.
58        if let Some(found) = storage.get_location(address)? {
59            return Ok(Some(found));
60        }
61        // Search from our temporary heads.
62        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    /// Write current perspective, merge transaction heads, and commit to graph.
71    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        // Write out current perspective.
80        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        // Merge heads pairwise until single head left, then commit.
88        // TODO(#370): Merge deterministically
89        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                // Try to commit. If it fails with `HeadNotAncestor`, we know we
123                // need to merge with the graph head.
124                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    /// Attempt to store the `command` in the graph with `storage_id`. Effects will be
149    /// emitted to the `sink`. This interface is used when syncing with another device
150    /// and integrating the new commands.
151    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        // Get storage or try to initialize with first command.
162        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        // Handle remaining commands.
173        for command in commands {
174            if self
175                .perspective
176                .as_ref()
177                .is_some_and(|p| p.includes(command.id()))
178            {
179                // Command in current perspective.
180                continue;
181            }
182            if (self.locate(storage, command.address()?)?).is_some() {
183                // Command already added.
184                continue;
185            }
186            match command.parent() {
187                Prior::None => {
188                    if command.id().into_id() == self.storage_id.into_id() {
189                        // Graph already initialized, extra init just spurious
190                    } 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        // Try to run command, or revert if failed.
222        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        // Must always start a new perspective for merges.
247        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        // Braid commands from left and right into an ordered sequence.
263        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        // These are no longer heads of the transaction, since they are both covered by the merge
274        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    /// Get a perspective to which we can add a command with the given parant.
284    ///
285    /// If parent is the head of the current perspective, we can just use it.
286    /// Otherwise, we must write out the perspective and get a new one.
287    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            // Command will append to current perspective.
294            return Ok(self
295                .perspective
296                .as_mut()
297                .assume("trx has perspective when has phead")?);
298        }
299
300        // Write out the current perspective.
301        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        // Get a new perspective and store it in the transaction.
313        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        // Storage ID is the id of the init command by definition.
333        if self.storage_id.into_id() != command.id().into_id() {
334            return Err(ClientError::InitError);
335        }
336
337        // The init command must not have a parent.
338        if !matches!(command.parent(), Prior::None) {
339            return Err(ClientError::InitError);
340        }
341
342        // The graph must have policy to start with.
343        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        // Get an empty perspective and run the init command.
351        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            // We don't need to revert perspective since we just drop it.
356            return Err(e.into());
357        }
358        perspective.add_command(command)?;
359
360        let (_, storage) = provider.new_storage(perspective)?;
361
362        // Wait to commit until we are absolutely sure we've initialized.
363        sink.commit();
364
365        Ok(storage)
366    }
367}
368
369/// Run the braid algorithm and evaluate the sequence to create a braided fact index.
370fn 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 the command failed in an uncontrolled way, rollback
400        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
415/// Select the policy from two locations with the greatest serial value.
416fn 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    /// [`SeqPolicy`] is a very simple policy which appends the id of each
451    /// command to a fact named `b"seq"`. At each point in the graph, the value
452    /// of this fact should be equal to the ids in braid order of all facts up
453    /// to that point.
454    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            // For init and basic commands, append the id to the seq fact.
498            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                    // Use the last byte of the ID as priority, just so we can
560                    // properly see the effects of braiding
561                    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            // We don't actually need any policy bytes, but the
579            // transaction/storage requires it on init commands.
580            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    /// [`GraphBuilder`] and the associated macro [`graph`] provide an easy way
604    /// to create a graph with a specific structure.
605    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    /// See tests for usage.
714    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}