1use crate::encoding::json_schema::{encode_change, export_json_in_id_span};
2pub use crate::encoding::ExportMode;
3use crate::pre_commit::{FirstCommitFromPeerCallback, FirstCommitFromPeerPayload};
4pub use crate::state::analyzer::{ContainerAnalysisInfo, DocAnalysis};
5use crate::sync::{AtomicBool, AtomicUsize};
6pub(crate) use crate::LoroDocInner;
7use crate::{
8 arena::SharedArena,
9 change::{Change, Timestamp},
10 configure::{Configure, DefaultRandom, SecureRandomGenerator, StyleConfig},
11 container::{
12 idx::ContainerIdx, list::list_op::InnerListOp, richtext::config::StyleConfigMap,
13 IntoContainerId,
14 },
15 cursor::{AbsolutePosition, CannotFindRelativePosition, Cursor, PosQueryResult},
16 dag::{Dag, DagUtils},
17 diff_calc::{DiffCalculator, DiffMode},
18 encoding::{
19 self, decode_snapshot, export_fast_snapshot, export_fast_updates,
20 export_fast_updates_in_range, export_shallow_snapshot, export_snapshot_at,
21 export_state_only_snapshot,
22 json_schema::{encode_change_to_json, json::JsonSchema},
23 parse_header_and_body, EncodeMode, ImportBlobMetadata, ImportStatus, ParsedHeaderAndBody,
24 },
25 event::{str_to_path, EventTriggerKind, Index, InternalDocDiff},
26 handler::{Handler, MovableListHandler, TextHandler, TreeHandler, ValueOrHandler},
27 id::PeerID,
28 json::JsonChange,
29 op::InnerContent,
30 oplog::{loro_dag::FrontiersNotIncluded, OpLog},
31 state::DocState,
32 subscription::{LocalUpdateCallback, Observer, Subscriber},
33 undo::DiffBatch,
34 utils::subscription::{SubscriberSetWithQueue, Subscription},
35 version::{shrink_frontiers, Frontiers, ImVersionVector, VersionRange, VersionVectorDiff},
36 ChangeMeta, DocDiff, HandlerTrait, InternalString, ListHandler, LoroDoc, LoroError, MapHandler,
37 VersionVector,
38};
39use crate::{change::ChangeRef, lock::LockKind};
40use crate::{lock::LoroMutexGuard, pre_commit::PreCommitCallback};
41use crate::{
42 lock::{LoroLockGroup, LoroMutex},
43 txn::Transaction,
44};
45use either::Either;
46use loro_common::{
47 ContainerID, ContainerType, HasCounterSpan, HasIdSpan, HasLamportSpan, IdSpan, LoroEncodeError,
48 LoroResult, LoroValue, ID,
49};
50use rle::HasLength;
51use rustc_hash::{FxHashMap, FxHashSet};
52use std::{
53 borrow::Cow,
54 cmp::Ordering,
55 collections::{hash_map::Entry, BinaryHeap},
56 ops::ControlFlow,
57 sync::{
58 atomic::Ordering::{Acquire, Release},
59 Arc,
60 },
61};
62use tracing::{debug_span, info_span, instrument, warn};
63
64impl Default for LoroDoc {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl std::fmt::Debug for LoroDocInner {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.debug_struct("LoroDoc")
73 .field("config", &self.config)
74 .field("auto_commit", &self.auto_commit)
75 .field("detached", &self.detached)
76 .finish()
77 }
78}
79
80impl LoroDoc {
81 #[inline]
89 pub fn with_barrier<F, R>(&self, f: F) -> R
90 where
91 F: FnOnce() -> R,
92 {
93 let (options, guard) = self.implicit_commit_then_stop();
94 let result = f();
95 drop(guard);
96 self.renew_txn_if_auto_commit(options);
97 result
98 }
99
100 pub fn new() -> Self {
101 let visible_op_count = Arc::new(AtomicUsize::new(0));
102 let oplog = OpLog::new(visible_op_count.clone());
103 let arena = oplog.arena.clone();
104 let config: Configure = oplog.configure.clone();
105 let lock_group = LoroLockGroup::new();
106 let global_txn = Arc::new(lock_group.new_lock(None, LockKind::Txn));
107 let inner = Arc::new_cyclic(|w| {
108 let state = DocState::new_arc(w.clone(), arena.clone(), config.clone(), &lock_group);
109 LoroDocInner {
110 oplog: Arc::new(lock_group.new_lock(oplog, LockKind::OpLog)),
111 state,
112 config,
113 visible_op_count,
114 detached: AtomicBool::new(false),
115 auto_commit: AtomicBool::new(false),
116 observer: Arc::new(Observer::new(arena.clone())),
117 diff_calculator: Arc::new(
118 lock_group.new_lock(DiffCalculator::new(true), LockKind::DiffCalculator),
119 ),
120 txn: global_txn,
121 arena,
122 local_update_subs: SubscriberSetWithQueue::new(),
123 peer_id_change_subs: SubscriberSetWithQueue::new(),
124 pre_commit_subs: SubscriberSetWithQueue::new(),
125 first_commit_from_peer_subs: SubscriberSetWithQueue::new(),
126 }
127 });
128 LoroDoc { inner }
129 }
130
131 pub fn fork(&self) -> Self {
132 if self.is_detached() {
133 return self
134 .fork_at(&self.state_frontiers())
135 .expect("fork_at on detached doc should not fail");
136 }
137
138 let snapshot = self
139 .with_barrier(|| encoding::fast_snapshot::encode_snapshot_inner(self))
140 .expect("forking a valid document should encode a snapshot");
141 let doc = Self::new();
142 doc.with_barrier(|| {
143 encoding::fast_snapshot::decode_snapshot_inner(snapshot, &doc, Default::default())
144 })
145 .unwrap();
146 doc.set_config(&self.config);
147 if self.auto_commit.load(std::sync::atomic::Ordering::Relaxed) {
148 doc.start_auto_commit();
149 }
150 doc
151 }
152 pub fn set_detached_editing(&self, enable: bool) {
168 self.config.set_detached_editing(enable);
169 if enable && self.is_detached() {
170 self.with_barrier(|| {
171 self.renew_peer_id();
172 });
173 }
174 }
175
176 #[inline]
178 pub fn new_auto_commit() -> Self {
179 let doc = Self::new();
180 doc.start_auto_commit();
181 doc
182 }
183
184 #[inline(always)]
185 pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
186 if peer == PeerID::MAX {
187 return Err(LoroError::InvalidPeerID);
188 }
189 let next_id = self.oplog.lock().next_id(peer);
190 if self.auto_commit.load(Acquire) {
191 let doc_state = self.state.lock();
192 doc_state
193 .peer
194 .store(peer, std::sync::atomic::Ordering::Relaxed);
195
196 if doc_state.is_in_txn() {
197 drop(doc_state);
198 self.with_barrier(|| {});
200 }
201 self.peer_id_change_subs.emit(&(), next_id);
202 return Ok(());
203 }
204
205 let doc_state = self.state.lock();
206 if doc_state.is_in_txn() {
207 return Err(LoroError::TransactionError(
208 "Cannot change peer id during transaction"
209 .to_string()
210 .into_boxed_str(),
211 ));
212 }
213
214 doc_state
215 .peer
216 .store(peer, std::sync::atomic::Ordering::Relaxed);
217 drop(doc_state);
218 self.peer_id_change_subs.emit(&(), next_id);
219 Ok(())
220 }
221
222 pub(crate) fn renew_peer_id(&self) {
224 let mut peer_id = DefaultRandom.next_u64();
225 while peer_id == PeerID::MAX {
226 peer_id = DefaultRandom.next_u64();
227 }
228 self.set_peer_id(peer_id).unwrap();
229 }
230
231 #[inline]
243 #[must_use]
244 pub fn implicit_commit_then_stop(
245 &self,
246 ) -> (
247 Option<CommitOptions>,
248 LoroMutexGuard<'_, Option<Transaction>>,
249 ) {
250 let (a, b) = self.commit_internal(CommitOptions::new().immediate_renew(false), true);
252 (a, b.unwrap())
253 }
254
255 #[inline]
260 pub fn commit_then_renew(&self) -> Option<CommitOptions> {
261 self.commit_internal(CommitOptions::new().immediate_renew(true), false)
263 .0
264 }
265
266 fn before_commit(&self) -> Option<LoroMutexGuard<'_, Option<Transaction>>> {
271 let mut txn_guard = self.txn.lock();
272 let Some(txn) = txn_guard.as_mut() else {
273 return Some(txn_guard);
274 };
275
276 if txn.is_peer_first_appearance {
277 txn.is_peer_first_appearance = false;
278 drop(txn_guard);
279 self.first_commit_from_peer_subs.emit(
281 &(),
282 FirstCommitFromPeerPayload {
283 peer: self.peer_id(),
284 },
285 );
286 }
287
288 None
289 }
290
291 #[instrument(skip_all)]
299 fn commit_internal(
300 &self,
301 config: CommitOptions,
302 preserve_on_empty: bool,
303 ) -> (
304 Option<CommitOptions>,
305 Option<LoroMutexGuard<'_, Option<Transaction>>>,
306 ) {
307 if !self.auto_commit.load(Acquire) {
308 let txn_guard = self.txn.lock();
309 return (None, Some(txn_guard));
312 }
313
314 loop {
315 if let Some(txn_guard) = self.before_commit() {
316 return (None, Some(txn_guard));
317 }
318
319 let mut txn_guard = self.txn.lock();
320 let txn = txn_guard.take();
321 let Some(mut txn) = txn else {
322 return (None, Some(txn_guard));
323 };
324 let on_commit = txn.take_on_commit();
325 if let Some(origin) = config.origin.clone() {
326 txn.set_origin(origin);
327 }
328
329 if let Some(timestamp) = config.timestamp {
330 txn.set_timestamp(timestamp);
331 }
332
333 if let Some(msg) = config.commit_msg.as_ref() {
334 txn.set_msg(Some(msg.clone()));
335 }
336
337 let id_span = txn.id_span();
338 let mut options = txn.commit().unwrap();
339 if let Some(opts) = options.as_mut() {
341 if config.origin.is_some() {
343 opts.set_origin(None);
344 }
345 if !preserve_on_empty {
347 options = None;
348 }
349 }
350 if config.immediate_renew && self.can_edit() {
351 let mut t = self.txn().unwrap();
352 if let Some(options) = options.as_ref() {
353 t.set_options(options.clone());
354 }
355 *txn_guard = Some(t);
356 }
357
358 if let Some(on_commit) = on_commit {
359 drop(txn_guard);
360 on_commit(&self.state, &self.oplog, id_span);
361 txn_guard = self.txn.lock();
362 if !config.immediate_renew && txn_guard.is_some() {
363 continue;
365 }
366 }
367
368 return (
369 options,
370 if !config.immediate_renew {
371 Some(txn_guard)
372 } else {
373 None
374 },
375 );
376 }
377 }
378
379 #[instrument(skip_all)]
384 pub fn commit_with(
385 &self,
386 config: CommitOptions,
387 ) -> (
388 Option<CommitOptions>,
389 Option<LoroMutexGuard<'_, Option<Transaction>>>,
390 ) {
391 self.commit_internal(config, false)
392 }
393
394 pub fn set_next_commit_message(&self, message: &str) {
396 let mut binding = self.txn.lock();
397 let Some(txn) = binding.as_mut() else {
398 return;
399 };
400
401 if message.is_empty() {
402 txn.set_msg(None)
403 } else {
404 txn.set_msg(Some(message.into()))
405 }
406 }
407
408 pub fn set_next_commit_origin(&self, origin: &str) {
410 let mut txn = self.txn.lock();
411 if let Some(txn) = txn.as_mut() {
412 txn.set_origin(origin.into());
413 }
414 }
415
416 pub fn set_next_commit_timestamp(&self, timestamp: Timestamp) {
418 let mut txn = self.txn.lock();
419 if let Some(txn) = txn.as_mut() {
420 txn.set_timestamp(timestamp);
421 }
422 }
423
424 pub fn set_next_commit_options(&self, options: CommitOptions) {
426 let mut txn = self.txn.lock();
427 if let Some(txn) = txn.as_mut() {
428 txn.set_options(options);
429 }
430 }
431
432 pub fn clear_next_commit_options(&self) {
434 let mut txn = self.txn.lock();
435 if let Some(txn) = txn.as_mut() {
436 txn.set_options(CommitOptions::new());
437 }
438 }
439
440 #[inline]
451 pub fn set_record_timestamp(&self, record: bool) {
452 self.config.set_record_timestamp(record);
453 }
454
455 #[inline]
460 pub fn set_change_merge_interval(&self, interval: i64) {
461 self.config.set_merge_interval(interval);
462 }
463
464 pub fn can_edit(&self) -> bool {
465 !self.is_detached() || self.config.detached_editing()
466 }
467
468 pub fn is_detached_editing_enabled(&self) -> bool {
469 self.config.detached_editing()
470 }
471
472 #[inline]
473 pub fn config_text_style(&self, text_style: StyleConfigMap) {
474 self.config.text_style_config.write().map = text_style.map;
475 }
476
477 #[inline]
478 pub fn config_default_text_style(&self, text_style: Option<StyleConfig>) {
479 self.config.text_style_config.write().default_style = text_style;
480 }
481 pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
482 let doc = Self::new();
483 let ParsedHeaderAndBody { mode, body, .. } = parse_header_and_body(bytes, true)?;
484 if mode.is_snapshot() {
485 doc.with_barrier(|| -> Result<(), LoroError> {
486 decode_snapshot(&doc, mode, body, Default::default())?;
487 Ok(())
488 })?;
489 Ok(doc)
490 } else {
491 Err(LoroError::DecodeError(
492 "Invalid encode mode".to_string().into(),
493 ))
494 }
495 }
496
497 #[inline(always)]
499 pub fn can_reset_with_snapshot(&self) -> bool {
500 let oplog = self.oplog.lock();
501 if oplog.batch_importing {
502 return false;
503 }
504
505 if self.is_detached() {
506 return false;
507 }
508
509 oplog.is_empty() && self.state.lock().can_import_snapshot()
510 }
511
512 #[inline(always)]
518 pub fn is_detached(&self) -> bool {
519 self.detached.load(Acquire)
520 }
521
522 pub(crate) fn set_detached(&self, detached: bool) {
523 self.detached.store(detached, Release);
524 }
525
526 #[inline(always)]
527 pub fn peer_id(&self) -> PeerID {
528 self.state
529 .lock()
530 .peer
531 .load(std::sync::atomic::Ordering::Relaxed)
532 }
533
534 #[inline(always)]
535 pub fn detach(&self) {
536 self.with_barrier(|| self.set_detached(true));
537 }
538
539 #[inline(always)]
540 pub fn attach(&self) {
541 self.checkout_to_latest()
542 }
543
544 pub fn state_timestamp(&self) -> Timestamp {
547 let f = { self.state.lock().frontiers.clone() };
549 self.oplog.lock().get_timestamp_of_version(&f)
550 }
551
552 #[inline(always)]
553 pub fn app_state(&self) -> &Arc<LoroMutex<DocState>> {
554 &self.state
555 }
556
557 #[inline]
558 pub fn get_state_deep_value(&self) -> LoroValue {
559 self.state.lock().get_deep_value()
560 }
561
562 #[inline(always)]
563 pub fn oplog(&self) -> &Arc<LoroMutex<OpLog>> {
564 &self.oplog
565 }
566
567 #[inline(always)]
568 pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError> {
569 let s = debug_span!("import", peer = self.peer_id());
570 let _e = s.enter();
571 self.import_with(bytes, Default::default())
572 }
573
574 #[inline]
575 pub fn import_with(
576 &self,
577 bytes: &[u8],
578 origin: InternalString,
579 ) -> Result<ImportStatus, LoroError> {
580 self.with_barrier(|| self._import_with(bytes, origin))
581 }
582
583 #[tracing::instrument(skip_all)]
584 fn _import_with(
585 &self,
586 bytes: &[u8],
587 origin: InternalString,
588 ) -> Result<ImportStatus, LoroError> {
589 ensure_cov::notify_cov("loro_internal::import");
590 let parsed = parse_header_and_body(bytes, true)?;
591 loro_common::info!("Importing with mode={:?}", &parsed.mode);
592 let result = match parsed.mode {
593 EncodeMode::OutdatedRle => {
594 if self.state.lock().is_in_txn() {
595 return Err(LoroError::ImportWhenInTxn);
596 }
597
598 let s = tracing::span!(
599 tracing::Level::INFO,
600 "Import updates ",
601 peer = self.peer_id()
602 );
603 let _e = s.enter();
604 self.update_oplog_and_apply_delta_to_state_if_needed(
605 |oplog| oplog.decode(parsed),
606 origin,
607 )
608 }
609 EncodeMode::OutdatedSnapshot => {
610 if self.can_reset_with_snapshot() {
611 loro_common::info!("Init by snapshot {}", self.peer_id());
612 decode_snapshot(self, parsed.mode, parsed.body, origin)
613 } else {
614 self.update_oplog_and_apply_delta_to_state_if_needed(
615 |oplog| oplog.decode(parsed),
616 origin,
617 )
618 }
619 }
620 EncodeMode::FastSnapshot => {
621 if self.can_reset_with_snapshot() {
622 ensure_cov::notify_cov("loro_internal::import::snapshot");
623 loro_common::info!("Init by fast snapshot {}", self.peer_id());
624 decode_snapshot(self, parsed.mode, parsed.body, origin)
625 } else {
626 self.import_changes_and_apply_delta_to_state_if_needed(
627 |oplog| encoding::decode_oplog_changes(oplog, parsed),
628 origin,
629 )
630
631 }
636 }
637 EncodeMode::FastUpdates => self.import_changes_and_apply_delta_to_state_if_needed(
638 |oplog| encoding::decode_oplog_changes(oplog, parsed),
639 origin,
640 ),
641 EncodeMode::Auto => {
642 unreachable!()
643 }
644 };
645
646 self.emit_events();
647
648 result
649 }
650
651 #[tracing::instrument(skip_all)]
652 pub(crate) fn update_oplog_and_apply_delta_to_state_if_needed(
653 &self,
654 f: impl FnOnce(&mut OpLog) -> Result<ImportStatus, LoroError>,
655 origin: InternalString,
656 ) -> Result<ImportStatus, LoroError> {
657 let mut oplog = self.oplog.lock();
658 oplog.begin_import_rollback();
659 if !self.is_detached() {
660 let old_vv = oplog.vv().clone();
661 let old_frontiers = oplog.frontiers().clone();
662 let result = f(&mut oplog);
663 if &old_vv != oplog.vv() {
664 let mut diff = DiffCalculator::new(false);
665 let (diff, diff_mode) = diff.calc_diff_internal(
666 &oplog,
667 &old_vv,
668 &old_frontiers,
669 oplog.vv(),
670 oplog.dag.get_frontiers(),
671 None,
672 );
673 let mut state = self.state.lock();
674 if let Err(e) = state.apply_diff(
675 InternalDocDiff {
676 origin,
677 diff: (diff).into(),
678 by: EventTriggerKind::Import,
679 new_version: Cow::Owned(oplog.frontiers().clone()),
680 },
681 diff_mode,
682 ) {
683 oplog.rollback_import();
684 return Err(e);
685 }
686 }
687 match result {
688 Ok(result) => {
689 oplog.commit_import_rollback();
690 Ok(result)
691 }
692 Err(e) => {
693 if &old_vv == oplog.vv() {
698 oplog.rollback_import();
699 } else {
700 oplog.commit_import_rollback();
701 }
702 Err(e)
703 }
704 }
705 } else {
706 match f(&mut oplog) {
707 Ok(result) => {
708 oplog.commit_import_rollback();
709 Ok(result)
710 }
711 Err(e) => {
712 oplog.rollback_import();
713 Err(e)
714 }
715 }
716 }
717 }
718
719 #[tracing::instrument(skip_all)]
720 pub(crate) fn import_changes_and_apply_delta_to_state_if_needed(
721 &self,
722 decode_changes: impl FnOnce(&mut OpLog) -> Result<Vec<Change>, LoroError>,
723 origin: InternalString,
724 ) -> Result<ImportStatus, LoroError> {
725 let mut oplog = self.oplog.lock();
726 let arena_checkpoint = oplog.arena.checkpoint_for_rollback();
727 let changes = match decode_changes(&mut oplog) {
728 Ok(changes) => changes,
729 Err(e) => {
730 oplog.arena.rollback(arena_checkpoint);
731 return Err(e);
732 }
733 };
734
735 let preflight = oplog.preflight_import_changes(&changes);
736 if preflight.has_deps_before_shallow_root
737 && (self.is_detached() || !preflight.applies_to_dag)
738 {
739 oplog.arena.rollback(arena_checkpoint);
740 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
741 }
742
743 if self.is_detached() {
744 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
745 if result.has_deps_before_shallow_root {
746 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
747 }
748
749 return Ok(result.status);
750 }
751
752 if !preflight.applies_to_dag {
753 let pending_root_containers = pending_root_containers_to_materialize(&oplog, &changes);
754 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
755 if result.has_deps_before_shallow_root {
756 oplog.arena.rollback(arena_checkpoint);
757 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
758 }
759
760 if !pending_root_containers.is_empty() {
761 let mut state = self.state.lock();
762 for id in pending_root_containers {
763 state.ensure_container(&id);
764 }
765 }
766
767 return Ok(result.status);
768 }
769
770 let old_vv = oplog.vv().clone();
771 let old_frontiers = oplog.frontiers().clone();
772 let isolated_batch = isolated_scalar_root_batch(&oplog, &changes).filter(|(_, names)| {
774 let mut state = self.state.lock();
775 names.iter().all(|name| {
776 let id = ContainerID::new_root(name, ContainerType::Map);
777 !state.store.contains_id(&id)
778 })
779 });
780 let rollback_enabled = preflight.needs_state_apply_rollback;
781 if rollback_enabled {
782 oplog.begin_import_rollback_with_arena(arena_checkpoint);
783 }
784
785 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
786 if &old_vv != oplog.vv() {
787 let mut diff = DiffCalculator::new(false);
788 let isolated_batch = isolated_batch.filter(|(component_vv, _)| {
791 component_vv
792 .iter()
793 .all(|(peer, end)| oplog.vv().get(peer) == Some(end))
794 && oplog.vv().iter().all(|(peer, end)| {
795 let old_end = old_vv.get(peer).copied().unwrap_or(0);
796 let component_end = component_vv.get(peer).copied().unwrap_or(0);
797 *end == old_end.max(component_end)
798 })
799 });
800 let (diff, diff_mode) = if let Some((component_vv, _)) = isolated_batch {
801 let component_frontiers = oplog.dag.vv_to_frontiers(&component_vv);
802 let (diff, _) = diff.calc_diff_internal(
803 &oplog,
804 &VersionVector::default(),
805 &Frontiers::default(),
806 &component_vv,
807 &component_frontiers,
808 None,
809 );
810 (diff, DiffMode::Import)
811 } else {
812 diff.calc_diff_internal(
813 &oplog,
814 &old_vv,
815 &old_frontiers,
816 oplog.vv(),
817 oplog.dag.get_frontiers(),
818 None,
819 )
820 };
821 let mut state = self.state.lock();
822 if let Err(e) = state.apply_diff(
823 InternalDocDiff {
824 origin,
825 diff: (diff).into(),
826 by: EventTriggerKind::Import,
827 new_version: Cow::Owned(oplog.frontiers().clone()),
828 },
829 diff_mode,
830 ) {
831 if rollback_enabled {
832 oplog.rollback_import();
833 return Err(e);
834 }
835
836 panic!("state apply returned Err for import without rollback guard: {e}");
837 }
838 }
839
840 if result.has_deps_before_shallow_root {
841 if rollback_enabled {
842 oplog.commit_import_rollback();
843 }
844 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
845 }
846
847 if rollback_enabled {
848 oplog.commit_import_rollback();
849 }
850 Ok(result.status)
851 }
852
853 fn emit_events(&self) {
854 let events = {
856 let mut state = self.state.lock();
857 state.take_events()
858 };
859 for event in events {
860 self.observer.emit(event);
861 }
862 }
863
864 pub(crate) fn drop_pending_events(&self) -> Vec<DocDiff> {
865 let mut state = self.state.lock();
866 state.take_events()
867 }
868
869 #[tracing::instrument(skip_all)]
873 pub fn import_json_updates<T: TryInto<JsonSchema>>(&self, json: T) -> LoroResult<ImportStatus> {
874 let json = json.try_into().map_err(|_| LoroError::InvalidJsonSchema)?;
875 self.with_barrier(|| {
876 let result = self.import_changes_and_apply_delta_to_state_if_needed(
877 |oplog| crate::encoding::json_schema::decode_json_changes(json, &oplog.arena),
878 Default::default(),
879 );
880 self.emit_events();
881 result
882 })
883 }
884
885 pub fn export_json_updates(
886 &self,
887 start_vv: &VersionVector,
888 end_vv: &VersionVector,
889 with_peer_compression: bool,
890 ) -> JsonSchema {
891 self.with_barrier(|| {
892 let oplog = self.oplog.lock();
893 let mut start_vv = start_vv;
894 let _temp: Option<VersionVector>;
895 if !oplog.dag.shallow_since_vv().is_empty() {
896 let mut include_all = true;
898 for (peer, counter) in oplog.dag.shallow_since_vv().iter() {
899 if start_vv.get(peer).unwrap_or(&0) < counter {
900 include_all = false;
901 break;
902 }
903 }
904 if !include_all {
905 let mut vv = start_vv.clone();
906 for (&peer, &counter) in oplog.dag.shallow_since_vv().iter() {
907 vv.extend_to_include_end_id(ID::new(peer, counter));
908 }
909 _temp = Some(vv);
910 start_vv = _temp.as_ref().unwrap();
911 }
912 }
913
914 crate::encoding::json_schema::export_json(
915 &oplog,
916 start_vv,
917 end_vv,
918 with_peer_compression,
919 )
920 })
921 }
922
923 pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange> {
924 let oplog = self.oplog.lock();
925 let mut changes = export_json_in_id_span(&oplog, id_span);
926 if let Some(uncommit) = oplog.get_uncommitted_change_in_span(id_span) {
927 let change_json = encode_change(ChangeRef::from_change(&uncommit), &self.arena, None);
928 changes.push(change_json);
929 }
930 changes
931 }
932
933 #[inline]
935 pub fn oplog_vv(&self) -> VersionVector {
936 self.oplog.lock().vv().clone()
937 }
938
939 #[inline]
941 pub fn state_vv(&self) -> VersionVector {
942 let oplog = self.oplog.lock();
943 let f = &self.state.lock().frontiers;
944 oplog.dag.frontiers_to_vv(f).unwrap()
945 }
946
947 pub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrHandler> {
948 let value: LoroValue = self.state.lock().get_value_by_path(path)?;
949 if let LoroValue::Container(c) = value {
950 Some(ValueOrHandler::Handler(Handler::new_attached(
951 c.clone(),
952 self.clone(),
953 )))
954 } else {
955 Some(ValueOrHandler::Value(value))
956 }
957 }
958
959 pub fn get_by_str_path(&self, path: &str) -> Option<ValueOrHandler> {
961 let path = str_to_path(path)?;
962 self.get_by_path(&path)
963 }
964
965 pub fn get_uncommitted_ops_as_json(&self) -> Option<JsonSchema> {
966 let arena = &self.arena;
967 let txn = self.txn.lock();
968 let txn = txn.as_ref()?;
969 let ops_ = txn.local_ops();
970 let new_id = ID {
971 peer: *txn.peer(),
972 counter: ops_.first()?.counter,
973 };
974 let change = ChangeRef {
975 id: &new_id,
976 deps: txn.frontiers(),
977 timestamp: &txn
978 .timestamp()
979 .as_ref()
980 .copied()
981 .unwrap_or_else(|| self.oplog.lock().get_timestamp_for_next_txn()),
982 commit_msg: txn.msg(),
983 ops: ops_,
984 lamport: txn.lamport(),
985 };
986 let json = encode_change_to_json(change, arena);
987 Some(json)
988 }
989
990 #[inline]
991 pub fn get_handler(&self, id: ContainerID) -> Option<Handler> {
992 if self.has_container(&id) {
993 self.ensure_root_container(&id);
994 Some(Handler::new_attached(id, self.clone()))
995 } else {
996 None
997 }
998 }
999
1000 #[inline]
1001 fn ensure_root_container(&self, id: &ContainerID) {
1002 if id.is_root() && !id.is_mergeable() {
1006 self.state.lock().ensure_container(id);
1007 }
1008 }
1009
1010 #[inline]
1013 pub fn try_get_text<I: IntoContainerId>(&self, id: I) -> Option<TextHandler> {
1014 let id = id.into_container_id(&self.arena, ContainerType::Text);
1015 if !self.has_container(&id) {
1016 return None;
1017 }
1018 self.ensure_root_container(&id);
1019 Handler::new_attached(id, self.clone()).into_text().ok()
1020 }
1021
1022 #[inline]
1025 pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
1026 self.try_get_text(id)
1027 .expect("The container does not exist in the document. Use `try_get_text` or `get_container` to check for existence.")
1028 }
1029
1030 #[inline]
1033 pub fn try_get_list<I: IntoContainerId>(&self, id: I) -> Option<ListHandler> {
1034 let id = id.into_container_id(&self.arena, ContainerType::List);
1035 if !self.has_container(&id) {
1036 return None;
1037 }
1038 self.ensure_root_container(&id);
1039 Handler::new_attached(id, self.clone()).into_list().ok()
1040 }
1041
1042 #[inline]
1045 pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
1046 self.try_get_list(id)
1047 .expect("The container does not exist in the document. Use `try_get_list` or `get_container` to check for existence.")
1048 }
1049
1050 #[inline]
1053 pub fn try_get_movable_list<I: IntoContainerId>(&self, id: I) -> Option<MovableListHandler> {
1054 let id = id.into_container_id(&self.arena, ContainerType::MovableList);
1055 if !self.has_container(&id) {
1056 return None;
1057 }
1058 self.ensure_root_container(&id);
1059 Handler::new_attached(id, self.clone())
1060 .into_movable_list()
1061 .ok()
1062 }
1063
1064 #[inline]
1067 pub fn get_movable_list<I: IntoContainerId>(&self, id: I) -> MovableListHandler {
1068 self.try_get_movable_list(id)
1069 .expect("The container does not exist in the document. Use `try_get_movable_list` or `get_container` to check for existence.")
1070 }
1071
1072 #[inline]
1075 pub fn try_get_map<I: IntoContainerId>(&self, id: I) -> Option<MapHandler> {
1076 let id = id.into_container_id(&self.arena, ContainerType::Map);
1077 if !self.has_container(&id) {
1078 return None;
1079 }
1080 self.ensure_root_container(&id);
1081 Handler::new_attached(id, self.clone()).into_map().ok()
1082 }
1083
1084 #[inline]
1087 pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
1088 self.try_get_map(id)
1089 .expect("The container does not exist in the document. Use `try_get_map` or `get_container` to check for existence.")
1090 }
1091
1092 #[inline]
1095 pub fn try_get_tree<I: IntoContainerId>(&self, id: I) -> Option<TreeHandler> {
1096 let id = id.into_container_id(&self.arena, ContainerType::Tree);
1097 if !self.has_container(&id) {
1098 return None;
1099 }
1100 self.ensure_root_container(&id);
1101 Handler::new_attached(id, self.clone()).into_tree().ok()
1102 }
1103
1104 #[inline]
1107 pub fn get_tree<I: IntoContainerId>(&self, id: I) -> TreeHandler {
1108 self.try_get_tree(id)
1109 .expect("The container does not exist in the document. Use `try_get_tree` or `get_container` to check for existence.")
1110 }
1111
1112 #[cfg(feature = "counter")]
1113 pub fn try_get_counter<I: IntoContainerId>(
1114 &self,
1115 id: I,
1116 ) -> Option<crate::handler::counter::CounterHandler> {
1117 let id = id.into_container_id(&self.arena, ContainerType::Counter);
1118 if !self.has_container(&id) {
1119 return None;
1120 }
1121 self.ensure_root_container(&id);
1122 Handler::new_attached(id, self.clone()).into_counter().ok()
1123 }
1124
1125 #[cfg(feature = "counter")]
1126 pub fn get_counter<I: IntoContainerId>(
1127 &self,
1128 id: I,
1129 ) -> crate::handler::counter::CounterHandler {
1130 self.try_get_counter(id)
1131 .expect("The container does not exist in the document. Use `try_get_counter` or `get_container` to check for existence.")
1132 }
1133
1134 #[must_use]
1135 pub fn has_container(&self, id: &ContainerID) -> bool {
1136 if id.is_root() && !id.is_mergeable() {
1137 return true;
1138 }
1139
1140 let exist = self.state.lock().does_container_exist(id);
1141 exist
1142 }
1143
1144 #[instrument(level = "info", skip_all)]
1158 pub fn undo_internal(
1159 &self,
1160 id_span: IdSpan,
1161 container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1162 post_transform_base: Option<&DiffBatch>,
1163 before_diff: &mut dyn FnMut(&DiffBatch),
1164 ) -> LoroResult<CommitWhenDrop<'_>> {
1165 if !self.can_edit() {
1166 return Err(LoroError::EditWhenDetached);
1167 }
1168
1169 let (options, txn) = self.implicit_commit_then_stop();
1170 if !self.oplog().lock().vv().includes_id(id_span.id_last()) {
1171 self.renew_txn_if_auto_commit(options);
1172 return Err(LoroError::UndoInvalidIdSpan(id_span.id_last()));
1173 }
1174
1175 let (was_recording, latest_frontiers) = {
1176 let mut state = self.state.lock();
1177 let was_recording = state.is_recording();
1178 state.stop_and_clear_recording();
1179 (was_recording, state.frontiers.clone())
1180 };
1181
1182 let spans = self.oplog.lock().split_span_based_on_deps(id_span);
1183 let diff = crate::undo::undo(
1184 spans,
1185 match post_transform_base {
1186 Some(d) => Either::Right(d),
1187 None => Either::Left(&latest_frontiers),
1188 },
1189 |from, to| {
1190 self._checkout_without_emitting(from, false, false).unwrap();
1191 self.state.lock().start_recording();
1192 self._checkout_without_emitting(to, false, false).unwrap();
1193 let mut state = self.state.lock();
1194 let e = state.take_events();
1195 state.stop_and_clear_recording();
1196 DiffBatch::new(e)
1197 },
1198 before_diff,
1199 );
1200
1201 self._checkout_without_emitting(&latest_frontiers, false, false)?;
1205 self.set_detached(false);
1206 if was_recording {
1207 self.state.lock().start_recording();
1208 }
1209 drop(txn);
1210 self.start_auto_commit();
1211 if let Err(e) = self._apply_diff(diff, container_remap, true) {
1215 warn!("Undo Failed {:?}", e);
1216 }
1217
1218 if let Some(options) = options {
1219 self.set_next_commit_options(options);
1220 }
1221 Ok(CommitWhenDrop {
1222 doc: self,
1223 default_options: CommitOptions::new().origin("undo"),
1224 })
1225 }
1226
1227 pub fn revert_to(&self, target: &Frontiers) -> LoroResult<()> {
1233 let f = self.state_frontiers();
1236 let diff = self.diff(&f, target)?;
1237 self._apply_diff(diff, &mut Default::default(), false)
1238 }
1239
1240 pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<DiffBatch> {
1245 {
1246 let oplog = self.oplog.lock();
1249 let validate_frontiers = |frontiers: &Frontiers| -> LoroResult<()> {
1250 for id in frontiers.iter() {
1251 if !oplog.dag.contains(id) {
1252 return Err(LoroError::FrontiersNotFound(id));
1253 }
1254 }
1255
1256 if oplog.dag.is_before_shallow_root(frontiers) {
1257 return Err(LoroError::SwitchToVersionBeforeShallowRoot);
1258 }
1259
1260 Ok(())
1261 };
1262
1263 validate_frontiers(a)?;
1264 validate_frontiers(b)?;
1265 }
1266
1267 let (options, txn) = self.implicit_commit_then_stop();
1268 let was_detached = self.is_detached();
1269 let old_frontiers = self.state_frontiers();
1270 let was_recording = {
1271 let mut state = self.state.lock();
1272 let is_recording = state.is_recording();
1273 state.stop_and_clear_recording();
1274 is_recording
1275 };
1276 let result = (|| {
1277 self._checkout_without_emitting(a, true, false)?;
1278 self.state.lock().start_recording();
1279 self._checkout_without_emitting(b, true, false)?;
1280 let mut state = self.state.lock();
1281 let e = state.take_events();
1282 state.stop_and_clear_recording();
1283 Ok::<_, LoroError>(e)
1284 })();
1285
1286 self._checkout_without_emitting(&old_frontiers, false, false)
1288 .unwrap();
1289 drop(txn);
1290 if !was_detached {
1291 self.set_detached(false);
1292 self.renew_txn_if_auto_commit(options);
1293 }
1294 if was_recording {
1295 self.state.lock().start_recording();
1296 }
1297 result.map(DiffBatch::new)
1298 }
1299
1300 #[inline(always)]
1302 pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> {
1303 self._apply_diff(diff, &mut Default::default(), true)
1304 }
1305
1306 pub(crate) fn _apply_diff(
1318 &self,
1319 diff: DiffBatch,
1320 container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1321 skip_unreachable: bool,
1322 ) -> LoroResult<()> {
1323 if !self.can_edit() {
1324 return Err(LoroError::EditWhenDetached);
1325 }
1326
1327 let mut ans: LoroResult<()> = Ok(());
1328 let mut missing_containers: Vec<ContainerID> = Vec::new();
1329 for (mut id, diff) in diff.into_iter() {
1330 let mut remapped = false;
1331 while let Some(rid) = container_remap.get(&id) {
1332 remapped = true;
1333 id = rid.clone();
1334 }
1335
1336 if matches!(&id, ContainerID::Normal { .. }) && self.arena.id_to_idx(&id).is_none() {
1337 let exists = self.state.lock().does_container_exist(&id);
1339 if !exists {
1340 missing_containers.push(id);
1341 continue;
1342 }
1343 self.state.lock().ensure_container(&id);
1345 }
1346
1347 if skip_unreachable && !remapped && !self.state.lock().get_reachable(&id) {
1348 continue;
1349 }
1350
1351 let Some(h) = self.get_handler(id.clone()) else {
1352 return Err(LoroError::ContainersNotFound {
1353 containers: Box::new(vec![id]),
1354 });
1355 };
1356 if let Err(e) = h.apply_diff(diff, container_remap) {
1357 ans = Err(e);
1358 }
1359 }
1360
1361 if !missing_containers.is_empty() {
1362 return Err(LoroError::ContainersNotFound {
1363 containers: Box::new(missing_containers),
1364 });
1365 }
1366
1367 ans
1368 }
1369
1370 #[inline]
1372 pub fn diagnose_size(&self) {
1373 self.oplog().lock().diagnose_size();
1374 }
1375
1376 #[inline]
1377 pub fn oplog_frontiers(&self) -> Frontiers {
1378 self.oplog().lock().frontiers().clone()
1379 }
1380
1381 #[inline]
1382 pub fn state_frontiers(&self) -> Frontiers {
1383 self.state.lock().frontiers.clone()
1384 }
1385
1386 #[inline]
1390 pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
1391 self.oplog().lock().cmp_with_frontiers(other)
1392 }
1393
1394 #[inline]
1398 pub fn cmp_frontiers(
1399 &self,
1400 a: &Frontiers,
1401 b: &Frontiers,
1402 ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
1403 self.oplog().lock().cmp_frontiers(a, b)
1404 }
1405
1406 pub fn subscribe_root(&self, callback: Subscriber) -> Subscription {
1407 let mut state = self.state.lock();
1408 if !state.is_recording() {
1409 state.start_recording();
1410 }
1411
1412 self.observer.subscribe_root(callback)
1413 }
1414
1415 pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription {
1416 let mut state = self.state.lock();
1417 if !state.is_recording() {
1418 state.start_recording();
1419 }
1420
1421 self.observer.subscribe(container_id, callback)
1422 }
1423
1424 pub fn subscribe_local_update(&self, callback: LocalUpdateCallback) -> Subscription {
1425 let (sub, activate) = self.local_update_subs.inner().insert((), callback);
1426 activate();
1427 sub
1428 }
1429
1430 #[tracing::instrument(skip_all)]
1432 pub fn import_batch(&self, bytes: &[Vec<u8>]) -> LoroResult<ImportStatus> {
1433 if bytes.is_empty() {
1434 return Ok(ImportStatus::default());
1435 }
1436
1437 if bytes.len() == 1 {
1438 return self.import(&bytes[0]);
1439 }
1440
1441 let mut success = VersionRange::default();
1442 let mut meta_arr = bytes
1443 .iter()
1444 .map(|b| Ok((LoroDoc::decode_import_blob_meta(b, false)?, b)))
1445 .collect::<LoroResult<Vec<(ImportBlobMetadata, &Vec<u8>)>>>()?;
1446 meta_arr.sort_by(|a, b| {
1447 a.0.mode
1448 .cmp(&b.0.mode)
1449 .then(b.0.change_num.cmp(&a.0.change_num))
1450 });
1451
1452 let (options, txn) = self.implicit_commit_then_stop();
1453 let is_detached = self.is_detached();
1477 self.set_detached(true);
1478 self.oplog.lock().batch_importing = true;
1479 let mut err = None;
1480 for (_meta, data) in meta_arr {
1481 match self._import_with(data, Default::default()) {
1482 Ok(s) => {
1483 for (peer, (start, end)) in s.success.iter() {
1484 match success.0.entry(*peer) {
1485 Entry::Occupied(mut e) => {
1486 e.get_mut().1 = *end.max(&e.get().1);
1487 }
1488 Entry::Vacant(e) => {
1489 e.insert((*start, *end));
1490 }
1491 }
1492 }
1493 }
1494 Err(e) => {
1495 err = Some(e);
1496 }
1497 }
1498 }
1499
1500 let mut oplog = self.oplog.lock();
1501 oplog.batch_importing = false;
1502 let pending = oplog.pending_changes.version_range();
1503 drop(oplog);
1504 if !is_detached {
1505 self._checkout_to_latest_with_guard(txn);
1506 } else {
1507 drop(txn);
1508 }
1509
1510 self.renew_txn_if_auto_commit(options);
1511 if let Some(err) = err {
1512 return Err(err);
1513 }
1514
1515 Ok(ImportStatus {
1516 success,
1517 pending: if pending.is_empty() {
1518 None
1519 } else {
1520 Some(pending)
1521 },
1522 })
1523 }
1524
1525 #[inline]
1527 pub fn get_value(&self) -> LoroValue {
1528 self.state.lock().get_value()
1529 }
1530
1531 #[inline]
1533 pub fn get_deep_value(&self) -> LoroValue {
1534 self.state.lock().get_deep_value()
1535 }
1536
1537 #[inline]
1539 pub fn get_deep_value_with_id(&self) -> LoroValue {
1540 self.state.lock().get_deep_value_with_id()
1541 }
1542
1543 pub fn checkout_to_latest(&self) {
1544 let (options, _guard) = self.implicit_commit_then_stop();
1545 if !self.is_detached() {
1546 drop(_guard);
1547 self.renew_txn_if_auto_commit(options);
1548 return;
1549 }
1550
1551 self._checkout_to_latest_without_commit(true)
1552 .expect("checkout to oplog frontiers should succeed");
1553 self.emit_events();
1554 drop(_guard);
1555 self.renew_txn_if_auto_commit(options);
1556 }
1557
1558 fn _checkout_to_latest_with_guard(&self, guard: LoroMutexGuard<Option<Transaction>>) {
1559 if !self.is_detached() {
1560 self._renew_txn_if_auto_commit_with_guard(None, guard);
1561 return;
1562 }
1563
1564 self._checkout_to_latest_without_commit(true)
1565 .expect("checkout to oplog frontiers should succeed");
1566 self._renew_txn_if_auto_commit_with_guard(None, guard);
1567 }
1568
1569 pub(crate) fn _checkout_to_latest_without_commit(
1571 &self,
1572 to_commit_then_renew: bool,
1573 ) -> LoroResult<()> {
1574 self._checkout_to_latest_without_commit_with_event(
1575 to_commit_then_renew,
1576 "checkout".into(),
1577 EventTriggerKind::Checkout,
1578 )
1579 }
1580
1581 pub(crate) fn _checkout_to_latest_without_commit_as_import(
1582 &self,
1583 to_commit_then_renew: bool,
1584 origin: InternalString,
1585 ) -> LoroResult<()> {
1586 self._checkout_to_latest_without_commit_with_event(
1587 to_commit_then_renew,
1588 origin,
1589 EventTriggerKind::Import,
1590 )
1591 }
1592
1593 fn _checkout_to_latest_without_commit_with_event(
1594 &self,
1595 to_commit_then_renew: bool,
1596 origin: InternalString,
1597 triggered_by: EventTriggerKind,
1598 ) -> LoroResult<()> {
1599 tracing::info_span!("CheckoutToLatest", peer = self.peer_id()).in_scope(|| {
1600 let f = self.oplog_frontiers();
1601 let this = &self;
1602 let frontiers = &f;
1603 this._checkout_without_emitting_with_event(
1604 frontiers,
1605 false,
1606 to_commit_then_renew,
1607 origin,
1608 triggered_by,
1609 )?;
1610 this.emit_events();
1612 if this.config.detached_editing() {
1613 this.renew_peer_id();
1614 }
1615
1616 self.set_detached(false);
1617 Ok(())
1618 })
1619 }
1620
1621 pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()> {
1626 let was_detached = self.is_detached();
1627 let (options, guard) = self.implicit_commit_then_stop();
1628 let result = self._checkout_without_emitting(frontiers, true, true);
1629 if result.is_ok() {
1630 self.emit_events();
1631 }
1632 drop(guard);
1633 if self.config.detached_editing() {
1634 if result.is_ok() {
1635 self.renew_peer_id();
1636 }
1637 self.renew_txn_if_auto_commit(options);
1638 } else if result.is_err() {
1639 if !was_detached {
1640 self.renew_txn_if_auto_commit(options);
1641 }
1642 } else if !self.is_detached() {
1643 self.renew_txn_if_auto_commit(options);
1644 }
1645
1646 result
1647 }
1648
1649 #[instrument(level = "info", skip(self))]
1651 pub(crate) fn _checkout_without_emitting(
1652 &self,
1653 frontiers: &Frontiers,
1654 to_shrink_frontiers: bool,
1655 to_commit_then_renew: bool,
1656 ) -> Result<(), LoroError> {
1657 self._checkout_without_emitting_with_event(
1658 frontiers,
1659 to_shrink_frontiers,
1660 to_commit_then_renew,
1661 "checkout".into(),
1662 EventTriggerKind::Checkout,
1663 )
1664 }
1665
1666 fn _checkout_without_emitting_with_event(
1667 &self,
1668 frontiers: &Frontiers,
1669 to_shrink_frontiers: bool,
1670 _to_commit_then_renew: bool,
1671 origin: InternalString,
1672 triggered_by: EventTriggerKind,
1673 ) -> Result<(), LoroError> {
1674 if !self.txn.is_locked() {
1675 return Err(LoroError::TransactionError(
1676 "checkout requires the transaction mutex to be held"
1677 .to_string()
1678 .into_boxed_str(),
1679 ));
1680 }
1681 let from_frontiers = self.state_frontiers();
1682 loro_common::info!(
1683 "checkout from={:?} to={:?} cur_vv={:?}",
1684 from_frontiers,
1685 frontiers,
1686 self.oplog_vv()
1687 );
1688
1689 if &from_frontiers == frontiers {
1690 self.set_detached(frontiers != &self.oplog_frontiers());
1691 return Ok(());
1692 }
1693
1694 let oplog = self.oplog.lock();
1695 if oplog.dag.is_before_shallow_root(frontiers) {
1696 return Err(LoroError::SwitchToVersionBeforeShallowRoot);
1697 }
1698
1699 let frontiers = if to_shrink_frontiers {
1700 shrink_frontiers(frontiers, &oplog.dag).map_err(LoroError::FrontiersNotFound)?
1701 } else {
1702 frontiers.clone()
1703 };
1704
1705 if from_frontiers == frontiers {
1706 return Ok(());
1707 }
1708
1709 let mut state = self.state.lock();
1710 let mut calc = self.diff_calculator.lock();
1711 for i in frontiers.iter() {
1712 if !oplog.dag.contains(i) {
1713 return Err(LoroError::FrontiersNotFound(i));
1714 }
1715 }
1716
1717 let before = oplog.dag.frontiers_to_vv(&state.frontiers).ok_or_else(|| {
1718 LoroError::NotFoundError(
1719 format!(
1720 "Cannot find the current state version {:?}",
1721 state.frontiers
1722 )
1723 .into_boxed_str(),
1724 )
1725 })?;
1726 let Some(after) = &oplog.dag.frontiers_to_vv(&frontiers) else {
1727 return Err(LoroError::NotFoundError(
1728 format!("Cannot find the specified version {:?}", frontiers).into_boxed_str(),
1729 ));
1730 };
1731
1732 self.set_detached(true);
1733 let (diff, diff_mode) =
1734 calc.calc_diff_internal(&oplog, &before, &state.frontiers, after, &frontiers, None);
1735 state.apply_diff(
1736 InternalDocDiff {
1737 origin,
1738 diff: Cow::Owned(diff),
1739 by: triggered_by,
1740 new_version: Cow::Owned(frontiers.clone()),
1741 },
1742 diff_mode,
1743 )?;
1744
1745 Ok(())
1746 }
1747
1748 #[inline]
1749 pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers {
1750 self.oplog.lock().dag.vv_to_frontiers(vv)
1751 }
1752
1753 #[inline]
1754 pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector> {
1755 self.oplog.lock().dag.frontiers_to_vv(frontiers)
1756 }
1757
1758 pub fn merge(&self, other: &Self) -> LoroResult<ImportStatus> {
1762 let updates = other.export(ExportMode::updates(&self.oplog_vv())).unwrap();
1763 self.import(&updates)
1764 }
1765
1766 pub(crate) fn arena(&self) -> &SharedArena {
1767 &self.arena
1768 }
1769
1770 #[inline]
1771 pub fn len_ops(&self) -> usize {
1772 if self.oplog.can_lock_in_this_thread() {
1773 return self.oplog.lock().visible_op_count_exact();
1774 }
1775
1776 self.visible_op_count.load(Acquire)
1777 }
1778
1779 #[inline]
1780 pub fn len_changes(&self) -> usize {
1781 let oplog = self.oplog.lock();
1782 oplog.len_changes()
1783 }
1784
1785 pub fn config(&self) -> &Configure {
1786 &self.config
1787 }
1788
1789 pub fn check_state_diff_calc_consistency_slow(&self) {
1794 {
1796 static IS_CHECKING: std::sync::atomic::AtomicBool =
1797 std::sync::atomic::AtomicBool::new(false);
1798 if IS_CHECKING.load(std::sync::atomic::Ordering::Acquire) {
1799 return;
1800 }
1801
1802 IS_CHECKING.store(true, std::sync::atomic::Ordering::Release);
1803 let peer_id = self.peer_id();
1804 let s = info_span!("CheckStateDiffCalcConsistencySlow", ?peer_id);
1805 let _g = s.enter();
1806 let options = self.implicit_commit_then_stop().0;
1807 self.oplog.lock().check_dag_correctness();
1808 if self.is_shallow() {
1809 let initial_snapshot = self
1820 .export(ExportMode::state_only(Some(
1821 &self.shallow_since_frontiers(),
1822 )))
1823 .unwrap();
1824
1825 let doc = LoroDoc::new();
1827 doc.import(&initial_snapshot).unwrap();
1828 self.checkout(&self.shallow_since_frontiers()).unwrap();
1829 assert_eq!(self.get_deep_value(), doc.get_deep_value());
1830
1831 let updates = self.export(ExportMode::all_updates()).unwrap();
1833
1834 doc.import(&updates).unwrap();
1836 self.checkout_to_latest();
1837
1838 assert_eq!(doc.get_deep_value(), self.get_deep_value());
1841 let mut calculated_state = doc.app_state().lock();
1842 let mut current_state = self.app_state().lock();
1843 current_state.check_is_the_same(&mut calculated_state);
1844 } else {
1845 let f = self.state_frontiers();
1846 let vv = self.oplog().lock().dag.frontiers_to_vv(&f).unwrap();
1847 let bytes = self.export(ExportMode::updates_till(&vv)).unwrap();
1848 let doc = Self::new();
1849 doc.import(&bytes).unwrap();
1850 let mut calculated_state = doc.app_state().lock();
1851 let mut current_state = self.app_state().lock();
1852 current_state.check_is_the_same(&mut calculated_state);
1853 }
1854
1855 self.renew_txn_if_auto_commit(options);
1856 IS_CHECKING.store(false, std::sync::atomic::Ordering::Release);
1857 }
1858 }
1859
1860 pub fn query_pos(&self, pos: &Cursor) -> Result<PosQueryResult, CannotFindRelativePosition> {
1861 self.query_pos_internal(pos, true)
1862 }
1863
1864 pub(crate) fn query_pos_internal(
1866 &self,
1867 pos: &Cursor,
1868 ret_event_index: bool,
1869 ) -> Result<PosQueryResult, CannotFindRelativePosition> {
1870 if !self.has_container(&pos.container) {
1871 return Err(CannotFindRelativePosition::IdNotFound);
1872 }
1873
1874 let mut state = self.state.lock();
1875 if let Some(ans) = state.get_relative_position(pos, ret_event_index) {
1876 Ok(PosQueryResult {
1877 update: None,
1878 current: AbsolutePosition {
1879 pos: ans,
1880 side: pos.side,
1881 },
1882 })
1883 } else {
1884 drop(state);
1896 let result = self.with_barrier(|| {
1897 let oplog = self.oplog().lock();
1898 if let Some(id) = pos.id {
1900 if oplog.arena.id_to_idx(&pos.container).is_none() {
1902 let mut s = self.state.lock();
1903 if !s.does_container_exist(&pos.container) {
1904 return Err(CannotFindRelativePosition::ContainerDeleted);
1905 }
1906 s.ensure_container(&pos.container);
1907 drop(s);
1908 }
1909 let idx = oplog.arena.id_to_idx(&pos.container).unwrap();
1910 let Some(delete_op_id) = find_last_delete_op(&oplog, id, idx) else {
1912 if oplog.shallow_since_vv().includes_id(id) {
1913 return Err(CannotFindRelativePosition::HistoryCleared);
1914 }
1915
1916 tracing::error!("Cannot find id {}", id);
1917 return Err(CannotFindRelativePosition::IdNotFound);
1918 };
1919 let mut diff_calc = DiffCalculator::new(true);
1921 let before_frontiers: Frontiers = oplog.dag.find_deps_of_id(delete_op_id);
1922 let before = &oplog.dag.frontiers_to_vv(&before_frontiers).unwrap();
1923 diff_calc.calc_diff_internal(
1925 &oplog,
1926 before,
1927 &before_frontiers,
1928 oplog.vv(),
1929 oplog.frontiers(),
1930 Some(&|target| idx == target),
1931 );
1932 let depth = self.arena.get_depth(idx);
1934 let (_, diff_calc) = &mut diff_calc.get_or_create_calc(idx, depth);
1935 match diff_calc {
1936 crate::diff_calc::ContainerDiffCalculator::Richtext(text) => {
1937 let c = text.get_id_latest_pos(id).unwrap();
1938 let new_pos = c.pos;
1939 let handler = self.get_text(&pos.container);
1940 let current_pos = handler.convert_entity_index_to_event_index(new_pos);
1941 Ok(PosQueryResult {
1942 update: handler.get_cursor(current_pos, c.side),
1943 current: AbsolutePosition {
1944 pos: current_pos,
1945 side: c.side,
1946 },
1947 })
1948 }
1949 crate::diff_calc::ContainerDiffCalculator::List(list) => {
1950 let c = list.get_id_latest_pos(id).unwrap();
1951 let new_pos = c.pos;
1952 let handler = self.get_list(&pos.container);
1953 Ok(PosQueryResult {
1954 update: handler.get_cursor(new_pos, c.side),
1955 current: AbsolutePosition {
1956 pos: new_pos,
1957 side: c.side,
1958 },
1959 })
1960 }
1961 crate::diff_calc::ContainerDiffCalculator::MovableList(list) => {
1962 let c = list.get_id_latest_pos(id).unwrap();
1963 let new_pos = c.pos;
1964 let handler = self.get_movable_list(&pos.container);
1965 let new_pos = handler.op_pos_to_user_pos(new_pos);
1966 Ok(PosQueryResult {
1967 update: handler.get_cursor(new_pos, c.side),
1968 current: AbsolutePosition {
1969 pos: new_pos,
1970 side: c.side,
1971 },
1972 })
1973 }
1974 crate::diff_calc::ContainerDiffCalculator::Tree(_) => unreachable!(),
1975 crate::diff_calc::ContainerDiffCalculator::Map(_) => unreachable!(),
1976 #[cfg(feature = "counter")]
1977 crate::diff_calc::ContainerDiffCalculator::Counter(_) => unreachable!(),
1978 crate::diff_calc::ContainerDiffCalculator::Unknown(_) => unreachable!(),
1979 }
1980 } else {
1981 match pos.container.container_type() {
1982 ContainerType::Text => {
1983 let text = self.get_text(&pos.container);
1984 Ok(PosQueryResult {
1985 update: Some(Cursor {
1986 id: None,
1987 container: text.id(),
1988 side: pos.side,
1989 origin_pos: text.len_unicode(),
1990 }),
1991 current: AbsolutePosition {
1992 pos: text.len_event(),
1993 side: pos.side,
1994 },
1995 })
1996 }
1997 ContainerType::List => {
1998 let list = self.get_list(&pos.container);
1999 Ok(PosQueryResult {
2000 update: Some(Cursor {
2001 id: None,
2002 container: list.id(),
2003 side: pos.side,
2004 origin_pos: list.len(),
2005 }),
2006 current: AbsolutePosition {
2007 pos: list.len(),
2008 side: pos.side,
2009 },
2010 })
2011 }
2012 ContainerType::MovableList => {
2013 let list = self.get_movable_list(&pos.container);
2014 Ok(PosQueryResult {
2015 update: Some(Cursor {
2016 id: None,
2017 container: list.id(),
2018 side: pos.side,
2019 origin_pos: list.len(),
2020 }),
2021 current: AbsolutePosition {
2022 pos: list.len(),
2023 side: pos.side,
2024 },
2025 })
2026 }
2027 ContainerType::Map | ContainerType::Tree | ContainerType::Unknown(_) => {
2028 unreachable!()
2029 }
2030 #[cfg(feature = "counter")]
2031 ContainerType::Counter => unreachable!(),
2032 }
2033 }
2034 });
2035 result
2036 }
2037 }
2038
2039 pub fn free_history_cache(&self) {
2044 self.oplog.lock().free_history_cache();
2045 }
2046
2047 pub fn free_diff_calculator(&self) {
2049 *self.diff_calculator.lock() = DiffCalculator::new(true);
2050 }
2051
2052 pub fn has_history_cache(&self) -> bool {
2055 self.oplog.lock().has_history_cache()
2056 }
2057
2058 #[inline]
2062 pub fn compact_change_store(&self) {
2063 self.with_barrier(|| {
2064 self.oplog.lock().compact_change_store();
2065 });
2066 }
2067
2068 #[inline]
2072 pub fn analyze(&self) -> DocAnalysis {
2073 DocAnalysis::analyze(self)
2074 }
2075
2076 pub fn get_path_to_container(&self, id: &ContainerID) -> Option<Vec<(ContainerID, Index)>> {
2078 let mut state = self.state.lock();
2079 if state.arena.id_to_idx(id).is_none() {
2080 if id.is_mergeable() {
2081 state.arena.register_container(id);
2085 } else if !state.does_container_exist(id) {
2086 return None;
2087 } else {
2088 state.ensure_container(id);
2089 }
2090 }
2091 let idx = state.arena.id_to_idx(id).unwrap();
2092 state.get_path(idx)
2093 }
2094
2095 #[instrument(skip(self))]
2096 pub fn export(&self, mode: ExportMode) -> Result<Vec<u8>, LoroEncodeError> {
2097 self.with_barrier(|| {
2098 let ans = match mode {
2099 ExportMode::Snapshot => export_fast_snapshot(self)?,
2100 ExportMode::Updates { from } => export_fast_updates(self, &from),
2101 ExportMode::UpdatesInRange { spans } => {
2102 export_fast_updates_in_range(&self.oplog.lock(), spans.as_ref())
2103 }
2104 ExportMode::ShallowSnapshot(f) => export_shallow_snapshot(self, &f)?,
2105 ExportMode::StateOnly(f) => match f {
2106 Some(f) => export_state_only_snapshot(self, &f)?,
2107 None => export_state_only_snapshot(self, &self.oplog_frontiers())?,
2108 },
2109 ExportMode::SnapshotAt { version } => export_snapshot_at(self, &version)?,
2110 };
2111 Ok(ans)
2112 })
2113 }
2114
2115 pub fn shallow_since_vv(&self) -> ImVersionVector {
2121 self.oplog().lock().shallow_since_vv().clone()
2122 }
2123
2124 pub fn shallow_since_frontiers(&self) -> Frontiers {
2125 self.oplog().lock().shallow_since_frontiers().clone()
2126 }
2127
2128 pub fn is_shallow(&self) -> bool {
2130 !self.oplog().lock().shallow_since_vv().is_empty()
2131 }
2132
2133 pub fn get_pending_txn_len(&self) -> usize {
2138 if let Some(txn) = self.txn.lock().as_ref() {
2139 txn.len()
2140 } else {
2141 0
2142 }
2143 }
2144
2145 #[inline]
2146 pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
2147 self.oplog().lock().dag.find_path(from, to)
2148 }
2149
2150 pub fn subscribe_first_commit_from_peer(
2156 &self,
2157 callback: FirstCommitFromPeerCallback,
2158 ) -> Subscription {
2159 let (s, enable) = self
2160 .first_commit_from_peer_subs
2161 .inner()
2162 .insert((), callback);
2163 enable();
2164 s
2165 }
2166
2167 pub fn subscribe_pre_commit(&self, callback: PreCommitCallback) -> Subscription {
2172 let (s, enable) = self.pre_commit_subs.inner().insert((), callback);
2173 enable();
2174 s
2175 }
2176}
2177
2178fn pending_root_containers_to_materialize(oplog: &OpLog, changes: &[Change]) -> Vec<ContainerID> {
2179 let mut roots = FxHashSet::default();
2180 for change in changes {
2181 if change.ctr_end() <= oplog.vv().get(&change.id.peer).copied().unwrap_or(0) {
2182 continue;
2183 }
2184
2185 if oplog.dag.is_before_shallow_root(&change.deps)
2186 || oplog
2187 .dag
2188 .get_change_lamport_from_deps(&change.deps)
2189 .is_some()
2190 {
2191 continue;
2192 }
2193
2194 for op in change.ops.iter() {
2195 let id = oplog
2196 .arena
2197 .get_container_id(op.container)
2198 .expect("decoded op container should be registered");
2199 if id.is_root() && !id.is_mergeable() {
2208 roots.insert(id);
2209 }
2210 }
2211 }
2212
2213 roots.into_iter().collect()
2214}
2215
2216fn isolated_scalar_root_batch(
2220 oplog: &OpLog,
2221 changes: &[Change],
2222) -> Option<(VersionVector, FxHashSet<InternalString>)> {
2223 if changes.is_empty() || !oplog.shallow_since_vv().is_empty() {
2224 return None;
2225 }
2226
2227 let mut component_vv = VersionVector::new();
2230 let mut root_names = FxHashSet::default();
2231 for change in changes {
2232 let peer = change.id.peer;
2233 if oplog.vv().get(&peer).copied().unwrap_or(0) != 0 {
2234 return None;
2235 }
2236 let end = component_vv.entry(peer).or_insert(0);
2237 if change.id.counter != *end {
2238 return None;
2239 }
2240 *end = change.ctr_end();
2241
2242 for op in change.ops.iter() {
2243 let cid = oplog.arena.idx_to_id(op.container)?;
2244 if cid.is_mergeable() {
2245 return None;
2246 }
2247 let ContainerID::Root {
2248 name,
2249 container_type: ContainerType::Map,
2250 } = cid
2251 else {
2252 return None;
2253 };
2254 let InnerContent::Map(map) = &op.content else {
2255 return None;
2256 };
2257 if let Some(value) = &map.value {
2258 if value.is_container()
2259 || loro_common::parse_mergeable_marker(
2260 &ContainerID::new_root(&name, ContainerType::Map),
2261 &map.key,
2262 value,
2263 )
2264 .is_some()
2265 {
2266 return None;
2267 }
2268 }
2269 root_names.insert(name);
2270 }
2271 }
2272
2273 for change in changes {
2275 for dep in change.deps.iter() {
2276 if dep.counter >= component_vv.get(&dep.peer).copied().unwrap_or(0) {
2277 return None;
2278 }
2279 }
2280 }
2281
2282 if oplog
2283 .change_store()
2284 .old_history_may_touch_root_names(&root_names)
2285 {
2286 return None;
2287 }
2288
2289 Some((component_vv, root_names))
2290}
2291
2292#[derive(Debug, thiserror::Error)]
2293pub enum ChangeTravelError {
2294 #[error("Target id not found {0:?}")]
2295 TargetIdNotFound(ID),
2296 #[error("The shallow history of the doc doesn't include the target version")]
2297 TargetVersionNotIncluded,
2298}
2299
2300impl LoroDoc {
2301 pub fn travel_change_ancestors(
2302 &self,
2303 ids: &[ID],
2304 f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>,
2305 ) -> Result<(), ChangeTravelError> {
2306 let (options, guard) = self.implicit_commit_then_stop();
2307 drop(guard);
2308 struct PendingNode(ChangeMeta);
2309 impl PartialEq for PendingNode {
2310 fn eq(&self, other: &Self) -> bool {
2311 self.0.lamport_last() == other.0.lamport_last() && self.0.id.peer == other.0.id.peer
2312 }
2313 }
2314
2315 impl Eq for PendingNode {}
2316 impl PartialOrd for PendingNode {
2317 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2318 Some(self.cmp(other))
2319 }
2320 }
2321
2322 impl Ord for PendingNode {
2323 fn cmp(&self, other: &Self) -> Ordering {
2324 self.0
2325 .lamport_last()
2326 .cmp(&other.0.lamport_last())
2327 .then_with(|| self.0.id.peer.cmp(&other.0.id.peer))
2328 }
2329 }
2330
2331 for id in ids {
2332 let op_log = &self.oplog().lock();
2333 if !op_log.vv().includes_id(*id) {
2334 return Err(ChangeTravelError::TargetIdNotFound(*id));
2335 }
2336 if op_log.dag.shallow_since_vv().includes_id(*id) {
2337 return Err(ChangeTravelError::TargetVersionNotIncluded);
2338 }
2339 }
2340
2341 let mut visited = FxHashSet::default();
2342 let mut pending: BinaryHeap<PendingNode> = BinaryHeap::new();
2343 for id in ids {
2344 pending.push(PendingNode(ChangeMeta::from_change(
2345 &self.oplog().lock().get_change_at(*id).unwrap(),
2346 )));
2347 }
2348 while let Some(PendingNode(node)) = pending.pop() {
2349 let deps = node.deps.clone();
2350 if f(node).is_break() {
2351 break;
2352 }
2353
2354 for dep in deps.iter() {
2355 let Some(dep_node) = self.oplog().lock().get_change_at(dep) else {
2356 continue;
2357 };
2358 if visited.contains(&dep_node.id) {
2359 continue;
2360 }
2361
2362 visited.insert(dep_node.id);
2363 pending.push(PendingNode(ChangeMeta::from_change(&dep_node)));
2364 }
2365 }
2366
2367 let ans = Ok(());
2368 self.renew_txn_if_auto_commit(options);
2369 ans
2370 }
2371
2372 pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
2373 self.with_barrier(|| {
2374 let mut set = FxHashSet::default();
2375 let len = i64::try_from(len).unwrap_or(i64::MAX);
2376 let start = i64::from(id.counter);
2377 let end = start.saturating_add(len);
2378 if end <= 0 {
2379 return set;
2380 }
2381
2382 let start = start.max(0).min(i64::from(i32::MAX));
2383 let end = end.max(0).min(i64::from(i32::MAX));
2384 if start >= end {
2385 return set;
2386 }
2387
2388 {
2389 let oplog = self.oplog().lock();
2390 let span = IdSpan::new(id.peer, start as i32, end as i32);
2391 for op in oplog.iter_ops(span) {
2392 let id = oplog.arena.get_container_id(op.container()).unwrap();
2393 set.insert(id);
2394 }
2395 }
2396 set
2397 })
2398 }
2399
2400 pub fn delete_root_container(&self, cid: ContainerID) {
2401 if !cid.is_root() {
2402 return;
2403 }
2404
2405 if !self.has_container(&cid) {
2407 return;
2408 }
2409
2410 let Some(h) = self.get_handler(cid.clone()) else {
2411 return;
2412 };
2413
2414 self.config
2415 .deleted_root_containers
2416 .lock()
2417 .insert(cid.clone());
2418 if let Err(e) = h.clear() {
2419 self.config.deleted_root_containers.lock().remove(&cid);
2420 eprintln!("Failed to clear handler: {:?}", e);
2421 }
2422 }
2423
2424 pub fn set_hide_empty_root_containers(&self, hide: bool) {
2425 self.config
2426 .hide_empty_root_containers
2427 .store(hide, std::sync::atomic::Ordering::Relaxed);
2428 }
2429}
2430
2431fn find_last_delete_op(oplog: &OpLog, id: ID, idx: ContainerIdx) -> Option<ID> {
2432 let start_vv = oplog
2443 .dag
2444 .frontiers_to_vv(&id.into())
2445 .unwrap_or_else(|| oplog.shallow_since_vv().to_vv());
2446
2447 let mut best: Option<((loro_common::Lamport, loro_common::PeerID), ID)> = None;
2451
2452 for change in oplog.iter_changes_peer_by_peer(&start_vv, oplog.vv()) {
2453 let peer = change.peer();
2454 for op in change.ops.iter() {
2455 if op.container != idx {
2456 continue;
2457 }
2458 if let InnerContent::List(InnerListOp::Delete(d)) = &op.content {
2459 if d.id_start.to_span(d.atom_len()).contains(id) {
2460 debug_assert!(op.counter >= change.id().counter);
2461 let op_lamport =
2462 change.lamport + (op.counter - change.id().counter) as loro_common::Lamport;
2463 let key = (op_lamport, peer);
2464 if best.is_none_or(|(bk, _)| key > bk) {
2465 best = Some((key, ID::new(peer, op.counter)));
2466 }
2467 }
2468 }
2469 }
2470 }
2471
2472 best.map(|(_, op_id)| op_id)
2473}
2474
2475#[derive(Debug)]
2476pub struct CommitWhenDrop<'a> {
2477 doc: &'a LoroDoc,
2478 default_options: CommitOptions,
2479}
2480
2481impl Drop for CommitWhenDrop<'_> {
2482 fn drop(&mut self) {
2483 {
2484 let mut guard = self.doc.txn.lock();
2485 if let Some(txn) = guard.as_mut() {
2486 txn.set_default_options(std::mem::take(&mut self.default_options));
2487 };
2488 }
2489
2490 self.doc.commit_then_renew();
2491 }
2492}
2493
2494#[derive(Debug, Clone)]
2496pub struct CommitOptions {
2497 pub origin: Option<InternalString>,
2500
2501 pub immediate_renew: bool,
2504
2505 pub timestamp: Option<Timestamp>,
2508
2509 pub commit_msg: Option<Arc<str>>,
2511}
2512
2513impl CommitOptions {
2514 pub fn new() -> Self {
2516 Self {
2517 origin: None,
2518 immediate_renew: true,
2519 timestamp: None,
2520 commit_msg: None,
2521 }
2522 }
2523
2524 pub fn origin(mut self, origin: &str) -> Self {
2526 self.origin = Some(origin.into());
2527 self
2528 }
2529
2530 pub fn immediate_renew(mut self, immediate_renew: bool) -> Self {
2532 self.immediate_renew = immediate_renew;
2533 self
2534 }
2535
2536 pub fn timestamp(mut self, timestamp: Timestamp) -> Self {
2540 self.timestamp = Some(timestamp);
2541 self
2542 }
2543
2544 pub fn commit_msg(mut self, commit_msg: &str) -> Self {
2546 self.commit_msg = Some(commit_msg.into());
2547 self
2548 }
2549
2550 pub fn set_origin(&mut self, origin: Option<&str>) {
2552 self.origin = origin.map(|x| x.into())
2553 }
2554
2555 pub fn set_timestamp(&mut self, timestamp: Option<Timestamp>) {
2557 self.timestamp = timestamp;
2558 }
2559}
2560
2561impl Default for CommitOptions {
2562 fn default() -> Self {
2563 Self::new()
2564 }
2565}
2566
2567#[cfg(test)]
2568mod test {
2569 use std::{
2570 panic::AssertUnwindSafe,
2571 sync::{
2572 atomic::{AtomicUsize, Ordering},
2573 Arc,
2574 },
2575 };
2576
2577 use crate::{
2578 cursor::PosType,
2579 encoding::json_schema::json::{JsonOpContent, JsonSchema, ListOp},
2580 encoding::{fast_snapshot::EMPTY_MARK, EncodeMode},
2581 loro::ExportMode,
2582 version::{Frontiers, VersionVector},
2583 LoroDoc, ToJson, TreeParentId,
2584 };
2585 use bytes::{BufMut, Bytes};
2586 use loro_common::{ContainerID, ContainerType, ID};
2587 use loro_kv_store::{mem_store::MemKvConfig, MemKvStore};
2588
2589 const XXH_SEED: u32 = u32::from_le_bytes(*b"LORO");
2590
2591 fn encode_import_blob(mode: EncodeMode, body: &[u8]) -> Vec<u8> {
2592 let mut ans = Vec::new();
2593 ans.extend_from_slice(b"loro");
2594 ans.extend_from_slice(&[0; 16]);
2595 ans.extend_from_slice(&mode.to_bytes());
2596 ans.extend_from_slice(body);
2597 let checksum = xxhash_rust::xxh32::xxh32(&ans[20..], XXH_SEED);
2598 ans[16..20].copy_from_slice(&checksum.to_le_bytes());
2599 ans
2600 }
2601
2602 fn encode_fast_snapshot_import(oplog_bytes: &[u8]) -> Vec<u8> {
2603 let mut body = Vec::new();
2604 body.put_u32_le(oplog_bytes.len() as u32);
2605 body.extend_from_slice(oplog_bytes);
2606 body.put_u32_le(EMPTY_MARK.len() as u32);
2607 body.extend_from_slice(EMPTY_MARK);
2608 body.put_u32_le(0);
2609 encode_import_blob(EncodeMode::FastSnapshot, &body)
2610 }
2611
2612 fn sstable_with_huge_meta_block_count() -> Vec<u8> {
2613 let mut bytes = Vec::new();
2614 bytes.extend_from_slice(b"LORO");
2615 bytes.push(0);
2616 bytes.put_u32_le(10_000_000);
2617 bytes.put_u32_le(xxhash_rust::xxh32::xxh32(&[], XXH_SEED));
2618 bytes.put_u32_le(5);
2619 bytes
2620 }
2621
2622 fn snapshot_oplog_with_malformed_block() -> Vec<u8> {
2623 let peer = 1;
2624 let id = ID::new(peer, 0);
2625 let vv = VersionVector::from_iter([(peer, 1)]);
2626 let frontiers = Frontiers::from_id(id);
2627 let mut store = MemKvStore::new(MemKvConfig::default());
2628 store.set(b"vv", vv.encode().into());
2629 store.set(b"fr", frontiers.encode().into());
2630 store.set(&id.to_bytes(), Bytes::from_static(&[0]));
2631 store.export_all().to_vec()
2632 }
2633
2634 fn make_json_import_stress_doc(peer: u64) -> LoroDoc {
2635 let doc = LoroDoc::new_auto_commit();
2636 doc.set_peer_id(peer).unwrap();
2637
2638 let text = doc.get_text("text");
2639 let mut text_pos = 0;
2640 for i in 0..32 {
2641 let chunk = format!("segment-{i}-abcdefghijklmnopqrstuvwxyz;");
2642 text.insert_unicode(text_pos, &chunk).unwrap();
2643 text_pos += chunk.chars().count();
2644 }
2645
2646 let list = doc.get_list("list");
2647 for i in 0..32 {
2648 list.insert(i, format!("item-{i}")).unwrap();
2649 }
2650
2651 let map = doc.get_map("map");
2652 for i in 0..32 {
2653 let key = format!("key-{i}");
2654 map.insert(&key, format!("value-{i}")).unwrap();
2655 }
2656
2657 let tree = doc.get_tree("tree");
2658 let mut parent = TreeParentId::Root;
2659 for i in 0..16 {
2660 let node = tree.create(parent).unwrap();
2661 let meta = tree.get_meta(node).unwrap();
2662 meta.insert("name", format!("node-{i}")).unwrap();
2663 meta.insert("payload", format!("payload-{i}-{}", "x".repeat(16)))
2664 .unwrap();
2665 parent = TreeParentId::Node(node);
2666 }
2667
2668 doc
2669 }
2670
2671 fn make_json_list_update_with_four_ops(peer: u64) -> (LoroDoc, JsonSchema) {
2672 let doc = LoroDoc::new();
2673 doc.set_peer_id(peer).unwrap();
2674 let map = doc.get_map("map");
2675 let list = doc.get_list("list");
2676 let text = doc.get_text("text");
2677
2678 let mut txn = doc.txn().unwrap();
2679 map.insert_with_txn(&mut txn, "prefix", "map-value".into())
2680 .unwrap();
2681 list.insert_with_txn(&mut txn, 0, "seed".into()).unwrap();
2682 text.insert_with_txn(&mut txn, 0, "text-value", PosType::Unicode)
2683 .unwrap();
2684 list.insert_with_txn(&mut txn, 1, "tail".into()).unwrap();
2685 txn.commit().unwrap();
2686
2687 let json = doc.export_json_updates(&Default::default(), &doc.oplog_vv(), false);
2688 assert_eq!(json.changes.len(), 1);
2689 assert_eq!(json.changes[0].ops.len(), 4);
2690 (doc, json)
2691 }
2692
2693 fn move_last_list_insert_far_out_of_bounds(json: &mut JsonSchema) {
2694 let last_change = json.changes.last_mut().unwrap();
2695 let last_op = last_change.ops.last_mut().unwrap();
2696 match &mut last_op.content {
2697 JsonOpContent::List(ListOp::Insert { pos, .. }) => {
2698 *pos = 1_000;
2699 }
2700 other => panic!("expected list insert op, got {other:?}"),
2701 }
2702 }
2703
2704 #[test]
2705 fn test_sync() {
2706 fn is_send_sync<T: Send + Sync>(_v: T) {}
2707 let loro = super::LoroDoc::new();
2708 is_send_sync(loro)
2709 }
2710
2711 #[test]
2712 fn import_rejects_huge_sstable_meta_block_count_without_panic() {
2713 let bytes = encode_fast_snapshot_import(&sstable_with_huge_meta_block_count());
2714
2715 let result = std::panic::catch_unwind(AssertUnwindSafe(|| LoroDoc::new().import(&bytes)));
2716 assert!(result.is_ok(), "malformed import should not panic");
2717 assert!(result.unwrap().is_err());
2718 }
2719
2720 #[test]
2721 fn import_rejects_malformed_change_block_without_panic() {
2722 let bytes = encode_fast_snapshot_import(&snapshot_oplog_with_malformed_block());
2723
2724 let result = std::panic::catch_unwind(AssertUnwindSafe(|| LoroDoc::new().import(&bytes)));
2725 assert!(result.is_ok(), "malformed import should not panic");
2726 assert!(result.unwrap().is_err());
2727 }
2728
2729 #[test]
2730 fn failed_import_rolls_back_oplog_and_arena() {
2731 let src = LoroDoc::new();
2732 src.set_peer_id(1).unwrap();
2733 let text = src.get_text("text");
2734 let mut txn = src.txn().unwrap();
2735 text.insert_with_txn(&mut txn, 0, "hello", PosType::Unicode)
2736 .unwrap();
2737 txn.commit().unwrap();
2738 let update = src.export(ExportMode::all_updates()).unwrap();
2739
2740 let dst = LoroDoc::new();
2741 let vv_before_import = dst.oplog_vv();
2742 let state_before_import = dst.get_deep_value();
2743 let err = dst
2744 .import_with(&update, "__loro_fail_import_state_apply".into())
2745 .unwrap_err();
2746 assert!(err.to_string().contains("state apply failpoint"));
2747 assert_eq!(dst.oplog_vv(), vv_before_import);
2748 assert_eq!(dst.get_deep_value(), state_before_import);
2749 assert!(dst.oplog().lock().is_empty());
2750
2751 dst.import(&update).unwrap();
2752 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2753 }
2754
2755 #[test]
2756 fn failed_incremental_import_restores_previous_change_store_block() {
2757 let src = LoroDoc::new();
2758 src.set_peer_id(1).unwrap();
2759 let text = src.get_text("text");
2760 let mut txn = src.txn().unwrap();
2761 text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode)
2762 .unwrap();
2763 txn.commit().unwrap();
2764 let first_update = src.export(ExportMode::all_updates()).unwrap();
2765 let first_vv = src.oplog_vv();
2766
2767 let mut txn = src.txn().unwrap();
2768 text.insert_with_txn(&mut txn, 1, "b", PosType::Unicode)
2769 .unwrap();
2770 txn.commit().unwrap();
2771 let second_update = src.export(ExportMode::updates(&first_vv)).unwrap();
2772
2773 let dst = LoroDoc::new();
2774 dst.import(&first_update).unwrap();
2775 let vv_before_import = dst.oplog_vv();
2776 let state_before_import = dst.get_deep_value();
2777 dst.import_with(&second_update, "__loro_fail_import_state_apply".into())
2778 .unwrap_err();
2779 assert_eq!(dst.oplog_vv(), vv_before_import);
2780 assert_eq!(dst.get_deep_value(), state_before_import);
2781
2782 dst.import(&second_update).unwrap();
2783 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2784 }
2785
2786 #[test]
2787 fn failed_import_json_updates_rolls_back_complex_empty_doc() {
2788 let src = make_json_import_stress_doc(11);
2789 let json = src.export_json_updates(&Default::default(), &src.oplog_vv(), false);
2790
2791 let dst = LoroDoc::new();
2792 let vv_before_import = dst.oplog_vv();
2793 let frontiers_before_import = dst.oplog_frontiers();
2794 let state_before_import = dst.get_deep_value();
2795 for _ in 0..3 {
2796 crate::state::fail_next_import_state_apply_for_test();
2797 let err = dst.import_json_updates(json.clone()).unwrap_err();
2798 assert!(err.to_string().contains("state apply failpoint"));
2799 assert_eq!(dst.oplog_vv(), vv_before_import);
2800 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
2801 assert_eq!(dst.get_deep_value(), state_before_import);
2802 assert!(dst.oplog().lock().is_empty());
2803 }
2804
2805 dst.import_json_updates(json).unwrap();
2806 assert_eq!(dst.oplog_vv(), src.oplog_vv());
2807 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
2808 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2809 }
2810
2811 #[test]
2812 fn failed_incremental_import_json_updates_restores_previous_change_store_block() {
2813 let src = LoroDoc::new_auto_commit();
2814 src.set_peer_id(12).unwrap();
2815 let text = src.get_text("text");
2816 text.insert_unicode(0, "a").unwrap();
2817 let list = src.get_list("list");
2818 list.push("seed").unwrap();
2819 let map = src.get_map("map");
2820 map.insert("seed", "value").unwrap();
2821 let tree = src.get_tree("tree");
2822 let root = tree.create(TreeParentId::Root).unwrap();
2823 tree.get_meta(root).unwrap().insert("name", "root").unwrap();
2824
2825 let first_vv = src.oplog_vv();
2826 let first_json = src.export_json_updates(&Default::default(), &first_vv, false);
2827
2828 let mut text_pos = text.len_unicode();
2829 for i in 0..64 {
2830 let chunk = format!("chunk-{i};");
2831 text.insert_unicode(text_pos, &chunk).unwrap();
2832 text_pos += chunk.chars().count();
2833 }
2834 for i in 0..32 {
2835 list.push(format!("after-{i}")).unwrap();
2836 let key = format!("after-{i}");
2837 map.insert(&key, format!("value-{i}")).unwrap();
2838 }
2839 let child = tree.create(TreeParentId::Node(root)).unwrap();
2840 tree.get_meta(child)
2841 .unwrap()
2842 .insert("name", "child")
2843 .unwrap();
2844
2845 let second_json = src.export_json_updates(&first_vv, &src.oplog_vv(), false);
2846
2847 let dst = LoroDoc::new();
2848 dst.import_json_updates(first_json).unwrap();
2849 let vv_before_import = dst.oplog_vv();
2850 let frontiers_before_import = dst.oplog_frontiers();
2851 let state_before_import = dst.get_deep_value();
2852
2853 for _ in 0..2 {
2854 crate::state::fail_next_import_state_apply_for_test();
2855 let err = dst.import_json_updates(second_json.clone()).unwrap_err();
2856 assert!(err.to_string().contains("state apply failpoint"));
2857 assert_eq!(dst.oplog_vv(), vv_before_import);
2858 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
2859 assert_eq!(dst.get_deep_value(), state_before_import);
2860 }
2861
2862 dst.import_json_updates(second_json).unwrap();
2863 assert_eq!(dst.oplog_vv(), src.oplog_vv());
2864 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
2865 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2866 }
2867
2868 #[test]
2869 fn malformed_later_import_json_update_rolls_back_after_valid_prefix_enters_oplog() {
2870 let peer = 13;
2871 let (src, good_json) = make_json_list_update_with_four_ops(peer);
2872 let mut bad_json = good_json.clone();
2873 move_last_list_insert_far_out_of_bounds(&mut bad_json);
2874
2875 let good_dst = LoroDoc::new();
2876 good_dst.import_json_updates(good_json.clone()).unwrap();
2877 assert_eq!(good_dst.get_deep_value(), src.get_deep_value());
2878
2879 let last_op_counter = good_json.changes[0].ops.last().unwrap().counter;
2880 let prefix_vv = VersionVector::from_iter([(peer, last_op_counter)]);
2881 let prefix_json = src.export_json_updates(&Default::default(), &prefix_vv, false);
2882 assert_eq!(
2883 prefix_json.changes[0].ops.len(),
2884 good_json.changes[0].ops.len() - 1
2885 );
2886 let good_suffix_json = src.export_json_updates(&prefix_vv, &src.oplog_vv(), false);
2887 assert_eq!(good_suffix_json.changes[0].ops.len(), 1);
2888 let mut bad_suffix_json = good_suffix_json.clone();
2889 move_last_list_insert_far_out_of_bounds(&mut bad_suffix_json);
2890
2891 let prefix_dst = LoroDoc::new();
2892 prefix_dst.import_json_updates(prefix_json.clone()).unwrap();
2893 let vv_before_bad_suffix = prefix_dst.oplog_vv();
2894 let frontiers_before_bad_suffix = prefix_dst.oplog_frontiers();
2895 let state_before_bad_suffix = prefix_dst.get_deep_value();
2896
2897 let bad_suffix_json = serde_json::to_string(&bad_suffix_json).unwrap();
2898 let err = prefix_dst
2899 .import_json_updates(&bad_suffix_json)
2900 .unwrap_err();
2901 assert!(
2902 err.to_string().contains("list diff"),
2903 "expected state list bounds validation, got {err:?}"
2904 );
2905 assert_eq!(prefix_dst.oplog_vv(), vv_before_bad_suffix);
2906 assert_eq!(prefix_dst.oplog_frontiers(), frontiers_before_bad_suffix);
2907 assert_eq!(prefix_dst.get_deep_value(), state_before_bad_suffix);
2908
2909 prefix_dst.import_json_updates(good_suffix_json).unwrap();
2910 assert_eq!(prefix_dst.get_deep_value(), src.get_deep_value());
2911 assert_eq!(prefix_dst.oplog_vv(), src.oplog_vv());
2912
2913 let dst = LoroDoc::new();
2914 let vv_before_import = dst.oplog_vv();
2915 let frontiers_before_import = dst.oplog_frontiers();
2916 let state_before_import = dst.get_deep_value();
2917 let bad_json = serde_json::to_string(&bad_json).unwrap();
2918 let err = dst.import_json_updates(&bad_json).unwrap_err();
2919 assert!(
2920 err.to_string().contains("list diff"),
2921 "expected state list bounds validation, got {err:?}"
2922 );
2923 assert_eq!(dst.oplog_vv(), vv_before_import);
2924 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
2925 assert_eq!(dst.get_deep_value(), state_before_import);
2926 assert!(dst.oplog().lock().is_empty());
2927 }
2928
2929 #[test]
2930 fn failed_import_restores_pending_changes_that_were_applied_during_import() {
2931 let src = LoroDoc::new();
2932 src.set_peer_id(14).unwrap();
2933 let text = src.get_text("text");
2934
2935 let mut txn = src.txn().unwrap();
2936 text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode)
2937 .unwrap();
2938 txn.commit().unwrap();
2939 let first_update = src.export(ExportMode::all_updates()).unwrap();
2940 let first_vv = src.oplog_vv();
2941
2942 let mut txn = src.txn().unwrap();
2943 text.insert_with_txn(&mut txn, 1, "b", PosType::Unicode)
2944 .unwrap();
2945 txn.commit().unwrap();
2946 let second_update = src.export(ExportMode::updates(&first_vv)).unwrap();
2947
2948 let dst = LoroDoc::new();
2949 let status = dst.import(&second_update).unwrap();
2950 assert!(status.success.is_empty());
2951 assert!(status.pending.is_some());
2952 let vv_before_dependency = dst.oplog_vv();
2953 let frontiers_before_dependency = dst.oplog_frontiers();
2954 let state_before_dependency = dst.get_deep_value();
2955
2956 crate::state::fail_next_import_state_apply_for_test();
2957 let err = dst.import(&first_update).unwrap_err();
2958 assert!(err.to_string().contains("state apply failpoint"));
2959 assert_eq!(dst.oplog_vv(), vv_before_dependency);
2960 assert_eq!(dst.oplog_frontiers(), frontiers_before_dependency);
2961 assert_eq!(dst.get_deep_value(), state_before_dependency);
2962
2963 dst.import(&first_update).unwrap();
2964 assert_eq!(dst.oplog_vv(), src.oplog_vv());
2965 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
2966 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2967 }
2968
2969 #[test]
2970 fn failed_import_json_updates_does_not_emit_or_leave_events() {
2971 let (src, good_json) = make_json_list_update_with_four_ops(15);
2972 let mut bad_json = good_json.clone();
2973 move_last_list_insert_far_out_of_bounds(&mut bad_json);
2974
2975 let dst = LoroDoc::new();
2976 let event_count = Arc::new(AtomicUsize::new(0));
2977 let event_count_cloned = event_count.clone();
2978 let _sub = dst.subscribe_root(Arc::new(move |_| {
2979 event_count_cloned.fetch_add(1, Ordering::SeqCst);
2980 }));
2981
2982 let bad_json = serde_json::to_string(&bad_json).unwrap();
2983 let err = dst.import_json_updates(&bad_json).unwrap_err();
2984 assert!(
2985 err.to_string().contains("list diff"),
2986 "expected state list bounds validation, got {err:?}"
2987 );
2988 assert_eq!(event_count.load(Ordering::SeqCst), 0);
2989 assert!(dst.drop_pending_events().is_empty());
2990 assert!(dst.oplog().lock().is_empty());
2991
2992 dst.import_json_updates(good_json).unwrap();
2993 assert_eq!(event_count.load(Ordering::SeqCst), 1);
2994 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2995 }
2996
2997 #[test]
2998 fn test_checkout() {
2999 let loro = LoroDoc::new();
3000 loro.set_peer_id(1).unwrap();
3001 let text = loro.get_text("text");
3002 let map = loro.get_map("map");
3003 let list = loro.get_list("list");
3004 let mut txn = loro.txn().unwrap();
3005 for i in 0..10 {
3006 map.insert_with_txn(&mut txn, "key", i.into()).unwrap();
3007 text.insert_with_txn(&mut txn, 0, &i.to_string(), PosType::Unicode)
3008 .unwrap();
3009 list.insert_with_txn(&mut txn, 0, i.into()).unwrap();
3010 }
3011 txn.commit().unwrap();
3012 let b = LoroDoc::new();
3013 b.import(&loro.export(ExportMode::Snapshot).unwrap())
3014 .unwrap();
3015 loro.checkout(&Frontiers::default()).unwrap();
3016 {
3017 let json = &loro.get_deep_value();
3018 assert_eq!(
3019 json.to_json_value(),
3020 serde_json::json!({"text":"","list":[],"map":{}})
3021 );
3022 }
3023
3024 b.checkout(&ID::new(1, 2).into()).unwrap();
3025 {
3026 let json = &b.get_deep_value();
3027 assert_eq!(
3028 json.to_json_value(),
3029 serde_json::json!({"text":"0","list":[0],"map":{"key":0}})
3030 );
3031 }
3032
3033 loro.checkout(&ID::new(1, 3).into()).unwrap();
3034 {
3035 let json = &loro.get_deep_value();
3036 assert_eq!(
3037 json.to_json_value(),
3038 serde_json::json!({"text":"0","list":[0],"map":{"key":1}})
3039 );
3040 }
3041
3042 b.checkout(&ID::new(1, 29).into()).unwrap();
3043 {
3044 let json = &b.get_deep_value();
3045 assert_eq!(
3046 json.to_json_value(),
3047 serde_json::json!({"text":"9876543210","list":[9,8,7,6,5,4,3,2,1,0],"map":{"key":9}})
3048 );
3049 }
3050 }
3051
3052 #[test]
3053 fn import_batch_err_181() {
3054 let a = LoroDoc::new_auto_commit();
3055 let update_a = a.export(ExportMode::Snapshot);
3056 let b = LoroDoc::new_auto_commit();
3057 b.import_batch(&[update_a.unwrap()]).unwrap();
3058 b.get_text("text")
3059 .insert(0, "hello", PosType::Unicode)
3060 .unwrap();
3061 b.commit_then_renew();
3062 let oplog = b.oplog().lock();
3063 drop(oplog);
3064 b.export(ExportMode::all_updates()).unwrap();
3065 }
3066
3067 #[test]
3068 fn poisoned_mutex_keeps_follow_up_operations_failed() {
3069 let doc = LoroDoc::new();
3070 let oplog = doc.oplog.clone();
3071 let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
3072 let _guard = oplog.lock();
3073 panic!("poison oplog");
3074 }));
3075
3076 let err = std::panic::catch_unwind(AssertUnwindSafe(|| doc.oplog_vv()))
3077 .expect_err("poisoned lock should continue to fail fast");
3078 let msg = if let Some(msg) = err.downcast_ref::<&str>() {
3079 (*msg).to_string()
3080 } else if let Some(msg) = err.downcast_ref::<String>() {
3081 msg.clone()
3082 } else {
3083 String::new()
3084 };
3085 assert!(msg.contains("poisoned LoroMutex"), "{msg}");
3086 }
3087
3088 #[test]
3089 fn repeated_independent_scalar_root_imports_scan_history_once() {
3090 let base = LoroDoc::new_auto_commit();
3091 base.set_peer_id(1).unwrap();
3092 for i in 0..64 {
3093 base.get_map("existing")
3094 .insert(&format!("key-{i}"), i)
3095 .unwrap();
3096 base.commit_then_renew();
3097 }
3098
3099 let target = LoroDoc::new();
3100 target
3101 .import(&base.export(ExportMode::Snapshot).unwrap())
3102 .unwrap();
3103 assert!(!target.has_history_cache());
3104
3105 for i in 0_u64..8 {
3106 let remote = LoroDoc::new_auto_commit();
3107 remote.set_peer_id(2 + i).unwrap();
3108 remote
3109 .get_map(format!("isolated-{i}"))
3110 .insert("value", i as i32)
3111 .unwrap();
3112 let update = remote.export(ExportMode::all_updates()).unwrap();
3113 target.import(&update).unwrap();
3114 assert_eq!(
3115 target.get_map(format!("isolated-{i}")).get("value"),
3116 Some((i as i32).into())
3117 );
3118 }
3119
3120 assert!(!target.has_history_cache());
3121 assert_eq!(
3122 target
3123 .oplog
3124 .lock()
3125 .change_store()
3126 .root_history_scan_count_for_test(),
3127 1
3128 );
3129 }
3130
3131 #[test]
3132 fn independent_fast_path_rejects_root_present_only_in_snapshot_state() {
3133 fn snapshot_sections(doc: &LoroDoc) -> crate::encoding::fast_snapshot::Snapshot {
3134 let (_, txn_guard) = doc.implicit_commit_then_stop();
3135 drop(txn_guard);
3136 crate::encoding::fast_snapshot::encode_snapshot_inner(doc).unwrap()
3137 }
3138
3139 let base = LoroDoc::new_auto_commit();
3140 base.set_peer_id(1).unwrap();
3141 base.get_map("existing").insert("value", "base").unwrap();
3142 let base_sections = snapshot_sections(&base);
3143
3144 let state_donor = LoroDoc::new_auto_commit();
3145 state_donor.set_peer_id(999).unwrap();
3146 state_donor
3147 .get_map("isolated")
3148 .insert("value", "stale")
3149 .unwrap();
3150 let donor_sections = snapshot_sections(&state_donor);
3151
3152 let target = LoroDoc::new();
3153 crate::encoding::fast_snapshot::decode_snapshot_inner(
3154 crate::encoding::fast_snapshot::Snapshot {
3155 oplog_bytes: base_sections.oplog_bytes,
3156 state_bytes: donor_sections.state_bytes,
3157 shallow_root_state_bytes: Bytes::new(),
3158 },
3159 &target,
3160 Default::default(),
3161 )
3162 .unwrap();
3163 assert!(!target.has_history_cache());
3164
3165 let left = LoroDoc::new_auto_commit();
3166 left.set_peer_id(2).unwrap();
3167 left.get_map("isolated").insert("value", "left").unwrap();
3168 let right = LoroDoc::new_auto_commit();
3169 right.set_peer_id(3).unwrap();
3170 right.get_map("isolated").insert("value", "winner").unwrap();
3171 let aggregate = LoroDoc::new();
3172 aggregate
3173 .import(&left.export(ExportMode::all_updates()).unwrap())
3174 .unwrap();
3175 aggregate
3176 .import(&right.export(ExportMode::all_updates()).unwrap())
3177 .unwrap();
3178
3179 target
3180 .import(&aggregate.export(ExportMode::all_updates()).unwrap())
3181 .unwrap();
3182
3183 assert_eq!(
3184 target.get_map("isolated").get("value"),
3185 Some("winner".into())
3186 );
3187 assert!(target.has_history_cache());
3188 }
3189
3190 #[test]
3191 fn independent_scalar_root_import_rolls_back_after_state_failure() {
3192 let base = LoroDoc::new_auto_commit();
3193 base.set_peer_id(1).unwrap();
3194 base.get_map("existing").insert("value", "base").unwrap();
3195
3196 let target = LoroDoc::new();
3197 target
3198 .import(&base.export(ExportMode::Snapshot).unwrap())
3199 .unwrap();
3200 let vv_before = target.oplog_vv();
3201 let state_before = target.get_deep_value();
3202
3203 let remote = LoroDoc::new_auto_commit();
3204 remote.set_peer_id(2).unwrap();
3205 remote.get_map("isolated").insert("value", 7).unwrap();
3206 let update = remote.export(ExportMode::all_updates()).unwrap();
3207
3208 crate::state::fail_next_import_state_apply_for_test();
3209 let err = target.import(&update).unwrap_err();
3210 assert!(err.to_string().contains("state apply failpoint"));
3211 assert_eq!(target.oplog_vv(), vv_before);
3212 assert_eq!(target.get_deep_value(), state_before);
3213 assert!(!target.has_history_cache());
3214
3215 target.import(&update).unwrap();
3216 assert_eq!(target.get_map("isolated").get("value"), Some(7.into()));
3217 }
3218
3219 #[test]
3220 fn independent_fast_path_rejects_root_name_seen_in_history() {
3221 let base = LoroDoc::new_auto_commit();
3222 base.set_peer_id(1).unwrap();
3223 base.get_map("shared").insert("value", "base").unwrap();
3224
3225 let target = LoroDoc::new();
3226 target
3227 .import(&base.export(ExportMode::Snapshot).unwrap())
3228 .unwrap();
3229 assert!(!target.has_history_cache());
3230
3231 let remote = LoroDoc::new_auto_commit();
3232 remote.set_peer_id(999).unwrap();
3233 remote.get_map("shared").insert("value", "remote").unwrap();
3234 target
3235 .import(&remote.export(ExportMode::all_updates()).unwrap())
3236 .unwrap();
3237
3238 assert_eq!(target.get_map("shared").get("value"), Some("remote".into()));
3239 assert!(target.has_history_cache());
3240 }
3241
3242 #[test]
3243 fn independent_fast_path_rejects_deleted_root_name() {
3244 let base = LoroDoc::new_auto_commit();
3245 base.set_peer_id(1).unwrap();
3246 let root_id = ContainerID::new_root("deleted", ContainerType::Map);
3247 base.get_map("deleted").insert("value", "base").unwrap();
3248 base.delete_root_container(root_id);
3249
3250 let target = LoroDoc::new();
3251 target
3252 .import(&base.export(ExportMode::Snapshot).unwrap())
3253 .unwrap();
3254 assert!(!target.has_history_cache());
3255
3256 let remote = LoroDoc::new_auto_commit();
3257 remote.set_peer_id(2).unwrap();
3258 remote.get_map("deleted").insert("value", "remote").unwrap();
3259 target
3260 .import(&remote.export(ExportMode::all_updates()).unwrap())
3261 .unwrap();
3262
3263 assert!(target.has_history_cache());
3264 }
3265}