1use alloc::collections::{BTreeMap, VecDeque};
2use core::{marker::PhantomData, mem};
3
4use buggy::{BugExt as _, bug};
5
6use super::braiding;
7use crate::{
8 Address, BraidBuffer, ClientError, CmdId, Command, GraphId, Location, MAX_COMMAND_LENGTH,
9 MergeIds, Perspective as _, Policy as _, PolicyError, PolicyId, PolicyStore, Prior,
10 Revertable as _, RuntimeBuffers, Segment as _, Sink, Storage, StorageError, StorageProvider,
11 TraversalBuffer, policy::CommandPlacement, storage::Spill,
12};
13
14pub struct Transaction<SP: StorageProvider, PS> {
21 graph_id: GraphId,
23 original_head: Option<Location>,
25 perspective: Option<SP::Perspective>,
27 phead: Option<CmdId>,
29 heads: BTreeMap<CmdId, Location>,
31 policy_store: PhantomData<PS>,
33}
34
35impl<SP: StorageProvider, PS> Transaction<SP, PS> {
36 pub(super) const fn new(graph_id: GraphId) -> Self {
37 Self {
38 graph_id,
39 original_head: None,
40 perspective: None,
41 phead: None,
42 heads: BTreeMap::new(),
43 policy_store: PhantomData,
44 }
45 }
46}
47
48impl<SP: StorageProvider, PS: PolicyStore> Transaction<SP, PS> {
49 pub fn graph_id(&self) -> GraphId {
51 self.graph_id
52 }
53
54 fn locate(
58 &self,
59 storage: &mut SP::Storage,
60 address: Address,
61 buffer: &mut TraversalBuffer,
62 ) -> Result<Option<Location>, ClientError> {
63 if let Some(found) = storage.get_location(address, buffer)? {
65 return Ok(Some(found));
66 }
67 for &head in self.heads.values() {
69 if let Some(found) = storage.get_location_from(head, address, buffer)? {
70 return Ok(Some(found));
71 }
72 }
73 Ok(None)
74 }
75
76 pub(super) fn commit<F, MS>(
78 mut self,
79 provider: &mut SP,
80 policy_store: &mut PS,
81 sink: &mut impl Sink<PS::Effect>,
82 buffers: &mut RuntimeBuffers<SP::Segment>,
83 make_spill: &MS,
84 ) -> Result<bool, ClientError>
85 where
86 F: Spill,
87 MS: Fn() -> Result<F, StorageError>,
88 {
89 let storage = provider.get_storage(self.graph_id)?;
90
91 let Some(original_head) = self.original_head else {
92 return Ok(false);
93 };
94 if original_head != storage.get_head()? {
95 return Err(ClientError::ConcurrentTransaction);
96 }
97
98 if let Some(p) = Option::take(&mut self.perspective) {
100 self.phead = None;
101 let segment = storage.write(p)?;
102 self.heads
103 .insert(segment.head_id(), segment.head_location()?);
104 }
105
106 if self.heads.is_empty() {
107 return Ok(false);
108 }
109
110 let mut heads: VecDeque<_> = mem::take(&mut self.heads).into_iter().collect();
113 let mut merging_head = false;
114 while let Some((left_id, mut left_loc)) = heads.pop_front() {
115 if let Some((right_id, mut right_loc)) = heads.pop_front() {
116 let (policy, policy_id) =
117 choose_policy(storage, policy_store, left_loc, right_loc)?;
118
119 let mut buf = [0u8; MAX_COMMAND_LENGTH];
120 let merge_ids = MergeIds::new(
121 Address {
122 id: left_id,
123 max_cut: left_loc.max_cut,
124 },
125 Address {
126 id: right_id,
127 max_cut: right_loc.max_cut,
128 },
129 )
130 .assume("merging different ids")?;
131 if left_id > right_id {
132 mem::swap(&mut left_loc, &mut right_loc);
133 }
134 let command = policy.merge(&mut buf, merge_ids)?;
135
136 let (braid, last_common_ancestor) = make_braid_segment::<_, PS, F, MS>(
137 storage,
138 left_loc,
139 right_loc,
140 sink,
141 policy,
142 &mut buffers.traversal.primary,
143 &mut buffers.braid,
144 make_spill,
145 )?;
146
147 let mut perspective = storage.new_merge_perspective(
148 left_loc,
149 right_loc,
150 last_common_ancestor,
151 policy_id,
152 braid,
153 )?;
154 perspective.add_command(&command)?;
155
156 let segment = storage.write(perspective)?;
157 heads.push_back((segment.head_id(), segment.head_location()?));
158 } else if storage.is_ancestor(
159 storage.get_head()?,
160 left_loc,
161 &mut buffers.traversal.primary,
162 )? {
163 let segment = storage.get_segment(left_loc)?;
164 storage.commit(segment)?;
165 debug_assert!(heads.is_empty());
166 } else {
167 if merging_head {
168 bug!("merging with graph head again, would loop");
169 }
170 merging_head = true;
171
172 heads.push_back((left_id, left_loc));
173
174 let head_loc = storage.get_head()?;
175 let segment = storage.get_segment(head_loc)?;
176 heads.push_back((segment.head_id(), segment.head_location()?));
177 }
178 }
179
180 Ok(true)
181 }
182
183 pub(super) fn add_commands<F, MS>(
187 &mut self,
188 commands: &[impl Command],
189 provider: &mut SP,
190 policy_store: &mut PS,
191 sink: &mut impl Sink<PS::Effect>,
192 buffers: &mut RuntimeBuffers<SP::Segment>,
193 make_spill: &MS,
194 ) -> Result<usize, ClientError>
195 where
196 F: Spill,
197 MS: Fn() -> Result<F, StorageError>,
198 {
199 let mut commands = commands.iter();
200 let mut count: usize = 0;
201
202 let storage = match provider.get_storage(self.graph_id) {
204 Ok(s) => s,
205 Err(StorageError::NoSuchStorage) => {
206 let command = commands.next().ok_or(ClientError::InitError)?;
207 count = count.checked_add(1).assume("must not overflow")?;
208 self.init(command, policy_store, provider, sink)?
209 }
210 Err(e) => return Err(e.into()),
211 };
212
213 if self.original_head.is_none() {
214 self.original_head = Some(storage.get_head()?);
215 }
216
217 for command in commands {
219 if self
220 .perspective
221 .as_ref()
222 .is_some_and(|p| p.includes(command.id()))
223 {
224 continue;
226 }
227
228 if self
229 .locate(storage, command.address()?, &mut buffers.traversal.primary)?
230 .is_some()
231 {
232 continue;
234 }
235 match command.parent() {
236 Prior::None => {
237 if command.id().as_base() == self.graph_id.as_base() {
238 } else {
240 bug!("init command does not belong in graph");
241 }
242 }
243 Prior::Single(parent) => {
244 self.add_single(
245 storage,
246 policy_store,
247 sink,
248 command,
249 parent,
250 &mut buffers.traversal.primary,
251 )?;
252 count = count.checked_add(1).assume("must not overflow")?;
253 }
254 Prior::Merge(left, right) => {
255 self.add_merge::<F, MS>(
256 storage,
257 policy_store,
258 sink,
259 command,
260 (left, right),
261 buffers,
262 make_spill,
263 )?;
264 count = count.checked_add(1).assume("must not overflow")?;
265 }
266 }
267 }
268
269 Ok(count)
270 }
271
272 fn add_single(
273 &mut self,
274 storage: &mut <SP as StorageProvider>::Storage,
275 policy_store: &mut PS,
276 sink: &mut impl Sink<PS::Effect>,
277 command: &impl Command,
278 parent: Address,
279 buffer: &mut TraversalBuffer,
280 ) -> Result<(), ClientError> {
281 let perspective = self.get_perspective(parent, storage, buffer)?;
282
283 let policy_id = perspective.policy();
284 let policy = policy_store.get_policy(policy_id)?;
285
286 sink.begin();
288 let checkpoint = perspective.checkpoint();
289 if let Err(e) = policy.call_rule(
290 command,
291 perspective,
292 sink,
293 CommandPlacement::OnGraphAtOrigin,
294 ) {
295 perspective.revert(checkpoint)?;
296 sink.rollback();
297 return Err(e.into());
298 }
299 perspective.add_command(command)?;
300 sink.commit();
301
302 self.phead = Some(command.id());
303
304 Ok(())
305 }
306
307 #[allow(clippy::too_many_arguments)]
308 fn add_merge<F, MS>(
309 &mut self,
310 storage: &mut <SP as StorageProvider>::Storage,
311 policy_store: &mut PS,
312 sink: &mut impl Sink<PS::Effect>,
313 command: &impl Command,
314 (left, right): (Address, Address),
315 buffers: &mut RuntimeBuffers<SP::Segment>,
316 make_spill: &MS,
317 ) -> Result<bool, ClientError>
318 where
319 F: Spill,
320 MS: Fn() -> Result<F, StorageError>,
321 {
322 if let Some(p) = Option::take(&mut self.perspective) {
324 let seg = storage.write(p)?;
325 self.heads.insert(seg.head_id(), seg.head_location()?);
326 }
327
328 let left_loc = self
329 .locate(storage, left, &mut buffers.traversal.primary)?
330 .ok_or(ClientError::NoSuchParent(left.id))?;
331 let right_loc = self
332 .locate(storage, right, &mut buffers.traversal.primary)?
333 .ok_or(ClientError::NoSuchParent(right.id))?;
334
335 let (policy, policy_id) = choose_policy(storage, policy_store, left_loc, right_loc)?;
336
337 let (braid, last_common_ancestor) = make_braid_segment::<_, PS, F, MS>(
339 storage,
340 left_loc,
341 right_loc,
342 sink,
343 policy,
344 &mut buffers.traversal.primary,
345 &mut buffers.braid,
346 make_spill,
347 )?;
348
349 let mut perspective = storage.new_merge_perspective(
350 left_loc,
351 right_loc,
352 last_common_ancestor,
353 policy_id,
354 braid,
355 )?;
356 perspective.add_command(command)?;
357
358 self.heads.remove(&left.id);
360 self.heads.remove(&right.id);
361
362 self.perspective = Some(perspective);
363 self.phead = Some(command.id());
364
365 Ok(true)
366 }
367
368 fn get_perspective(
373 &mut self,
374 parent: Address,
375 storage: &mut <SP as StorageProvider>::Storage,
376 buffer: &mut TraversalBuffer,
377 ) -> Result<&mut <SP as StorageProvider>::Perspective, ClientError> {
378 if self.phead == Some(parent.id) {
379 return Ok(self
381 .perspective
382 .as_mut()
383 .assume("trx has perspective when has phead")?);
384 }
385
386 if let Some(p) = Option::take(&mut self.perspective) {
388 self.phead = None;
389 let seg = storage.write(p)?;
390 self.heads.insert(seg.head_id(), seg.head_location()?);
391 }
392
393 let loc = self
394 .locate(storage, parent, buffer)?
395 .ok_or(ClientError::NoSuchParent(parent.id))?;
396
397 let p = self
399 .perspective
400 .insert(storage.get_linear_perspective(loc)?);
401
402 self.phead = Some(parent.id);
403 self.heads.remove(&parent.id);
404
405 Ok(p)
406 }
407
408 fn init<'sp>(
409 &mut self,
410 command: &impl Command,
411 policy_store: &mut PS,
412 provider: &'sp mut SP,
413 sink: &mut impl Sink<PS::Effect>,
414 ) -> Result<&'sp mut <SP as StorageProvider>::Storage, ClientError> {
415 if self.graph_id.as_base() != command.id().as_base() {
417 return Err(ClientError::InitError);
418 }
419
420 if !matches!(command.parent(), Prior::None) {
422 return Err(ClientError::InitError);
423 }
424
425 let Some(policy_data) = command.policy() else {
427 return Err(ClientError::InitError);
428 };
429
430 let policy_id = policy_store.add_policy(policy_data)?;
431 let policy = policy_store.get_policy(policy_id)?;
432
433 let mut perspective = provider.new_perspective(policy_id);
435 sink.begin();
436 if let Err(e) = policy.call_rule(
437 command,
438 &mut perspective,
439 sink,
440 CommandPlacement::OnGraphAtOrigin,
441 ) {
442 sink.rollback();
443 return Err(e.into());
445 }
446 perspective.add_command(command)?;
447
448 let (_, storage) = provider.new_storage(perspective)?;
449
450 sink.commit();
452
453 Ok(storage)
454 }
455}
456
457#[allow(clippy::too_many_arguments)]
459fn make_braid_segment<S, PS, F, MS>(
460 storage: &mut S,
461 left: Location,
462 right: Location,
463 sink: &mut impl Sink<PS::Effect>,
464 policy: &PS::Policy,
465 traversal: &mut TraversalBuffer,
466 braid_buf: &mut BraidBuffer<S::Segment>,
467 make_spill: &MS,
468) -> Result<(S::FactIndex, Location), ClientError>
469where
470 S: Storage,
471 PS: PolicyStore,
472 F: Spill,
473 MS: Fn() -> Result<F, StorageError>,
474{
475 let last_common_ancestor = braiding::last_common_ancestor(storage, left, right)?;
476 let mut order = braiding::braid::<_, F, MS>(
477 storage,
478 left,
479 right,
480 last_common_ancestor,
481 traversal,
482 braid_buf,
483 make_spill,
484 )?;
485
486 let mut iter = order.iter()?;
487 let first = iter.next().assume("braid is non-empty")??;
488
489 let mut braid_perspective = storage.get_fact_perspective(first)?;
490
491 sink.begin();
492
493 while let Some(location) = iter.next().transpose()? {
494 let segment = storage.get_segment(location)?;
495 let command = segment
496 .get_command(location)
497 .assume("braid only contains existing commands")?;
498
499 let result = policy.call_rule(
500 &command,
501 &mut braid_perspective,
502 sink,
503 CommandPlacement::OnGraphInBraid,
504 );
505
506 if let Err(e) = result
508 && !matches!(e, PolicyError::Check)
509 {
510 sink.rollback();
511 return Err(e.into());
512 }
513 }
514
515 let braid = storage.write_facts(braid_perspective)?;
516
517 sink.commit();
518
519 Ok((braid, last_common_ancestor))
520}
521
522fn choose_policy<'a, PS: PolicyStore>(
524 storage: &impl Storage,
525 policy_store: &'a PS,
526 left: Location,
527 right: Location,
528) -> Result<(&'a PS::Policy, PolicyId), ClientError> {
529 Ok(core::cmp::max_by_key(
530 get_policy(storage, policy_store, left)?,
531 get_policy(storage, policy_store, right)?,
532 |(p, _)| p.serial(),
533 ))
534}
535
536fn get_policy<'a, PS: PolicyStore>(
537 storage: &impl Storage,
538 policy_store: &'a PS,
539 location: Location,
540) -> Result<(&'a PS::Policy, PolicyId), ClientError> {
541 let segment = storage.get_segment(location)?;
542 let policy_id = segment.policy();
543 let policy = policy_store.get_policy(policy_id)?;
544 Ok((policy, policy_id))
545}
546
547#[cfg(test)]
548mod test {
549 use std::collections::HashMap;
550
551 use aranya_crypto::id::{Id, IdTag};
552 use buggy::Bug;
553 use test_log::test;
554
555 use super::*;
556 use crate::{
557 Bytes, ClientState, Keys, MaxCut, MemSpill, MergeIds, Perspective, Policy, Priority,
558 policy::{ActionPlacement, CommandPlacement},
559 storage::linear::testing::MemStorageProvider,
560 testing::{hash_for_testing_only, short_b58},
561 };
562
563 struct SeqPolicyStore;
564
565 struct SeqPolicy;
570
571 struct SeqCommand {
572 id: CmdId,
573 prior: Prior<Address>,
574 finalize: bool,
575 data: Box<str>,
576 max_cut: MaxCut,
577 }
578
579 impl PolicyStore for SeqPolicyStore {
580 type Policy = SeqPolicy;
581 type Effect = ();
582
583 fn add_policy(&mut self, _policy: &[u8]) -> Result<PolicyId, PolicyError> {
584 Ok(PolicyId::new(0))
585 }
586
587 fn get_policy(&self, _id: PolicyId) -> Result<&Self::Policy, PolicyError> {
588 Ok(&SeqPolicy)
589 }
590 }
591
592 impl Policy for SeqPolicy {
593 type Action<'a> = &'a str;
594 type Effect = ();
595 type Command<'a> = SeqCommand;
596
597 fn serial(&self) -> u32 {
598 0
599 }
600
601 fn call_rule(
602 &self,
603 command: &impl Command,
604 facts: &mut impl crate::FactPerspective,
605 _sink: &mut impl Sink<Self::Effect>,
606 _placement: CommandPlacement,
607 ) -> Result<(), PolicyError> {
608 assert!(
609 !matches!(command.parent(), Prior::Merge { .. }),
610 "merges shouldn't be evaluated"
611 );
612
613 let data = command.bytes();
614 if !data.starts_with(b"q") {
616 if let Some(seq) = facts
618 .query("seq", &Keys::default())
619 .assume("can query")?
620 .as_deref()
621 {
622 facts
623 .insert(
624 "seq".into(),
625 Keys::default(),
626 [seq, b":", data].concat().into(),
627 )
628 .unwrap();
629 } else {
630 facts
631 .insert("seq".into(), Keys::default(), data.into())
632 .unwrap();
633 }
634 }
635 Ok(())
636 }
637
638 fn call_action(
639 &self,
640 _action: Self::Action<'_>,
641 _facts: &mut impl Perspective,
642 _sink: &mut impl Sink<Self::Effect>,
643 _placement: ActionPlacement,
644 ) -> Result<(), PolicyError> {
645 unimplemented!()
646 }
647
648 fn merge<'a>(
649 &self,
650 _target: &'a mut [u8],
651 ids: MergeIds,
652 ) -> Result<Self::Command<'a>, PolicyError> {
653 let (left, right): (Address, Address) = ids.into();
654 let parents = [*left.id.as_array(), *right.id.as_array()];
655 let id = hash_for_testing_only(parents.as_flattened());
656
657 Ok(SeqCommand::new(
658 id,
659 Prior::Merge(left, right),
660 left.max_cut
661 .max(right.max_cut)
662 .checked_add(1)
663 .assume("must not overflow")?,
664 ))
665 }
666 }
667
668 impl SeqCommand {
669 fn new(id: CmdId, prior: Prior<Address>, max_cut: MaxCut) -> Self {
670 let data = short_b58(id).into_boxed_str();
671 Self {
672 id,
673 prior,
674 finalize: false,
675 data,
676 max_cut,
677 }
678 }
679
680 fn finalize(id: CmdId, prev: Address, max_cut: MaxCut) -> Self {
681 let data = short_b58(id).into_boxed_str();
682 Self {
683 id,
684 prior: Prior::Single(prev),
685 finalize: true,
686 data,
687 max_cut,
688 }
689 }
690 }
691
692 impl Command for SeqCommand {
693 fn priority(&self) -> Priority {
694 if self.finalize {
695 return Priority::Finalize;
696 }
697 match self.prior {
698 Prior::None => Priority::Init,
699 Prior::Single(_) => {
700 let id = self.id.as_bytes();
703 let priority = u32::from(*id.last().unwrap());
704 Priority::Basic(priority)
705 }
706 Prior::Merge(_, _) => Priority::Merge,
707 }
708 }
709
710 fn id(&self) -> CmdId {
711 self.id
712 }
713
714 fn parent(&self) -> Prior<Address> {
715 self.prior
716 }
717
718 fn policy(&self) -> Option<&[u8]> {
719 match self.prior {
722 Prior::None => Some(b""),
723 _ => None,
724 }
725 }
726
727 fn bytes(&self) -> &[u8] {
728 self.data.as_bytes()
729 }
730
731 fn max_cut(&self) -> Result<MaxCut, Bug> {
732 Ok(self.max_cut)
733 }
734 }
735
736 struct NullSink;
737 impl<Eff> Sink<Eff> for NullSink {
738 fn begin(&mut self) {}
739 fn consume(&mut self, _: Eff) {}
740 fn rollback(&mut self) {}
741 fn commit(&mut self) {}
742 }
743
744 struct GraphBuilder<SP: StorageProvider> {
747 client: ClientState<SeqPolicyStore, SP>,
748 trx: Transaction<SP, SeqPolicyStore>,
749 max_cuts: HashMap<CmdId, MaxCut>,
750 buffers: RuntimeBuffers<SP::Segment>,
751 }
752
753 impl<SP: StorageProvider> GraphBuilder<SP> {
754 pub fn init(
755 mut client: ClientState<SeqPolicyStore, SP>,
756 ids: &[CmdId],
757 ) -> Result<Self, ClientError> {
758 let mut trx = Transaction::new(GraphId::transmute(ids[0]));
759 let mut prior: Prior<Address> = Prior::None;
760 let mut max_cuts = HashMap::new();
761 let mut buffers = RuntimeBuffers::new();
762 for (max_cut, &id) in ids.iter().enumerate() {
763 let max_cut = MaxCut::new(max_cut as u64);
764 let cmd = SeqCommand::new(id, prior, max_cut);
765 trx.add_commands(
766 &[cmd],
767 &mut client.provider,
768 &mut client.policy_store,
769 &mut NullSink,
770 &mut buffers,
771 &MemSpill::new,
772 )?;
773 max_cuts.insert(id, max_cut);
774 prior = Prior::Single(Address { id, max_cut });
775 }
776 Ok(Self {
777 client,
778 trx,
779 max_cuts,
780 buffers,
781 })
782 }
783
784 fn get_addr(&self, id: CmdId) -> Address {
785 let max_cut = *self
786 .max_cuts
787 .get(&id)
788 .unwrap_or_else(|| panic!("bad ID {id}"));
789 Address { id, max_cut }
790 }
791
792 pub fn line(&mut self, prev: CmdId, ids: &[CmdId]) -> Result<(), ClientError> {
793 let mut prev = self.get_addr(prev);
794 for &id in ids {
795 let max_cut = prev.max_cut.checked_add(1).unwrap();
796 let cmd = SeqCommand::new(id, Prior::Single(prev), max_cut);
797 self.trx.add_commands(
798 &[cmd],
799 &mut self.client.provider,
800 &mut self.client.policy_store,
801 &mut NullSink,
802 &mut self.buffers,
803 &MemSpill::new,
804 )?;
805 self.max_cuts.insert(id, max_cut);
806 prev = Address { id, max_cut };
807 }
808 Ok(())
809 }
810
811 pub fn finalize(&mut self, prev: CmdId, id: CmdId) -> Result<(), ClientError> {
812 let prev = self.get_addr(prev);
813 let max_cut = prev.max_cut.checked_add(1).unwrap();
814 let cmd = SeqCommand::finalize(id, prev, max_cut);
815 self.trx.add_commands(
816 &[cmd],
817 &mut self.client.provider,
818 &mut self.client.policy_store,
819 &mut NullSink,
820 &mut self.buffers,
821 &MemSpill::new,
822 )?;
823 self.max_cuts.insert(id, max_cut);
824 Ok(())
825 }
826
827 pub fn merge(
828 &mut self,
829 (left, right): (CmdId, CmdId),
830 ids: &[CmdId],
831 ) -> Result<(), ClientError> {
832 let prior = Prior::Merge(self.get_addr(left), self.get_addr(right));
833 let mergecmd = SeqCommand::new(ids[0], prior, prior.next_max_cut().unwrap());
834 let mut prev = Address {
835 id: mergecmd.id,
836 max_cut: mergecmd.max_cut,
837 };
838 self.max_cuts.insert(mergecmd.id, mergecmd.max_cut);
839 self.trx.add_commands(
840 &[mergecmd],
841 &mut self.client.provider,
842 &mut self.client.policy_store,
843 &mut NullSink,
844 &mut self.buffers,
845 &MemSpill::new,
846 )?;
847 for &id in &ids[1..] {
848 let cmd = SeqCommand::new(
849 id,
850 Prior::Single(prev),
851 prev.max_cut.checked_add(1).expect("must not overflow"),
852 );
853 prev = Address {
854 id: cmd.id,
855 max_cut: cmd.max_cut,
856 };
857 self.max_cuts.insert(cmd.id, cmd.max_cut);
858 self.trx.add_commands(
859 &[cmd],
860 &mut self.client.provider,
861 &mut self.client.policy_store,
862 &mut NullSink,
863 &mut self.buffers,
864 &MemSpill::new,
865 )?;
866 }
867 Ok(())
868 }
869
870 pub fn flush(&mut self) {
871 if let Some(p) = Option::take(&mut self.trx.perspective) {
872 self.trx.phead = None;
873 let seg = self
874 .client
875 .provider
876 .get_storage(self.trx.graph_id)
877 .unwrap()
878 .write(p)
879 .unwrap();
880 self.trx
881 .heads
882 .insert(seg.head_id(), seg.head_location().unwrap());
883 }
884 }
885
886 pub fn commit(&mut self) -> Result<(), ClientError> {
887 let graph_id = self.trx.graph_id;
888 let trx = mem::replace(&mut self.trx, Transaction::new(graph_id));
889 assert!(trx.commit(
890 &mut self.client.provider,
891 &mut self.client.policy_store,
892 &mut NullSink,
893 &mut self.buffers,
894 &MemSpill::new,
895 )?);
896 Ok(())
897 }
898 }
899
900 fn mkid<Tag: IdTag>(x: &str) -> Id<Tag> {
901 x.parse().unwrap()
902 }
903
904 macro_rules! graph {
906 ( $client:expr ; $init:literal $($inits:literal )* ; $($rest:tt)*) => {{
907 let mut gb = GraphBuilder::init($client, &[mkid($init), $(mkid($inits)),*]).unwrap();
908 graph!(@ gb, $($rest)*);
909 gb
910 }};
911 (@ $gb:ident, $prev:literal < $($id:literal)+; $($rest:tt)*) => {
912 $gb.line(mkid($prev), &[$(mkid($id)),+]).unwrap();
913 graph!(@ $gb, $($rest)*);
914 };
915 (@ $gb:ident, $l:literal $r:literal < $($id:literal)+; $($rest:tt)*) => {
916 $gb.merge((mkid($l), mkid($r)), &[$(mkid($id)),+]).unwrap();
917 graph!(@ $gb, $($rest)*);
918 };
919 (@ $gb:ident, $prev:literal < finalize $id:literal; $($rest:tt)*) => {
920 $gb.finalize(mkid($prev), mkid($id)).unwrap();
921 graph!(@ $gb, $($rest)*);
922 };
923 (@ $gb:ident, commit; $($rest:tt)*) => {
924 $gb.commit().unwrap();
925 graph!(@ $gb, $($rest)*);
926 };
927 (@ $gb:ident, ) => {
928 $gb.flush();
929 };
930 }
931
932 fn lookup(storage: &impl Storage, name: &str) -> Option<Bytes> {
933 use crate::Query as _;
934 let head = storage.get_head().unwrap();
935 let p = storage.get_fact_perspective(head).unwrap();
936 p.query(name, &[]).unwrap()
937 }
938
939 #[test]
940 fn test_simple() -> Result<(), StorageError> {
941 let mut gb = graph! {
942 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
943 "a";
944 "a" < "b";
945 "a" < "c";
946 "b" "c" < "ma";
947 "b" < "d";
948 "ma" "d" < "mb";
949 commit;
950 };
951 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
952
953 #[cfg(feature = "graphviz")]
954 graphviz::dot(g, "simple");
955
956 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(3));
957
958 let seq = lookup(g, "seq").unwrap();
959 let seq = std::str::from_utf8(&seq).unwrap();
960 assert_eq!(seq, "a:b:d:c");
961
962 Ok(())
963 }
964
965 #[test]
966 fn test_complex() -> Result<(), StorageError> {
967 let mut gb = graph! {
968 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
969 "a";
970 "a" < "1" "2" "3";
971 "3" < "4" "6" "7";
972 "3" < "5" "8";
973 "6" "8" < "9" "aa"; commit;
974 "7" < "a1" "a2";
975 "aa" "a2" < "a3";
976 "a3" < "a6" "a4";
977 "a3" < "a7" "a5";
978 "a4" "a5" < "a8";
979 "9" < "42" "43";
980 "42" < "45" "46";
981 "45" < "47" "48";
982 commit;
983 };
984
985 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
986
987 #[cfg(feature = "graphviz")]
988 graphviz::dot(g, "complex");
989
990 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(15));
991
992 let seq = lookup(g, "seq").unwrap();
993 let seq = std::str::from_utf8(&seq).unwrap();
994 assert_eq!(
995 seq,
996 "a:1:2:3:5:8:4:6:42:45:47:48:46:43:aa:7:a1:a2:a7:a6:a5:a4"
997 );
998
999 Ok(())
1000 }
1001
1002 #[test]
1003 fn test_duplicates() {
1004 let mut gb = graph! {
1005 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1006 "a";
1007 "a" < "b" "c";
1008 "a" < "b";
1009 "b" < "c";
1010 "c" < "d";
1011 commit;
1012 "a" < "b";
1013 "b" < "c";
1014 "d" < "e";
1015 commit;
1016 };
1017
1018 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
1019
1020 #[cfg(feature = "graphviz")]
1021 graphviz::dot(g, "duplicates");
1022
1023 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(4));
1024
1025 let seq = lookup(g, "seq").unwrap();
1026 let seq = std::str::from_utf8(&seq).unwrap();
1027 assert_eq!(seq, "a:b:c:d:e");
1028 }
1029
1030 #[test]
1031 fn test_mid_braid_1() {
1032 let mut gb = graph! {
1033 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1034 "a";
1035 "a" < "b" "c" "d" "e" "f" "g";
1036 "d" < "h" "i" "j";
1037 commit;
1038 };
1039
1040 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
1041
1042 #[cfg(feature = "graphviz")]
1043 graphviz::dot(g, "mid_braid_1");
1044
1045 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(7));
1046
1047 let seq = lookup(g, "seq").unwrap();
1048 let seq = std::str::from_utf8(&seq).unwrap();
1049 assert_eq!(seq, "a:b:c:d:h:i:j:e:f:g");
1050 }
1051
1052 #[test]
1053 fn test_mid_braid_2() {
1054 let mut gb = graph! {
1055 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1056 "a";
1057 "a" < "b" "c" "d" "h" "i" "j";
1058 "d" < "e" "f" "g";
1059 commit;
1060 };
1061
1062 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
1063
1064 #[cfg(feature = "graphviz")]
1065 graphviz::dot(g, "mid_braid_2");
1066
1067 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(7));
1068
1069 let seq = lookup(g, "seq").unwrap();
1070 let seq = std::str::from_utf8(&seq).unwrap();
1071 assert_eq!(seq, "a:b:c:d:h:i:j:e:f:g");
1072 }
1073
1074 #[test]
1075 fn test_sequential_finalize() {
1076 let mut gb = graph! {
1077 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1078 "a";
1079 "a" < "b" "c" "d" "e" "f" "g";
1080 "d" < "h" "i" "j";
1081 "e" < finalize "fff1";
1082 "fff1" < "x" "y";
1083 "y" < finalize "fff2";
1084 commit;
1085 };
1086
1087 let g = gb.client.provider.get_storage(mkid("a")).unwrap();
1088
1089 #[cfg(feature = "graphviz")]
1090 graphviz::dot(g, "finalize_success");
1091
1092 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(9));
1093
1094 let seq = lookup(g, "seq").unwrap();
1095 let seq = std::str::from_utf8(&seq).unwrap();
1096 assert_eq!(seq, "a:b:c:d:e:fff1:x:y:fff2:h:i:j:f:g");
1097 }
1098
1099 #[test]
1100 fn test_parallel_finalize() {
1101 let mut gb = graph! {
1102 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1103 "a";
1104 "a" < "b" "c" "d" "e" "f" "g";
1105 "d" < "h" "i" "j";
1106 "e" < finalize "fff1";
1107 "i" < finalize "fff2";
1108 };
1109 let err = gb.commit().expect_err("merge should fail");
1110 assert!(matches!(err, ClientError::ParallelFinalize), "{err:?}");
1111 }
1112
1113 #[test]
1114 fn test_merge_bug() -> Result<(), StorageError> {
1115 let mut gb = graph! {
1116 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1117 "i";
1118 "i" < "j";
1119 "j" < "qo1";
1120 "j" < "qa1";
1121 "qo1" "qa1" < "m1";
1122 "m1" < "qo2";
1123 "m1" < "qa2";
1124 "qo2" "qa2" < "m2";
1125 "m2" < "h";
1126 commit;
1127 };
1128 let g = gb.client.provider.get_storage(mkid("i")).unwrap();
1129
1130 #[cfg(feature = "graphviz")]
1131 graphviz::dot(g, "merge-bug");
1132
1133 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(6));
1134
1135 let seq = lookup(g, "seq").unwrap();
1136 let seq = std::str::from_utf8(&seq).unwrap();
1137 assert_eq!(seq, "i:j:h");
1138
1139 Ok(())
1140 }
1141
1142 #[test]
1143 fn test_linear_bug() -> Result<(), StorageError> {
1144 let mut gb = graph! {
1145 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1146 "i";
1147 "i" < "j";
1148 commit;
1149 "j" < "qa" "qb";
1150 commit;
1151 "qa" < "c";
1152 "qb" "c" < "m";
1153 commit;
1154 };
1155 let g = gb.client.provider.get_storage(mkid("i")).unwrap();
1156
1157 #[cfg(feature = "graphviz")]
1158 graphviz::dot(g, "linear-bug");
1159
1160 assert_eq!(g.get_head().unwrap().max_cut, MaxCut::new(4));
1161
1162 let seq = lookup(g, "seq").unwrap();
1163 let seq = std::str::from_utf8(&seq).unwrap();
1164 assert_eq!(seq, "i:j:c");
1165
1166 Ok(())
1167 }
1168
1169 #[test]
1170 fn test_fact_convergence_bug() -> Result<(), StorageError> {
1171 let mut gb = graph! {
1172 ClientState::new(SeqPolicyStore, MemStorageProvider::default());
1173 "i";
1174 "i" < "a" "b";
1175 "i" < "c";
1176 "a" "c" < "m1";
1177 "m1" "b" < "m2";
1178 commit;
1179 };
1180 let g = gb.client.provider.get_storage(mkid("i")).unwrap();
1181
1182 #[cfg(feature = "graphviz")]
1183 graphviz::dot(g, "fact-convergence-bug");
1184
1185 let seq = lookup(g, "seq").unwrap();
1186 let seq = std::str::from_utf8(&seq).unwrap();
1187 assert!(
1188 "iabc".chars().all(|c| seq.contains(c)),
1189 "fact missing from {seq:?}"
1190 );
1191
1192 Ok(())
1193 }
1194
1195 #[cfg(feature = "graphviz")]
1196 mod graphviz {
1197 #![allow(clippy::unwrap_used)]
1198
1199 use std::{
1200 collections::{HashSet, VecDeque},
1201 fs::File,
1202 io::BufWriter,
1203 };
1204
1205 use dot_writer::{Attributes as _, DotWriter, Style};
1206
1207 use crate::{
1208 Command as _, FactIndexExtra, Location, Prior, Query, Segment as _, Storage,
1209 testing::short_b58,
1210 };
1211
1212 fn loc(location: impl Into<Location>) -> String {
1213 let location = location.into();
1214 format!("\"{}:{}\"", location.segment, location.max_cut)
1215 }
1216
1217 fn get_seq(p: &impl Query) -> String {
1218 p.query("seq", &[]).unwrap().map_or(String::new(), |seq| {
1219 String::from_utf8(seq.into_vec()).unwrap()
1220 })
1221 }
1222
1223 fn get_segments(storage: &impl Storage) -> Vec<Location> {
1224 let mut locations = Vec::new();
1225 let mut seen_segments = HashSet::new();
1226 let mut segment_queue = VecDeque::new();
1227 segment_queue.push_back(storage.get_head().unwrap());
1228 while let Some(location) = segment_queue.pop_front() {
1229 if !seen_segments.insert(location.segment) {
1230 continue;
1231 }
1232 let segment = storage.get_segment(location).unwrap();
1233 segment_queue.extend(segment.prior());
1234 locations.push(location);
1235 }
1236 locations.sort_by_key(|loc| loc.segment);
1237 locations
1238 }
1239
1240 fn dotwrite(storage: &impl Storage<FactIndex: FactIndexExtra>, out: &mut DotWriter<'_>) {
1241 let mut graph = out.digraph();
1242 graph
1243 .graph_attributes()
1244 .set("compound", "true", false)
1245 .set("rankdir", "RL", false)
1246 .set_style(Style::Filled)
1247 .set("color", "grey", false);
1248 graph
1249 .node_attributes()
1250 .set("shape", "square", false)
1251 .set_style(Style::Filled)
1252 .set("color", "lightgrey", false);
1253
1254 let mut seen_facts = HashSet::new();
1255 let mut external_facts = Vec::new();
1256
1257 let segments = get_segments(storage);
1258
1259 for &location in &segments {
1260 let segment = storage.get_segment(location).unwrap();
1261
1262 let mut cluster = graph.cluster();
1263 match segment.prior() {
1264 Prior::None => {
1265 cluster.graph_attributes().set("color", "green", false);
1266 }
1267 Prior::Single(..) => {}
1268 Prior::Merge(..) => {
1269 cluster.graph_attributes().set("color", "crimson", false);
1270 }
1271 }
1272
1273 for (i, cmd) in segment
1275 .get_from(segment.first_location())
1276 .into_iter()
1277 .enumerate()
1278 {
1279 {
1280 let mut node =
1281 cluster.node_named(loc((segment.index(), cmd.max_cut().unwrap())));
1282 node.set_label(&short_b58(cmd.id()));
1283 match cmd.parent() {
1284 Prior::None => {
1285 node.set("shape", "house", false);
1286 }
1287 Prior::Single(..) => {}
1288 Prior::Merge(..) => {
1289 node.set("shape", "hexagon", false);
1290 }
1291 }
1292 }
1293 if i > 0 {
1294 let previous = cmd.max_cut().unwrap().decremented().expect("i must be > 0");
1295 cluster.edge(
1296 loc((segment.index(), cmd.max_cut().unwrap())),
1297 loc((segment.index(), previous)),
1298 );
1299 }
1300 }
1301
1302 let first = loc(segment.first_location());
1304 for p in segment.prior() {
1305 cluster.edge(&first, loc(p));
1306 }
1307
1308 let facts = segment.facts().unwrap();
1310 let curr = facts.name();
1311 cluster
1312 .node_named(curr.clone())
1313 .set_label(&get_seq(&facts))
1314 .set("shape", "cylinder", false)
1315 .set("color", "black", false)
1316 .set("style", "solid", false);
1317 cluster
1318 .edge(loc(segment.head_location().unwrap()), &curr)
1319 .attributes()
1320 .set("color", "red", false);
1321
1322 seen_facts.insert(curr);
1323
1324 let mut prior = facts.prior().unwrap();
1326 while let Some(node) = prior {
1327 let name = node.name();
1328 if !seen_facts.insert(name) {
1329 break;
1330 }
1331 prior = node.prior().unwrap();
1332 external_facts.push(node);
1333 }
1334 }
1335
1336 graph
1337 .node_attributes()
1338 .set("shape", "cylinder", false)
1339 .set("color", "black", false)
1340 .set("style", "solid", false);
1341
1342 for fact in external_facts {
1343 graph.node_named(fact.name()).set_label(&get_seq(&fact));
1345
1346 if let Some(prior) = fact.prior().unwrap() {
1348 graph
1349 .edge(fact.name(), prior.name())
1350 .attributes()
1351 .set("color", "blue", false);
1352 }
1353 }
1354
1355 for &location in &segments {
1357 let segment = storage.get_segment(location).unwrap();
1358 let facts = segment.facts().unwrap();
1359 if let Some(prior) = facts.prior().unwrap() {
1360 graph
1361 .edge(facts.name(), prior.name())
1362 .attributes()
1363 .set("color", "blue", false);
1364 }
1365 }
1366
1367 graph.node_named("HEAD").set("shape", "none", false);
1369 graph.edge("HEAD", loc(storage.get_head().unwrap()));
1370 }
1371
1372 pub fn dot(storage: &impl Storage<FactIndex: FactIndexExtra>, name: &str) {
1373 std::fs::create_dir_all(".ignore").unwrap();
1374 dotwrite(
1375 storage,
1376 &mut DotWriter::from(&mut BufWriter::new(
1377 File::create(format!(".ignore/{name}.dot")).unwrap(),
1378 )),
1379 );
1380 }
1381 }
1382}