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
70#[cfg(test)]
71thread_local! {
72 static PANIC_AT_BATCH_IMPORT_BLOB: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
75}
76
77#[cfg(test)]
78pub(crate) fn panic_at_batch_import_blob_for_test(blobs_to_import_first: usize) {
79 PANIC_AT_BATCH_IMPORT_BLOB.with(|f| f.set(Some(blobs_to_import_first)));
80}
81
82#[cfg(test)]
83fn take_panic_at_batch_import_blob_for_test() {
84 PANIC_AT_BATCH_IMPORT_BLOB.with(|f| match f.get() {
85 Some(0) => {
86 f.set(None);
87 panic!("batch import failpoint");
88 }
89 Some(n) => f.set(Some(n - 1)),
90 None => {}
91 });
92}
93
94impl std::fmt::Debug for LoroDocInner {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("LoroDoc")
97 .field("config", &self.config)
98 .field("auto_commit", &self.auto_commit)
99 .field("detached", &self.detached)
100 .finish()
101 }
102}
103
104impl LoroDoc {
105 #[inline]
113 pub fn with_barrier<F, R>(&self, f: F) -> R
114 where
115 F: FnOnce() -> R,
116 {
117 let (options, guard) = self.implicit_commit_then_stop();
118 let result = f();
119 drop(guard);
120 self.renew_txn_if_auto_commit(options);
121 result
122 }
123
124 pub fn new() -> Self {
125 let visible_op_count = Arc::new(AtomicUsize::new(0));
126 let oplog = OpLog::new(visible_op_count.clone());
127 let arena = oplog.arena.clone();
128 let config: Configure = oplog.configure.clone();
129 let lock_group = LoroLockGroup::new();
130 let global_txn = Arc::new(lock_group.new_lock(None, LockKind::Txn));
131 let inner = Arc::new_cyclic(|w| {
132 let state = DocState::new_arc(w.clone(), arena.clone(), config.clone(), &lock_group);
133 LoroDocInner {
134 oplog: Arc::new(lock_group.new_lock(oplog, LockKind::OpLog)),
135 state,
136 config,
137 visible_op_count,
138 detached: AtomicBool::new(false),
139 auto_commit: AtomicBool::new(false),
140 observer: Arc::new(Observer::new(arena.clone())),
141 diff_calculator: Arc::new(
142 lock_group.new_lock(DiffCalculator::new(true), LockKind::DiffCalculator),
143 ),
144 txn: global_txn,
145 arena,
146 local_update_subs: SubscriberSetWithQueue::new(),
147 peer_id_change_subs: SubscriberSetWithQueue::new(),
148 pre_commit_subs: SubscriberSetWithQueue::new(),
149 first_commit_from_peer_subs: SubscriberSetWithQueue::new(),
150 }
151 });
152 LoroDoc { inner }
153 }
154
155 pub fn fork(&self) -> Self {
156 if self.is_detached() {
157 return self
158 .fork_at(&self.state_frontiers())
159 .expect("fork_at on detached doc should not fail");
160 }
161
162 let snapshot = self
163 .with_barrier(|| encoding::fast_snapshot::encode_snapshot_inner(self))
164 .expect("forking a valid document should encode a snapshot");
165 let doc = Self::new();
166 doc.with_barrier(|| {
167 encoding::fast_snapshot::decode_snapshot_inner(snapshot, &doc, Default::default())
168 })
169 .unwrap();
170 doc.set_config(&self.config);
171 if self.auto_commit.load(std::sync::atomic::Ordering::Relaxed) {
172 doc.start_auto_commit();
173 }
174 doc
175 }
176 pub fn set_detached_editing(&self, enable: bool) {
192 self.config.set_detached_editing(enable);
193 if enable && self.is_detached() {
194 self.with_barrier(|| {
195 self.renew_peer_id();
196 });
197 }
198 }
199
200 #[inline]
202 pub fn new_auto_commit() -> Self {
203 let doc = Self::new();
204 doc.start_auto_commit();
205 doc
206 }
207
208 #[inline(always)]
209 pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
210 if peer == PeerID::MAX {
211 return Err(LoroError::InvalidPeerID);
212 }
213 let next_id = self.oplog.lock().next_id(peer);
214 if self.auto_commit.load(Acquire) {
215 let doc_state = self.state.lock();
216 doc_state
217 .peer
218 .store(peer, std::sync::atomic::Ordering::Relaxed);
219
220 if doc_state.is_in_txn() {
221 drop(doc_state);
222 self.with_barrier(|| {});
224 }
225 self.peer_id_change_subs.emit(&(), next_id);
226 return Ok(());
227 }
228
229 let doc_state = self.state.lock();
230 if doc_state.is_in_txn() {
231 return Err(LoroError::TransactionError(
232 "Cannot change peer id during transaction"
233 .to_string()
234 .into_boxed_str(),
235 ));
236 }
237
238 doc_state
239 .peer
240 .store(peer, std::sync::atomic::Ordering::Relaxed);
241 drop(doc_state);
242 self.peer_id_change_subs.emit(&(), next_id);
243 Ok(())
244 }
245
246 pub(crate) fn renew_peer_id(&self) {
248 let mut peer_id = DefaultRandom.next_u64();
249 while peer_id == PeerID::MAX {
250 peer_id = DefaultRandom.next_u64();
251 }
252 self.set_peer_id(peer_id).unwrap();
253 }
254
255 #[inline]
267 #[must_use]
268 pub fn implicit_commit_then_stop(
269 &self,
270 ) -> (
271 Option<CommitOptions>,
272 LoroMutexGuard<'_, Option<Transaction>>,
273 ) {
274 let (a, b) = self.commit_internal(CommitOptions::new().immediate_renew(false), true);
276 (a, b.unwrap())
277 }
278
279 #[inline]
284 pub fn commit_then_renew(&self) -> Option<CommitOptions> {
285 self.commit_internal(CommitOptions::new().immediate_renew(true), false)
287 .0
288 }
289
290 fn before_commit(&self) -> Option<LoroMutexGuard<'_, Option<Transaction>>> {
295 let mut txn_guard = self.txn.lock();
296 let Some(txn) = txn_guard.as_mut() else {
297 return Some(txn_guard);
298 };
299
300 if txn.is_peer_first_appearance {
301 txn.is_peer_first_appearance = false;
302 drop(txn_guard);
303 self.first_commit_from_peer_subs.emit(
305 &(),
306 FirstCommitFromPeerPayload {
307 peer: self.peer_id(),
308 },
309 );
310 }
311
312 None
313 }
314
315 #[instrument(skip_all)]
323 fn commit_internal(
324 &self,
325 config: CommitOptions,
326 preserve_on_empty: bool,
327 ) -> (
328 Option<CommitOptions>,
329 Option<LoroMutexGuard<'_, Option<Transaction>>>,
330 ) {
331 if !self.auto_commit.load(Acquire) {
332 let txn_guard = self.txn.lock();
333 return (None, Some(txn_guard));
336 }
337
338 loop {
339 if let Some(txn_guard) = self.before_commit() {
340 return (None, Some(txn_guard));
341 }
342
343 let mut txn_guard = self.txn.lock();
344 let txn = txn_guard.take();
345 let Some(mut txn) = txn else {
346 return (None, Some(txn_guard));
347 };
348 let on_commit = txn.take_on_commit();
349 if let Some(origin) = config.origin.clone() {
350 txn.set_origin(origin);
351 }
352
353 if let Some(timestamp) = config.timestamp {
354 txn.set_timestamp(timestamp);
355 }
356
357 if let Some(msg) = config.commit_msg.as_ref() {
358 txn.set_msg(Some(msg.clone()));
359 }
360
361 let id_span = txn.id_span();
362 let mut options = txn.commit().unwrap();
363 if let Some(opts) = options.as_mut() {
365 if config.origin.is_some() {
367 opts.set_origin(None);
368 }
369 if !preserve_on_empty {
371 options = None;
372 }
373 }
374 if config.immediate_renew && self.can_edit() {
375 let mut t = self.txn().unwrap();
376 if let Some(options) = options.as_ref() {
377 t.set_options(options.clone());
378 }
379 *txn_guard = Some(t);
380 }
381
382 if let Some(on_commit) = on_commit {
383 drop(txn_guard);
384 on_commit(&self.state, &self.oplog, id_span);
385 txn_guard = self.txn.lock();
386 if !config.immediate_renew && txn_guard.is_some() {
387 continue;
389 }
390 }
391
392 return (
393 options,
394 if !config.immediate_renew {
395 Some(txn_guard)
396 } else {
397 None
398 },
399 );
400 }
401 }
402
403 #[instrument(skip_all)]
408 pub fn commit_with(
409 &self,
410 config: CommitOptions,
411 ) -> (
412 Option<CommitOptions>,
413 Option<LoroMutexGuard<'_, Option<Transaction>>>,
414 ) {
415 self.commit_internal(config, false)
416 }
417
418 pub fn set_next_commit_message(&self, message: &str) {
420 let mut binding = self.txn.lock();
421 let Some(txn) = binding.as_mut() else {
422 return;
423 };
424
425 if message.is_empty() {
426 txn.set_msg(None)
427 } else {
428 txn.set_msg(Some(message.into()))
429 }
430 }
431
432 pub fn set_next_commit_origin(&self, origin: &str) {
434 let mut txn = self.txn.lock();
435 if let Some(txn) = txn.as_mut() {
436 txn.set_origin(origin.into());
437 }
438 }
439
440 pub fn set_next_commit_timestamp(&self, timestamp: Timestamp) {
442 let mut txn = self.txn.lock();
443 if let Some(txn) = txn.as_mut() {
444 txn.set_timestamp(timestamp);
445 }
446 }
447
448 pub fn set_next_commit_options(&self, options: CommitOptions) {
450 let mut txn = self.txn.lock();
451 if let Some(txn) = txn.as_mut() {
452 txn.set_options(options);
453 }
454 }
455
456 pub fn clear_next_commit_options(&self) {
458 let mut txn = self.txn.lock();
459 if let Some(txn) = txn.as_mut() {
460 txn.set_options(CommitOptions::new());
461 }
462 }
463
464 #[inline]
475 pub fn set_record_timestamp(&self, record: bool) {
476 self.config.set_record_timestamp(record);
477 }
478
479 #[inline]
484 pub fn set_change_merge_interval(&self, interval: i64) {
485 self.config.set_merge_interval(interval);
486 }
487
488 pub fn can_edit(&self) -> bool {
489 !self.is_detached() || self.config.detached_editing()
490 }
491
492 pub fn is_detached_editing_enabled(&self) -> bool {
493 self.config.detached_editing()
494 }
495
496 #[inline]
497 pub fn config_text_style(&self, text_style: StyleConfigMap) {
498 self.config.text_style_config.write().map = text_style.map;
499 }
500
501 #[inline]
502 pub fn config_default_text_style(&self, text_style: Option<StyleConfig>) {
503 self.config.text_style_config.write().default_style = text_style;
504 }
505 pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
506 let doc = Self::new();
507 let ParsedHeaderAndBody { mode, body, .. } = parse_header_and_body(bytes, true)?;
508 if mode.is_snapshot() {
509 doc.with_barrier(|| -> Result<(), LoroError> {
510 decode_snapshot(&doc, mode, body, Default::default())?;
511 Ok(())
512 })?;
513 Ok(doc)
514 } else {
515 Err(LoroError::DecodeError(
516 "Invalid encode mode".to_string().into(),
517 ))
518 }
519 }
520
521 #[inline(always)]
523 pub fn can_reset_with_snapshot(&self) -> bool {
524 let oplog = self.oplog.lock();
525 if oplog.batch_importing {
526 return false;
527 }
528
529 if self.is_detached() {
530 return false;
531 }
532
533 oplog.is_empty() && self.state.lock().can_import_snapshot()
534 }
535
536 #[inline(always)]
542 pub fn is_detached(&self) -> bool {
543 self.detached.load(Acquire)
544 }
545
546 pub(crate) fn set_detached(&self, detached: bool) {
547 self.detached.store(detached, Release);
548 }
549
550 #[inline(always)]
551 pub fn peer_id(&self) -> PeerID {
552 self.state
553 .lock()
554 .peer
555 .load(std::sync::atomic::Ordering::Relaxed)
556 }
557
558 #[inline(always)]
559 pub fn detach(&self) {
560 self.with_barrier(|| self.set_detached(true));
561 }
562
563 #[inline(always)]
564 pub fn attach(&self) {
565 self.checkout_to_latest()
566 }
567
568 pub fn state_timestamp(&self) -> Timestamp {
571 let f = { self.state.lock().frontiers.clone() };
573 self.oplog.lock().get_timestamp_of_version(&f)
574 }
575
576 #[inline(always)]
577 pub fn app_state(&self) -> &Arc<LoroMutex<DocState>> {
578 &self.state
579 }
580
581 #[inline]
582 pub fn get_state_deep_value(&self) -> LoroValue {
583 self.state.lock().get_deep_value()
584 }
585
586 #[inline(always)]
587 pub fn oplog(&self) -> &Arc<LoroMutex<OpLog>> {
588 &self.oplog
589 }
590
591 #[inline(always)]
592 pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError> {
593 let s = debug_span!("import", peer = self.peer_id());
594 let _e = s.enter();
595 self.import_with(bytes, Default::default())
596 }
597
598 #[inline]
599 pub fn import_with(
600 &self,
601 bytes: &[u8],
602 origin: InternalString,
603 ) -> Result<ImportStatus, LoroError> {
604 self.with_barrier(|| self._import_with(bytes, origin))
605 }
606
607 #[tracing::instrument(skip_all)]
608 fn _import_with(
609 &self,
610 bytes: &[u8],
611 origin: InternalString,
612 ) -> Result<ImportStatus, LoroError> {
613 ensure_cov::notify_cov("loro_internal::import");
614 let parsed = parse_header_and_body(bytes, true)?;
615 loro_common::info!("Importing with mode={:?}", &parsed.mode);
616 let result = match parsed.mode {
617 EncodeMode::OutdatedRle => {
618 if self.state.lock().is_in_txn() {
619 return Err(LoroError::ImportWhenInTxn);
620 }
621
622 let s = tracing::span!(
623 tracing::Level::INFO,
624 "Import updates ",
625 peer = self.peer_id()
626 );
627 let _e = s.enter();
628 self.update_oplog_and_apply_delta_to_state_if_needed(
629 |oplog| oplog.decode(parsed),
630 origin,
631 )
632 }
633 EncodeMode::OutdatedSnapshot => {
634 if self.can_reset_with_snapshot() {
635 loro_common::info!("Init by snapshot {}", self.peer_id());
636 decode_snapshot(self, parsed.mode, parsed.body, origin)
637 } else {
638 self.update_oplog_and_apply_delta_to_state_if_needed(
639 |oplog| oplog.decode(parsed),
640 origin,
641 )
642 }
643 }
644 EncodeMode::FastSnapshot => {
645 if self.can_reset_with_snapshot() {
646 ensure_cov::notify_cov("loro_internal::import::snapshot");
647 loro_common::info!("Init by fast snapshot {}", self.peer_id());
648 decode_snapshot(self, parsed.mode, parsed.body, origin)
649 } else {
650 self.import_changes_and_apply_delta_to_state_if_needed(
651 |oplog| encoding::decode_oplog_changes(oplog, parsed),
652 origin,
653 )
654
655 }
660 }
661 EncodeMode::FastUpdates => self.import_changes_and_apply_delta_to_state_if_needed(
662 |oplog| encoding::decode_oplog_changes(oplog, parsed),
663 origin,
664 ),
665 EncodeMode::Auto => {
666 unreachable!()
667 }
668 };
669
670 self.emit_events();
671
672 result
673 }
674
675 #[tracing::instrument(skip_all)]
676 pub(crate) fn update_oplog_and_apply_delta_to_state_if_needed(
677 &self,
678 f: impl FnOnce(&mut OpLog) -> Result<ImportStatus, LoroError>,
679 origin: InternalString,
680 ) -> Result<ImportStatus, LoroError> {
681 let mut oplog = self.oplog.lock();
682 let owns_rollback = !oplog.has_import_rollback();
688 if owns_rollback {
689 oplog.begin_import_rollback();
690 }
691 if !self.is_detached() {
692 let old_vv = oplog.vv().clone();
693 let old_frontiers = oplog.frontiers().clone();
694 let result = f(&mut oplog);
695 if &old_vv != oplog.vv() {
696 let mut diff = DiffCalculator::new(false);
697 let (diff, diff_mode) = diff.calc_diff_internal(
698 &oplog,
699 &old_vv,
700 &old_frontiers,
701 oplog.vv(),
702 oplog.dag.get_frontiers(),
703 None,
704 );
705 let mut state = self.state.lock();
706 if let Err(e) = state.apply_diff(
707 InternalDocDiff {
708 origin,
709 diff: (diff).into(),
710 by: EventTriggerKind::Import,
711 new_version: Cow::Owned(oplog.frontiers().clone()),
712 },
713 diff_mode,
714 ) {
715 oplog.end_import_rollback(owns_rollback, false);
716 return Err(e);
717 }
718 }
719 match result {
720 Ok(result) => {
721 oplog.end_import_rollback(owns_rollback, true);
722 Ok(result)
723 }
724 Err(e) => {
725 let keep_prefix = &old_vv != oplog.vv();
730 oplog.end_import_rollback(owns_rollback, keep_prefix);
731 Err(e)
732 }
733 }
734 } else {
735 match f(&mut oplog) {
736 Ok(result) => {
737 oplog.end_import_rollback(owns_rollback, true);
738 Ok(result)
739 }
740 Err(e) => {
741 oplog.end_import_rollback(owns_rollback, false);
742 Err(e)
743 }
744 }
745 }
746 }
747
748 #[tracing::instrument(skip_all)]
749 pub(crate) fn import_changes_and_apply_delta_to_state_if_needed(
750 &self,
751 decode_changes: impl FnOnce(&mut OpLog) -> Result<Vec<Change>, LoroError>,
752 origin: InternalString,
753 ) -> Result<ImportStatus, LoroError> {
754 let mut oplog = self.oplog.lock();
755 let arena_checkpoint = oplog.arena.checkpoint_for_rollback();
756 let changes = match decode_changes(&mut oplog) {
757 Ok(changes) => changes,
758 Err(e) => {
759 oplog.arena.rollback(arena_checkpoint);
760 return Err(e);
761 }
762 };
763
764 let preflight = oplog.preflight_import_changes(&changes);
765 if preflight.has_deps_before_shallow_root
766 && (self.is_detached() || !preflight.applies_to_dag)
767 {
768 oplog.arena.rollback(arena_checkpoint);
769 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
770 }
771
772 if self.is_detached() {
773 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
774 if result.has_deps_before_shallow_root {
775 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
776 }
777
778 return Ok(result.status);
779 }
780
781 if !preflight.applies_to_dag {
782 let pending_root_containers = pending_root_containers_to_materialize(&oplog, &changes);
783 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
784 if result.has_deps_before_shallow_root {
785 oplog.arena.rollback(arena_checkpoint);
786 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
787 }
788
789 if !pending_root_containers.is_empty() {
790 let mut state = self.state.lock();
791 for id in pending_root_containers {
792 state.ensure_container(&id);
793 }
794 }
795
796 return Ok(result.status);
797 }
798
799 let old_vv = oplog.vv().clone();
800 let old_frontiers = oplog.frontiers().clone();
801 let isolated_batch = isolated_scalar_root_batch(&oplog, &changes).filter(|(_, names)| {
803 let mut state = self.state.lock();
804 names.iter().all(|name| {
805 let id = ContainerID::new_root(name, ContainerType::Map);
806 !state.store.contains_id(&id)
807 })
808 });
809 let rollback_enabled = preflight.needs_state_apply_rollback;
810 if rollback_enabled {
811 oplog.begin_import_rollback_with_arena(arena_checkpoint);
812 }
813
814 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
815 if &old_vv != oplog.vv() {
816 let mut diff = DiffCalculator::new(false);
817 let isolated_batch = isolated_batch.filter(|(component_vv, _)| {
820 component_vv
821 .iter()
822 .all(|(peer, end)| oplog.vv().get(peer) == Some(end))
823 && oplog.vv().iter().all(|(peer, end)| {
824 let old_end = old_vv.get(peer).copied().unwrap_or(0);
825 let component_end = component_vv.get(peer).copied().unwrap_or(0);
826 *end == old_end.max(component_end)
827 })
828 });
829 let (diff, diff_mode) = if let Some((component_vv, _)) = isolated_batch {
830 let component_frontiers = oplog.dag.vv_to_frontiers(&component_vv);
831 let (diff, _) = diff.calc_diff_internal(
832 &oplog,
833 &VersionVector::default(),
834 &Frontiers::default(),
835 &component_vv,
836 &component_frontiers,
837 None,
838 );
839 (diff, DiffMode::Import)
840 } else {
841 diff.calc_diff_internal(
842 &oplog,
843 &old_vv,
844 &old_frontiers,
845 oplog.vv(),
846 oplog.dag.get_frontiers(),
847 None,
848 )
849 };
850 let mut state = self.state.lock();
851 if let Err(e) = state.apply_diff(
852 InternalDocDiff {
853 origin,
854 diff: (diff).into(),
855 by: EventTriggerKind::Import,
856 new_version: Cow::Owned(oplog.frontiers().clone()),
857 },
858 diff_mode,
859 ) {
860 if rollback_enabled {
861 oplog.rollback_import();
862 return Err(e);
863 }
864
865 panic!("state apply returned Err for import without rollback guard: {e}");
866 }
867 }
868
869 if result.has_deps_before_shallow_root {
870 if rollback_enabled {
871 oplog.commit_import_rollback();
872 }
873 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
874 }
875
876 if rollback_enabled {
877 oplog.commit_import_rollback();
878 }
879 Ok(result.status)
880 }
881
882 fn emit_events(&self) {
883 let events = {
885 let mut state = self.state.lock();
886 state.take_events()
887 };
888 for event in events {
889 self.observer.emit(event);
890 }
891 }
892
893 pub(crate) fn drop_pending_events(&self) -> Vec<DocDiff> {
894 let mut state = self.state.lock();
895 state.take_events()
896 }
897
898 #[tracing::instrument(skip_all)]
902 pub fn import_json_updates<T: TryInto<JsonSchema>>(&self, json: T) -> LoroResult<ImportStatus> {
903 let json = json.try_into().map_err(|_| LoroError::InvalidJsonSchema)?;
904 self.with_barrier(|| {
905 let result = self.import_changes_and_apply_delta_to_state_if_needed(
906 |oplog| crate::encoding::json_schema::decode_json_changes(json, &oplog.arena),
907 Default::default(),
908 );
909 self.emit_events();
910 result
911 })
912 }
913
914 pub fn export_json_updates(
915 &self,
916 start_vv: &VersionVector,
917 end_vv: &VersionVector,
918 with_peer_compression: bool,
919 ) -> JsonSchema {
920 self.with_barrier(|| {
921 let oplog = self.oplog.lock();
922 let mut start_vv = start_vv;
923 let _temp: Option<VersionVector>;
924 if !oplog.dag.shallow_since_vv().is_empty() {
925 let mut include_all = true;
927 for (peer, counter) in oplog.dag.shallow_since_vv().iter() {
928 if start_vv.get(peer).unwrap_or(&0) < counter {
929 include_all = false;
930 break;
931 }
932 }
933 if !include_all {
934 let mut vv = start_vv.clone();
935 for (&peer, &counter) in oplog.dag.shallow_since_vv().iter() {
936 vv.extend_to_include_end_id(ID::new(peer, counter));
937 }
938 _temp = Some(vv);
939 start_vv = _temp.as_ref().unwrap();
940 }
941 }
942
943 crate::encoding::json_schema::export_json(
944 &oplog,
945 start_vv,
946 end_vv,
947 with_peer_compression,
948 )
949 })
950 }
951
952 pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange> {
953 let oplog = self.oplog.lock();
954 let mut changes = export_json_in_id_span(&oplog, id_span);
955 if let Some(uncommit) = oplog.get_uncommitted_change_in_span(id_span) {
956 let change_json = encode_change(ChangeRef::from_change(&uncommit), &self.arena, None);
957 changes.push(change_json);
958 }
959 changes
960 }
961
962 #[inline]
964 pub fn oplog_vv(&self) -> VersionVector {
965 self.oplog.lock().vv().clone()
966 }
967
968 #[inline]
970 pub fn state_vv(&self) -> VersionVector {
971 let oplog = self.oplog.lock();
972 let f = &self.state.lock().frontiers;
973 oplog.dag.frontiers_to_vv(f).unwrap()
974 }
975
976 pub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrHandler> {
977 let value: LoroValue = self.state.lock().get_value_by_path(path)?;
978 if let LoroValue::Container(c) = value {
979 Some(ValueOrHandler::Handler(Handler::new_attached(
980 c.clone(),
981 self.clone(),
982 )))
983 } else {
984 Some(ValueOrHandler::Value(value))
985 }
986 }
987
988 pub fn get_by_str_path(&self, path: &str) -> Option<ValueOrHandler> {
990 let path = str_to_path(path)?;
991 self.get_by_path(&path)
992 }
993
994 pub fn get_uncommitted_ops_as_json(&self) -> Option<JsonSchema> {
995 let arena = &self.arena;
996 let txn = self.txn.lock();
997 let txn = txn.as_ref()?;
998 let ops_ = txn.local_ops();
999 let new_id = ID {
1000 peer: *txn.peer(),
1001 counter: ops_.first()?.counter,
1002 };
1003 let change = ChangeRef {
1004 id: &new_id,
1005 deps: txn.frontiers(),
1006 timestamp: &txn
1007 .timestamp()
1008 .as_ref()
1009 .copied()
1010 .unwrap_or_else(|| self.oplog.lock().get_timestamp_for_next_txn()),
1011 commit_msg: txn.msg(),
1012 ops: ops_,
1013 lamport: txn.lamport(),
1014 };
1015 let json = encode_change_to_json(change, arena);
1016 Some(json)
1017 }
1018
1019 #[inline]
1020 pub fn get_handler(&self, id: ContainerID) -> Option<Handler> {
1021 if self.has_container(&id) {
1022 self.ensure_root_container(&id);
1023 Some(Handler::new_attached(id, self.clone()))
1024 } else {
1025 None
1026 }
1027 }
1028
1029 #[inline]
1030 fn ensure_root_container(&self, id: &ContainerID) {
1031 if id.is_root() && !id.is_mergeable() {
1035 self.state.lock().ensure_container(id);
1036 }
1037 }
1038
1039 #[inline]
1042 pub fn try_get_text<I: IntoContainerId>(&self, id: I) -> Option<TextHandler> {
1043 let id = id.into_container_id(&self.arena, ContainerType::Text);
1044 if !self.has_container(&id) {
1045 return None;
1046 }
1047 self.ensure_root_container(&id);
1048 Handler::new_attached(id, self.clone()).into_text().ok()
1049 }
1050
1051 #[inline]
1054 pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
1055 self.try_get_text(id)
1056 .expect("The container does not exist in the document. Use `try_get_text` or `get_container` to check for existence.")
1057 }
1058
1059 #[inline]
1062 pub fn try_get_list<I: IntoContainerId>(&self, id: I) -> Option<ListHandler> {
1063 let id = id.into_container_id(&self.arena, ContainerType::List);
1064 if !self.has_container(&id) {
1065 return None;
1066 }
1067 self.ensure_root_container(&id);
1068 Handler::new_attached(id, self.clone()).into_list().ok()
1069 }
1070
1071 #[inline]
1074 pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
1075 self.try_get_list(id)
1076 .expect("The container does not exist in the document. Use `try_get_list` or `get_container` to check for existence.")
1077 }
1078
1079 #[inline]
1082 pub fn try_get_movable_list<I: IntoContainerId>(&self, id: I) -> Option<MovableListHandler> {
1083 let id = id.into_container_id(&self.arena, ContainerType::MovableList);
1084 if !self.has_container(&id) {
1085 return None;
1086 }
1087 self.ensure_root_container(&id);
1088 Handler::new_attached(id, self.clone())
1089 .into_movable_list()
1090 .ok()
1091 }
1092
1093 #[inline]
1096 pub fn get_movable_list<I: IntoContainerId>(&self, id: I) -> MovableListHandler {
1097 self.try_get_movable_list(id)
1098 .expect("The container does not exist in the document. Use `try_get_movable_list` or `get_container` to check for existence.")
1099 }
1100
1101 #[inline]
1104 pub fn try_get_map<I: IntoContainerId>(&self, id: I) -> Option<MapHandler> {
1105 let id = id.into_container_id(&self.arena, ContainerType::Map);
1106 if !self.has_container(&id) {
1107 return None;
1108 }
1109 self.ensure_root_container(&id);
1110 Handler::new_attached(id, self.clone()).into_map().ok()
1111 }
1112
1113 #[inline]
1116 pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
1117 self.try_get_map(id)
1118 .expect("The container does not exist in the document. Use `try_get_map` or `get_container` to check for existence.")
1119 }
1120
1121 #[inline]
1124 pub fn try_get_tree<I: IntoContainerId>(&self, id: I) -> Option<TreeHandler> {
1125 let id = id.into_container_id(&self.arena, ContainerType::Tree);
1126 if !self.has_container(&id) {
1127 return None;
1128 }
1129 self.ensure_root_container(&id);
1130 Handler::new_attached(id, self.clone()).into_tree().ok()
1131 }
1132
1133 #[inline]
1136 pub fn get_tree<I: IntoContainerId>(&self, id: I) -> TreeHandler {
1137 self.try_get_tree(id)
1138 .expect("The container does not exist in the document. Use `try_get_tree` or `get_container` to check for existence.")
1139 }
1140
1141 #[cfg(feature = "counter")]
1142 pub fn try_get_counter<I: IntoContainerId>(
1143 &self,
1144 id: I,
1145 ) -> Option<crate::handler::counter::CounterHandler> {
1146 let id = id.into_container_id(&self.arena, ContainerType::Counter);
1147 if !self.has_container(&id) {
1148 return None;
1149 }
1150 self.ensure_root_container(&id);
1151 Handler::new_attached(id, self.clone()).into_counter().ok()
1152 }
1153
1154 #[cfg(feature = "counter")]
1155 pub fn get_counter<I: IntoContainerId>(
1156 &self,
1157 id: I,
1158 ) -> crate::handler::counter::CounterHandler {
1159 self.try_get_counter(id)
1160 .expect("The container does not exist in the document. Use `try_get_counter` or `get_container` to check for existence.")
1161 }
1162
1163 #[must_use]
1164 pub fn has_container(&self, id: &ContainerID) -> bool {
1165 if id.is_root() && !id.is_mergeable() {
1166 return true;
1167 }
1168
1169 let exist = self.state.lock().does_container_exist(id);
1170 exist
1171 }
1172
1173 #[instrument(level = "info", skip_all)]
1187 pub fn undo_internal(
1188 &self,
1189 id_span: IdSpan,
1190 container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1191 post_transform_base: Option<&DiffBatch>,
1192 before_diff: &mut dyn FnMut(&DiffBatch),
1193 ) -> LoroResult<CommitWhenDrop<'_>> {
1194 if !self.can_edit() {
1195 return Err(LoroError::EditWhenDetached);
1196 }
1197
1198 let (options, txn) = self.implicit_commit_then_stop();
1199 if !self.oplog().lock().vv().includes_id(id_span.id_last()) {
1200 self.renew_txn_if_auto_commit(options);
1201 return Err(LoroError::UndoInvalidIdSpan(id_span.id_last()));
1202 }
1203
1204 let (was_recording, latest_frontiers) = {
1205 let mut state = self.state.lock();
1206 let was_recording = state.is_recording();
1207 state.stop_and_clear_recording();
1208 (was_recording, state.frontiers.clone())
1209 };
1210
1211 let spans = self.oplog.lock().split_span_based_on_deps(id_span);
1212 let diff = crate::undo::undo(
1213 spans,
1214 match post_transform_base {
1215 Some(d) => Either::Right(d),
1216 None => Either::Left(&latest_frontiers),
1217 },
1218 |from, to| {
1219 self._checkout_without_emitting(from, false, false).unwrap();
1220 self.state.lock().start_recording();
1221 self._checkout_without_emitting(to, false, false).unwrap();
1222 let mut state = self.state.lock();
1223 let e = state.take_events();
1224 state.stop_and_clear_recording();
1225 DiffBatch::new(e)
1226 },
1227 before_diff,
1228 );
1229
1230 self._checkout_without_emitting(&latest_frontiers, false, false)?;
1234 self.set_detached(false);
1235 if was_recording {
1236 self.state.lock().start_recording();
1237 }
1238 drop(txn);
1239 self.start_auto_commit();
1240 if let Err(e) = self._apply_diff(diff, container_remap, true) {
1244 warn!("Undo Failed {:?}", e);
1245 }
1246
1247 if let Some(options) = options {
1248 self.set_next_commit_options(options);
1249 }
1250 Ok(CommitWhenDrop {
1251 doc: self,
1252 default_options: CommitOptions::new().origin("undo"),
1253 })
1254 }
1255
1256 pub fn revert_to(&self, target: &Frontiers) -> LoroResult<()> {
1262 let f = self.state_frontiers();
1265 let diff = self.diff(&f, target)?;
1266 self._apply_diff(diff, &mut Default::default(), false)
1267 }
1268
1269 pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<DiffBatch> {
1274 {
1275 let oplog = self.oplog.lock();
1278 let validate_frontiers = |frontiers: &Frontiers| -> LoroResult<()> {
1279 for id in frontiers.iter() {
1280 if !oplog.dag.contains(id) {
1281 return Err(LoroError::FrontiersNotFound(id));
1282 }
1283 }
1284
1285 if oplog.dag.is_before_shallow_root(frontiers) {
1286 return Err(LoroError::SwitchToVersionBeforeShallowRoot);
1287 }
1288
1289 Ok(())
1290 };
1291
1292 validate_frontiers(a)?;
1293 validate_frontiers(b)?;
1294 }
1295
1296 let (options, txn) = self.implicit_commit_then_stop();
1297 let was_detached = self.is_detached();
1298 let old_frontiers = self.state_frontiers();
1299 let was_recording = {
1300 let mut state = self.state.lock();
1301 let is_recording = state.is_recording();
1302 state.stop_and_clear_recording();
1303 is_recording
1304 };
1305 let result = (|| {
1306 self._checkout_without_emitting(a, true, false)?;
1307 self.state.lock().start_recording();
1308 self._checkout_without_emitting(b, true, false)?;
1309 let mut state = self.state.lock();
1310 let e = state.take_events();
1311 state.stop_and_clear_recording();
1312 Ok::<_, LoroError>(e)
1313 })();
1314
1315 self._checkout_without_emitting(&old_frontiers, false, false)
1317 .unwrap();
1318 drop(txn);
1319 if !was_detached {
1320 self.set_detached(false);
1321 self.renew_txn_if_auto_commit(options);
1322 }
1323 if was_recording {
1324 self.state.lock().start_recording();
1325 }
1326 result.map(DiffBatch::new)
1327 }
1328
1329 #[inline(always)]
1331 pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> {
1332 self._apply_diff(diff, &mut Default::default(), true)
1333 }
1334
1335 pub(crate) fn _apply_diff(
1347 &self,
1348 diff: DiffBatch,
1349 container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1350 skip_unreachable: bool,
1351 ) -> LoroResult<()> {
1352 if !self.can_edit() {
1353 return Err(LoroError::EditWhenDetached);
1354 }
1355
1356 let mut ans: LoroResult<()> = Ok(());
1357 let mut missing_containers: Vec<ContainerID> = Vec::new();
1358 for (mut id, diff) in diff.into_iter() {
1359 let mut remapped = false;
1360 while let Some(rid) = container_remap.get(&id) {
1361 remapped = true;
1362 id = rid.clone();
1363 }
1364
1365 if matches!(&id, ContainerID::Normal { .. }) && self.arena.id_to_idx(&id).is_none() {
1366 let exists = self.state.lock().does_container_exist(&id);
1368 if !exists {
1369 missing_containers.push(id);
1370 continue;
1371 }
1372 self.state.lock().ensure_container(&id);
1374 }
1375
1376 if skip_unreachable && !remapped && !self.state.lock().get_reachable(&id) {
1377 continue;
1378 }
1379
1380 let Some(h) = self.get_handler(id.clone()) else {
1381 return Err(LoroError::ContainersNotFound {
1382 containers: Box::new(vec![id]),
1383 });
1384 };
1385 if let Err(e) = h.apply_diff(diff, container_remap) {
1386 ans = Err(e);
1387 }
1388 }
1389
1390 if !missing_containers.is_empty() {
1391 return Err(LoroError::ContainersNotFound {
1392 containers: Box::new(missing_containers),
1393 });
1394 }
1395
1396 ans
1397 }
1398
1399 #[inline]
1401 pub fn diagnose_size(&self) {
1402 self.oplog().lock().diagnose_size();
1403 }
1404
1405 #[inline]
1406 pub fn oplog_frontiers(&self) -> Frontiers {
1407 self.oplog().lock().frontiers().clone()
1408 }
1409
1410 #[inline]
1411 pub fn state_frontiers(&self) -> Frontiers {
1412 self.state.lock().frontiers.clone()
1413 }
1414
1415 #[inline]
1419 pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
1420 self.oplog().lock().cmp_with_frontiers(other)
1421 }
1422
1423 #[inline]
1427 pub fn cmp_frontiers(
1428 &self,
1429 a: &Frontiers,
1430 b: &Frontiers,
1431 ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
1432 self.oplog().lock().cmp_frontiers(a, b)
1433 }
1434
1435 pub fn subscribe_root(&self, callback: Subscriber) -> Subscription {
1436 let mut state = self.state.lock();
1437 if !state.is_recording() {
1438 state.start_recording();
1439 }
1440
1441 self.observer.subscribe_root(callback)
1442 }
1443
1444 pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription {
1445 let mut state = self.state.lock();
1446 if !state.is_recording() {
1447 state.start_recording();
1448 }
1449
1450 self.observer.subscribe(container_id, callback)
1451 }
1452
1453 pub fn subscribe_local_update(&self, callback: LocalUpdateCallback) -> Subscription {
1454 let (sub, activate) = self.local_update_subs.inner().insert((), callback);
1455 activate();
1456 sub
1457 }
1458
1459 #[tracing::instrument(skip_all)]
1461 pub fn import_batch(&self, bytes: &[Vec<u8>]) -> LoroResult<ImportStatus> {
1462 if bytes.is_empty() {
1463 return Ok(ImportStatus::default());
1464 }
1465
1466 if bytes.len() == 1 {
1467 return self.import(&bytes[0]);
1468 }
1469
1470 let mut success = VersionRange::default();
1471 let mut meta_arr = bytes
1472 .iter()
1473 .map(|b| Ok((LoroDoc::decode_import_blob_meta(b, false)?, b)))
1474 .collect::<LoroResult<Vec<(ImportBlobMetadata, &Vec<u8>)>>>()?;
1475 meta_arr.sort_by(|a, b| {
1476 a.0.mode
1477 .cmp(&b.0.mode)
1478 .then(b.0.change_num.cmp(&a.0.change_num))
1479 });
1480
1481 let (options, txn) = self.implicit_commit_then_stop();
1482 let was_attached = !self.is_detached();
1513 self.set_detached(true);
1514 {
1515 let mut oplog = self.oplog.lock();
1516 oplog.batch_importing = true;
1517 if was_attached {
1523 oplog.begin_import_rollback();
1524 }
1525 }
1526
1527 let mut guard = BatchImportGuard {
1528 doc: self,
1529 was_attached,
1530 txn: Some(txn),
1531 options,
1532 #[cfg(debug_assertions)]
1533 finished: false,
1534 };
1535
1536 let loop_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1541 let mut err = None;
1542 for (_meta, data) in meta_arr {
1543 #[cfg(test)]
1544 take_panic_at_batch_import_blob_for_test();
1545 match guard.doc._import_with(data, Default::default()) {
1546 Ok(s) => {
1547 for (peer, (start, end)) in s.success.iter() {
1548 match success.0.entry(*peer) {
1549 Entry::Occupied(mut e) => {
1550 e.get_mut().1 = *end.max(&e.get().1);
1551 }
1552 Entry::Vacant(e) => {
1553 e.insert((*start, *end));
1554 }
1555 }
1556 }
1557 }
1558 Err(e) => {
1559 err = Some(e);
1560 }
1561 }
1562 }
1563 err
1564 }));
1565
1566 let err = match loop_result {
1567 Ok(err) => err,
1568 Err(payload) => {
1569 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1573 let _ = guard.finish();
1574 }));
1575 std::panic::resume_unwind(payload);
1576 }
1577 };
1578
1579 let pending = guard.finish()?;
1580 if let Some(err) = err {
1581 return Err(err);
1582 }
1583
1584 Ok(ImportStatus {
1585 success,
1586 pending: if pending.is_empty() {
1587 None
1588 } else {
1589 Some(pending)
1590 },
1591 })
1592 }
1593
1594 #[inline]
1596 pub fn get_value(&self) -> LoroValue {
1597 self.state.lock().get_value()
1598 }
1599
1600 #[inline]
1602 pub fn get_deep_value(&self) -> LoroValue {
1603 self.state.lock().get_deep_value()
1604 }
1605
1606 #[inline]
1608 pub fn get_deep_value_with_id(&self) -> LoroValue {
1609 self.state.lock().get_deep_value_with_id()
1610 }
1611
1612 pub fn checkout_to_latest(&self) {
1613 let (options, _guard) = self.implicit_commit_then_stop();
1614 if !self.is_detached() {
1615 drop(_guard);
1616 self.renew_txn_if_auto_commit(options);
1617 return;
1618 }
1619
1620 self._checkout_to_latest_without_commit(true)
1621 .expect("checkout to oplog frontiers should succeed");
1622 self.emit_events();
1623 drop(_guard);
1624 self.renew_txn_if_auto_commit(options);
1625 }
1626
1627 pub(crate) fn _checkout_to_latest_without_commit(
1629 &self,
1630 to_commit_then_renew: bool,
1631 ) -> LoroResult<()> {
1632 self._checkout_to_latest_without_commit_with_event(
1633 to_commit_then_renew,
1634 "checkout".into(),
1635 EventTriggerKind::Checkout,
1636 )
1637 }
1638
1639 pub(crate) fn _checkout_to_latest_without_commit_as_import(
1640 &self,
1641 to_commit_then_renew: bool,
1642 origin: InternalString,
1643 ) -> LoroResult<()> {
1644 self._checkout_to_latest_without_commit_with_event(
1645 to_commit_then_renew,
1646 origin,
1647 EventTriggerKind::Import,
1648 )
1649 }
1650
1651 fn _checkout_to_latest_without_commit_with_event(
1652 &self,
1653 to_commit_then_renew: bool,
1654 origin: InternalString,
1655 triggered_by: EventTriggerKind,
1656 ) -> LoroResult<()> {
1657 tracing::info_span!("CheckoutToLatest", peer = self.peer_id()).in_scope(|| {
1658 let f = self.oplog_frontiers();
1659 let this = &self;
1660 let frontiers = &f;
1661 this._checkout_without_emitting_with_event(
1662 frontiers,
1663 false,
1664 to_commit_then_renew,
1665 origin,
1666 triggered_by,
1667 )?;
1668 this.emit_events();
1670 if this.config.detached_editing() {
1671 this.renew_peer_id();
1672 }
1673
1674 self.set_detached(false);
1675 Ok(())
1676 })
1677 }
1678
1679 pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()> {
1684 let was_detached = self.is_detached();
1685 let (options, guard) = self.implicit_commit_then_stop();
1686 let result = self._checkout_without_emitting(frontiers, true, true);
1687 if result.is_ok() {
1688 self.emit_events();
1689 }
1690 drop(guard);
1691 if self.config.detached_editing() {
1692 if result.is_ok() {
1693 self.renew_peer_id();
1694 }
1695 self.renew_txn_if_auto_commit(options);
1696 } else if result.is_err() {
1697 if !was_detached {
1698 self.renew_txn_if_auto_commit(options);
1699 }
1700 } else if !self.is_detached() {
1701 self.renew_txn_if_auto_commit(options);
1702 }
1703
1704 result
1705 }
1706
1707 #[instrument(level = "info", skip(self))]
1709 pub(crate) fn _checkout_without_emitting(
1710 &self,
1711 frontiers: &Frontiers,
1712 to_shrink_frontiers: bool,
1713 to_commit_then_renew: bool,
1714 ) -> Result<(), LoroError> {
1715 self._checkout_without_emitting_with_event(
1716 frontiers,
1717 to_shrink_frontiers,
1718 to_commit_then_renew,
1719 "checkout".into(),
1720 EventTriggerKind::Checkout,
1721 )
1722 }
1723
1724 fn _checkout_without_emitting_with_event(
1725 &self,
1726 frontiers: &Frontiers,
1727 to_shrink_frontiers: bool,
1728 _to_commit_then_renew: bool,
1729 origin: InternalString,
1730 triggered_by: EventTriggerKind,
1731 ) -> Result<(), LoroError> {
1732 if !self.txn.is_locked() {
1733 return Err(LoroError::TransactionError(
1734 "checkout requires the transaction mutex to be held"
1735 .to_string()
1736 .into_boxed_str(),
1737 ));
1738 }
1739 let from_frontiers = self.state_frontiers();
1740 loro_common::info!(
1741 "checkout from={:?} to={:?} cur_vv={:?}",
1742 from_frontiers,
1743 frontiers,
1744 self.oplog_vv()
1745 );
1746
1747 if &from_frontiers == frontiers {
1748 self.set_detached(frontiers != &self.oplog_frontiers());
1749 return Ok(());
1750 }
1751
1752 let oplog = self.oplog.lock();
1753 if oplog.dag.is_before_shallow_root(frontiers) {
1754 return Err(LoroError::SwitchToVersionBeforeShallowRoot);
1755 }
1756
1757 let frontiers = if to_shrink_frontiers {
1758 shrink_frontiers(frontiers, &oplog.dag).map_err(LoroError::FrontiersNotFound)?
1759 } else {
1760 frontiers.clone()
1761 };
1762
1763 if from_frontiers == frontiers {
1764 return Ok(());
1765 }
1766
1767 let mut state = self.state.lock();
1768 let mut calc = self.diff_calculator.lock();
1769 for i in frontiers.iter() {
1770 if !oplog.dag.contains(i) {
1771 return Err(LoroError::FrontiersNotFound(i));
1772 }
1773 }
1774
1775 let before = oplog.dag.frontiers_to_vv(&state.frontiers).ok_or_else(|| {
1776 LoroError::NotFoundError(
1777 format!(
1778 "Cannot find the current state version {:?}",
1779 state.frontiers
1780 )
1781 .into_boxed_str(),
1782 )
1783 })?;
1784 let Some(after) = &oplog.dag.frontiers_to_vv(&frontiers) else {
1785 return Err(LoroError::NotFoundError(
1786 format!("Cannot find the specified version {:?}", frontiers).into_boxed_str(),
1787 ));
1788 };
1789
1790 self.set_detached(true);
1791 let (diff, diff_mode) =
1792 calc.calc_diff_internal(&oplog, &before, &state.frontiers, after, &frontiers, None);
1793 state.apply_diff(
1794 InternalDocDiff {
1795 origin,
1796 diff: Cow::Owned(diff),
1797 by: triggered_by,
1798 new_version: Cow::Owned(frontiers.clone()),
1799 },
1800 diff_mode,
1801 )?;
1802
1803 Ok(())
1804 }
1805
1806 #[inline]
1807 pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers {
1808 self.oplog.lock().dag.vv_to_frontiers(vv)
1809 }
1810
1811 #[inline]
1812 pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector> {
1813 self.oplog.lock().dag.frontiers_to_vv(frontiers)
1814 }
1815
1816 pub fn merge(&self, other: &Self) -> LoroResult<ImportStatus> {
1820 let updates = other.export(ExportMode::updates(&self.oplog_vv())).unwrap();
1821 self.import(&updates)
1822 }
1823
1824 pub(crate) fn arena(&self) -> &SharedArena {
1825 &self.arena
1826 }
1827
1828 #[inline]
1829 pub fn len_ops(&self) -> usize {
1830 if self.oplog.can_lock_in_this_thread() {
1831 return self.oplog.lock().visible_op_count_exact();
1832 }
1833
1834 self.visible_op_count.load(Acquire)
1835 }
1836
1837 #[inline]
1838 pub fn len_changes(&self) -> usize {
1839 let oplog = self.oplog.lock();
1840 oplog.len_changes()
1841 }
1842
1843 pub fn config(&self) -> &Configure {
1844 &self.config
1845 }
1846
1847 pub fn check_state_diff_calc_consistency_slow(&self) {
1852 {
1854 static IS_CHECKING: std::sync::atomic::AtomicBool =
1855 std::sync::atomic::AtomicBool::new(false);
1856 if IS_CHECKING.load(std::sync::atomic::Ordering::Acquire) {
1857 return;
1858 }
1859
1860 IS_CHECKING.store(true, std::sync::atomic::Ordering::Release);
1861 let peer_id = self.peer_id();
1862 let s = info_span!("CheckStateDiffCalcConsistencySlow", ?peer_id);
1863 let _g = s.enter();
1864 let options = self.implicit_commit_then_stop().0;
1865 self.oplog.lock().check_dag_correctness();
1866 if self.is_shallow() {
1867 let initial_snapshot = self
1878 .export(ExportMode::state_only(Some(
1879 &self.shallow_since_frontiers(),
1880 )))
1881 .unwrap();
1882
1883 let doc = LoroDoc::new();
1885 doc.import(&initial_snapshot).unwrap();
1886 self.checkout(&self.shallow_since_frontiers()).unwrap();
1887 assert_eq!(self.get_deep_value(), doc.get_deep_value());
1888
1889 let updates = self.export(ExportMode::all_updates()).unwrap();
1891
1892 doc.import(&updates).unwrap();
1894 self.checkout_to_latest();
1895
1896 assert_eq!(doc.get_deep_value(), self.get_deep_value());
1899 let mut calculated_state = doc.app_state().lock();
1900 let mut current_state = self.app_state().lock();
1901 current_state.check_is_the_same(&mut calculated_state);
1902 } else {
1903 let f = self.state_frontiers();
1904 let vv = self.oplog().lock().dag.frontiers_to_vv(&f).unwrap();
1905 let bytes = self.export(ExportMode::updates_till(&vv)).unwrap();
1906 let doc = Self::new();
1907 doc.import(&bytes).unwrap();
1908 let mut calculated_state = doc.app_state().lock();
1909 let mut current_state = self.app_state().lock();
1910 current_state.check_is_the_same(&mut calculated_state);
1911 }
1912
1913 self.renew_txn_if_auto_commit(options);
1914 IS_CHECKING.store(false, std::sync::atomic::Ordering::Release);
1915 }
1916 }
1917
1918 pub fn query_pos(&self, pos: &Cursor) -> Result<PosQueryResult, CannotFindRelativePosition> {
1919 self.query_pos_internal(pos, true)
1920 }
1921
1922 pub(crate) fn query_pos_internal(
1924 &self,
1925 pos: &Cursor,
1926 ret_event_index: bool,
1927 ) -> Result<PosQueryResult, CannotFindRelativePosition> {
1928 if !self.has_container(&pos.container) {
1929 return Err(CannotFindRelativePosition::IdNotFound);
1930 }
1931
1932 let mut state = self.state.lock();
1933 if let Some(ans) = state.get_relative_position(pos, ret_event_index) {
1934 Ok(PosQueryResult {
1935 update: None,
1936 current: AbsolutePosition {
1937 pos: ans,
1938 side: pos.side,
1939 },
1940 })
1941 } else {
1942 drop(state);
1954 let result = self.with_barrier(|| {
1955 let oplog = self.oplog().lock();
1956 if let Some(id) = pos.id {
1958 if oplog.arena.id_to_idx(&pos.container).is_none() {
1960 let mut s = self.state.lock();
1961 if !s.does_container_exist(&pos.container) {
1962 return Err(CannotFindRelativePosition::ContainerDeleted);
1963 }
1964 s.ensure_container(&pos.container);
1965 drop(s);
1966 }
1967 let idx = oplog.arena.id_to_idx(&pos.container).unwrap();
1968 let Some(delete_op_id) = find_last_delete_op(&oplog, id, idx) else {
1970 if oplog.shallow_since_vv().includes_id(id) {
1971 return Err(CannotFindRelativePosition::HistoryCleared);
1972 }
1973
1974 tracing::error!("Cannot find id {}", id);
1975 return Err(CannotFindRelativePosition::IdNotFound);
1976 };
1977 let mut diff_calc = DiffCalculator::new(true);
1979 let before_frontiers: Frontiers = oplog.dag.find_deps_of_id(delete_op_id);
1980 let before = &oplog.dag.frontiers_to_vv(&before_frontiers).unwrap();
1981 diff_calc.calc_diff_internal(
1983 &oplog,
1984 before,
1985 &before_frontiers,
1986 oplog.vv(),
1987 oplog.frontiers(),
1988 Some(&|target| idx == target),
1989 );
1990 let depth = self.arena.get_depth(idx);
1992 let (_, diff_calc) = &mut diff_calc.get_or_create_calc(idx, depth);
1993 match diff_calc {
1994 crate::diff_calc::ContainerDiffCalculator::Richtext(text) => {
1995 let c = text.get_id_latest_pos(id).unwrap();
1996 let new_pos = c.pos;
1997 let handler = self.get_text(&pos.container);
1998 let current_pos = handler.convert_entity_index_to_event_index(new_pos);
1999 Ok(PosQueryResult {
2000 update: handler.get_cursor(current_pos, c.side),
2001 current: AbsolutePosition {
2002 pos: current_pos,
2003 side: c.side,
2004 },
2005 })
2006 }
2007 crate::diff_calc::ContainerDiffCalculator::List(list) => {
2008 let c = list.get_id_latest_pos(id).unwrap();
2009 let new_pos = c.pos;
2010 let handler = self.get_list(&pos.container);
2011 Ok(PosQueryResult {
2012 update: handler.get_cursor(new_pos, c.side),
2013 current: AbsolutePosition {
2014 pos: new_pos,
2015 side: c.side,
2016 },
2017 })
2018 }
2019 crate::diff_calc::ContainerDiffCalculator::MovableList(list) => {
2020 let c = list.get_id_latest_pos(id).unwrap();
2021 let new_pos = c.pos;
2022 let handler = self.get_movable_list(&pos.container);
2023 let new_pos = handler.op_pos_to_user_pos(new_pos);
2024 Ok(PosQueryResult {
2025 update: handler.get_cursor(new_pos, c.side),
2026 current: AbsolutePosition {
2027 pos: new_pos,
2028 side: c.side,
2029 },
2030 })
2031 }
2032 crate::diff_calc::ContainerDiffCalculator::Tree(_) => unreachable!(),
2033 crate::diff_calc::ContainerDiffCalculator::Map(_) => unreachable!(),
2034 #[cfg(feature = "counter")]
2035 crate::diff_calc::ContainerDiffCalculator::Counter(_) => unreachable!(),
2036 crate::diff_calc::ContainerDiffCalculator::Unknown(_) => unreachable!(),
2037 }
2038 } else {
2039 match pos.container.container_type() {
2040 ContainerType::Text => {
2041 let text = self.get_text(&pos.container);
2042 Ok(PosQueryResult {
2043 update: Some(Cursor {
2044 id: None,
2045 container: text.id(),
2046 side: pos.side,
2047 origin_pos: text.len_unicode(),
2048 }),
2049 current: AbsolutePosition {
2050 pos: text.len_event(),
2051 side: pos.side,
2052 },
2053 })
2054 }
2055 ContainerType::List => {
2056 let list = self.get_list(&pos.container);
2057 Ok(PosQueryResult {
2058 update: Some(Cursor {
2059 id: None,
2060 container: list.id(),
2061 side: pos.side,
2062 origin_pos: list.len(),
2063 }),
2064 current: AbsolutePosition {
2065 pos: list.len(),
2066 side: pos.side,
2067 },
2068 })
2069 }
2070 ContainerType::MovableList => {
2071 let list = self.get_movable_list(&pos.container);
2072 Ok(PosQueryResult {
2073 update: Some(Cursor {
2074 id: None,
2075 container: list.id(),
2076 side: pos.side,
2077 origin_pos: list.len(),
2078 }),
2079 current: AbsolutePosition {
2080 pos: list.len(),
2081 side: pos.side,
2082 },
2083 })
2084 }
2085 ContainerType::Map | ContainerType::Tree | ContainerType::Unknown(_) => {
2086 unreachable!()
2087 }
2088 #[cfg(feature = "counter")]
2089 ContainerType::Counter => unreachable!(),
2090 }
2091 }
2092 });
2093 result
2094 }
2095 }
2096
2097 pub fn free_history_cache(&self) {
2102 self.oplog.lock().free_history_cache();
2103 }
2104
2105 pub fn free_diff_calculator(&self) {
2107 *self.diff_calculator.lock() = DiffCalculator::new(true);
2108 }
2109
2110 pub fn has_history_cache(&self) -> bool {
2113 self.oplog.lock().has_history_cache()
2114 }
2115
2116 #[inline]
2120 pub fn compact_change_store(&self) {
2121 self.with_barrier(|| {
2122 self.oplog.lock().compact_change_store();
2123 });
2124 }
2125
2126 #[inline]
2130 pub fn analyze(&self) -> DocAnalysis {
2131 DocAnalysis::analyze(self)
2132 }
2133
2134 pub fn get_path_to_container(&self, id: &ContainerID) -> Option<Vec<(ContainerID, Index)>> {
2136 let mut state = self.state.lock();
2137 if state.arena.id_to_idx(id).is_none() {
2138 if id.is_mergeable() {
2139 state.arena.register_container(id);
2143 } else if !state.does_container_exist(id) {
2144 return None;
2145 } else {
2146 state.ensure_container(id);
2147 }
2148 }
2149 let idx = state.arena.id_to_idx(id).unwrap();
2150 state.get_path(idx)
2151 }
2152
2153 #[instrument(skip(self))]
2154 pub fn export(&self, mode: ExportMode) -> Result<Vec<u8>, LoroEncodeError> {
2155 self.with_barrier(|| {
2156 let ans = match mode {
2157 ExportMode::Snapshot => export_fast_snapshot(self)?,
2158 ExportMode::Updates { from } => export_fast_updates(self, &from),
2159 ExportMode::UpdatesInRange { spans } => {
2160 export_fast_updates_in_range(&self.oplog.lock(), spans.as_ref())
2161 }
2162 ExportMode::ShallowSnapshot(f) => export_shallow_snapshot(self, &f)?,
2163 ExportMode::StateOnly(f) => match f {
2164 Some(f) => export_state_only_snapshot(self, &f)?,
2165 None => export_state_only_snapshot(self, &self.oplog_frontiers())?,
2166 },
2167 ExportMode::SnapshotAt { version } => export_snapshot_at(self, &version)?,
2168 };
2169 Ok(ans)
2170 })
2171 }
2172
2173 pub fn shallow_since_vv(&self) -> ImVersionVector {
2179 self.oplog().lock().shallow_since_vv().clone()
2180 }
2181
2182 pub fn shallow_since_frontiers(&self) -> Frontiers {
2183 self.oplog().lock().shallow_since_frontiers().clone()
2184 }
2185
2186 pub fn is_shallow(&self) -> bool {
2188 !self.oplog().lock().shallow_since_vv().is_empty()
2189 }
2190
2191 pub fn get_pending_txn_len(&self) -> usize {
2196 if let Some(txn) = self.txn.lock().as_ref() {
2197 txn.len()
2198 } else {
2199 0
2200 }
2201 }
2202
2203 #[inline]
2204 pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
2205 self.oplog().lock().dag.find_path(from, to)
2206 }
2207
2208 pub fn subscribe_first_commit_from_peer(
2214 &self,
2215 callback: FirstCommitFromPeerCallback,
2216 ) -> Subscription {
2217 let (s, enable) = self
2218 .first_commit_from_peer_subs
2219 .inner()
2220 .insert((), callback);
2221 enable();
2222 s
2223 }
2224
2225 pub fn subscribe_pre_commit(&self, callback: PreCommitCallback) -> Subscription {
2230 let (s, enable) = self.pre_commit_subs.inner().insert((), callback);
2231 enable();
2232 s
2233 }
2234}
2235
2236fn pending_root_containers_to_materialize(oplog: &OpLog, changes: &[Change]) -> Vec<ContainerID> {
2237 let mut roots = FxHashSet::default();
2238 for change in changes {
2239 if change.ctr_end() <= oplog.vv().get(&change.id.peer).copied().unwrap_or(0) {
2240 continue;
2241 }
2242
2243 if oplog.dag.is_before_shallow_root(&change.deps)
2244 || oplog
2245 .dag
2246 .get_change_lamport_from_deps(&change.deps)
2247 .is_some()
2248 {
2249 continue;
2250 }
2251
2252 for op in change.ops.iter() {
2253 let id = oplog
2254 .arena
2255 .get_container_id(op.container)
2256 .expect("decoded op container should be registered");
2257 if id.is_root() && !id.is_mergeable() {
2266 roots.insert(id);
2267 }
2268 }
2269 }
2270
2271 roots.into_iter().collect()
2272}
2273
2274fn isolated_scalar_root_batch(
2278 oplog: &OpLog,
2279 changes: &[Change],
2280) -> Option<(VersionVector, FxHashSet<InternalString>)> {
2281 if changes.is_empty() || !oplog.shallow_since_vv().is_empty() {
2282 return None;
2283 }
2284
2285 let mut component_vv = VersionVector::new();
2288 let mut root_names = FxHashSet::default();
2289 for change in changes {
2290 let peer = change.id.peer;
2291 if oplog.vv().get(&peer).copied().unwrap_or(0) != 0 {
2292 return None;
2293 }
2294 let end = component_vv.entry(peer).or_insert(0);
2295 if change.id.counter != *end {
2296 return None;
2297 }
2298 *end = change.ctr_end();
2299
2300 for op in change.ops.iter() {
2301 let cid = oplog.arena.idx_to_id(op.container)?;
2302 if cid.is_mergeable() {
2303 return None;
2304 }
2305 let ContainerID::Root {
2306 name,
2307 container_type: ContainerType::Map,
2308 } = cid
2309 else {
2310 return None;
2311 };
2312 let InnerContent::Map(map) = &op.content else {
2313 return None;
2314 };
2315 if let Some(value) = &map.value {
2316 if value.is_container()
2317 || loro_common::parse_mergeable_marker(
2318 &ContainerID::new_root(&name, ContainerType::Map),
2319 &map.key,
2320 value,
2321 )
2322 .is_some()
2323 {
2324 return None;
2325 }
2326 }
2327 root_names.insert(name);
2328 }
2329 }
2330
2331 for change in changes {
2333 for dep in change.deps.iter() {
2334 if dep.counter >= component_vv.get(&dep.peer).copied().unwrap_or(0) {
2335 return None;
2336 }
2337 }
2338 }
2339
2340 if oplog
2341 .change_store()
2342 .old_history_may_touch_root_names(&root_names)
2343 {
2344 return None;
2345 }
2346
2347 Some((component_vv, root_names))
2348}
2349
2350#[derive(Debug, thiserror::Error)]
2351pub enum ChangeTravelError {
2352 #[error("Target id not found {0:?}")]
2353 TargetIdNotFound(ID),
2354 #[error("The shallow history of the doc doesn't include the target version")]
2355 TargetVersionNotIncluded,
2356}
2357
2358impl LoroDoc {
2359 pub fn travel_change_ancestors(
2360 &self,
2361 ids: &[ID],
2362 f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>,
2363 ) -> Result<(), ChangeTravelError> {
2364 let (options, guard) = self.implicit_commit_then_stop();
2365 drop(guard);
2366 struct PendingNode(ChangeMeta);
2367 impl PartialEq for PendingNode {
2368 fn eq(&self, other: &Self) -> bool {
2369 self.0.lamport_last() == other.0.lamport_last() && self.0.id.peer == other.0.id.peer
2370 }
2371 }
2372
2373 impl Eq for PendingNode {}
2374 impl PartialOrd for PendingNode {
2375 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2376 Some(self.cmp(other))
2377 }
2378 }
2379
2380 impl Ord for PendingNode {
2381 fn cmp(&self, other: &Self) -> Ordering {
2382 self.0
2383 .lamport_last()
2384 .cmp(&other.0.lamport_last())
2385 .then_with(|| self.0.id.peer.cmp(&other.0.id.peer))
2386 }
2387 }
2388
2389 for id in ids {
2390 let op_log = &self.oplog().lock();
2391 if !op_log.vv().includes_id(*id) {
2392 return Err(ChangeTravelError::TargetIdNotFound(*id));
2393 }
2394 if op_log.dag.shallow_since_vv().includes_id(*id) {
2395 return Err(ChangeTravelError::TargetVersionNotIncluded);
2396 }
2397 }
2398
2399 let mut visited = FxHashSet::default();
2400 let mut pending: BinaryHeap<PendingNode> = BinaryHeap::new();
2401 for id in ids {
2402 pending.push(PendingNode(ChangeMeta::from_change(
2403 &self.oplog().lock().get_change_at(*id).unwrap(),
2404 )));
2405 }
2406 while let Some(PendingNode(node)) = pending.pop() {
2407 let deps = node.deps.clone();
2408 if f(node).is_break() {
2409 break;
2410 }
2411
2412 for dep in deps.iter() {
2413 let Some(dep_node) = self.oplog().lock().get_change_at(dep) else {
2414 continue;
2415 };
2416 if visited.contains(&dep_node.id) {
2417 continue;
2418 }
2419
2420 visited.insert(dep_node.id);
2421 pending.push(PendingNode(ChangeMeta::from_change(&dep_node)));
2422 }
2423 }
2424
2425 let ans = Ok(());
2426 self.renew_txn_if_auto_commit(options);
2427 ans
2428 }
2429
2430 pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
2431 self.with_barrier(|| {
2432 let mut set = FxHashSet::default();
2433 let len = i64::try_from(len).unwrap_or(i64::MAX);
2434 let start = i64::from(id.counter);
2435 let end = start.saturating_add(len);
2436 if end <= 0 {
2437 return set;
2438 }
2439
2440 let start = start.max(0).min(i64::from(i32::MAX));
2441 let end = end.max(0).min(i64::from(i32::MAX));
2442 if start >= end {
2443 return set;
2444 }
2445
2446 {
2447 let oplog = self.oplog().lock();
2448 let span = IdSpan::new(id.peer, start as i32, end as i32);
2449 for op in oplog.iter_ops(span) {
2450 let id = oplog.arena.get_container_id(op.container()).unwrap();
2451 set.insert(id);
2452 }
2453 }
2454 set
2455 })
2456 }
2457
2458 pub fn delete_root_container(&self, cid: ContainerID) {
2459 if !cid.is_root() {
2460 return;
2461 }
2462
2463 if !self.has_container(&cid) {
2465 return;
2466 }
2467
2468 let Some(h) = self.get_handler(cid.clone()) else {
2469 return;
2470 };
2471
2472 self.config
2473 .deleted_root_containers
2474 .lock()
2475 .insert(cid.clone());
2476 if let Err(e) = h.clear() {
2477 self.config.deleted_root_containers.lock().remove(&cid);
2478 eprintln!("Failed to clear handler: {:?}", e);
2479 }
2480 }
2481
2482 pub fn set_hide_empty_root_containers(&self, hide: bool) {
2483 self.config
2484 .hide_empty_root_containers
2485 .store(hide, std::sync::atomic::Ordering::Relaxed);
2486 }
2487}
2488
2489fn find_last_delete_op(oplog: &OpLog, id: ID, idx: ContainerIdx) -> Option<ID> {
2490 let start_vv = oplog
2501 .dag
2502 .frontiers_to_vv(&id.into())
2503 .unwrap_or_else(|| oplog.shallow_since_vv().to_vv());
2504
2505 let mut best: Option<((loro_common::Lamport, loro_common::PeerID), ID)> = None;
2509
2510 for change in oplog.iter_changes_peer_by_peer(&start_vv, oplog.vv()) {
2511 let peer = change.peer();
2512 for op in change.ops.iter() {
2513 if op.container != idx {
2514 continue;
2515 }
2516 if let InnerContent::List(InnerListOp::Delete(d)) = &op.content {
2517 if d.id_start.to_span(d.atom_len()).contains(id) {
2518 debug_assert!(op.counter >= change.id().counter);
2519 let op_lamport =
2520 change.lamport + (op.counter - change.id().counter) as loro_common::Lamport;
2521 let key = (op_lamport, peer);
2522 if best.is_none_or(|(bk, _)| key > bk) {
2523 best = Some((key, ID::new(peer, op.counter)));
2524 }
2525 }
2526 }
2527 }
2528 }
2529
2530 best.map(|(_, op_id)| op_id)
2531}
2532
2533struct BatchImportGuard<'a> {
2545 doc: &'a LoroDoc,
2546 was_attached: bool,
2549 txn: Option<LoroMutexGuard<'a, Option<Transaction>>>,
2552 options: Option<CommitOptions>,
2553 #[cfg(debug_assertions)]
2554 finished: bool,
2555}
2556
2557impl BatchImportGuard<'_> {
2558 fn finish(&mut self) -> LoroResult<VersionRange> {
2566 #[cfg(debug_assertions)]
2567 {
2568 self.finished = true;
2569 }
2570 let doc = self.doc;
2571 let txn = self.txn.take().expect("finish called twice");
2572 let options = self.options.take();
2573
2574 let pending = {
2575 let mut oplog = doc.oplog.lock();
2576 oplog.batch_importing = false;
2577 oplog.pending_changes.version_range()
2578 };
2579
2580 let mut checkout = Ok(());
2585 if self.was_attached {
2586 checkout = doc._checkout_to_latest_without_commit(true);
2587 if let Err(e) = &checkout {
2588 tracing::warn!("import_batch cannot reattach, rolling the batch back: {e}");
2592 doc.oplog.lock().rollback_import();
2593 *doc.diff_calculator.lock() = DiffCalculator::new(true);
2596 doc.set_detached(false);
2597 } else {
2598 doc.oplog.lock().commit_import_rollback();
2599 }
2600 }
2601
2602 doc._renew_txn_if_auto_commit_with_guard(options, txn);
2603 checkout.map(|()| pending)
2604 }
2605}
2606
2607#[cfg(debug_assertions)]
2610impl Drop for BatchImportGuard<'_> {
2611 fn drop(&mut self) {
2612 if !std::thread::panicking() {
2613 assert!(
2614 self.finished,
2615 "BatchImportGuard dropped without calling finish()"
2616 );
2617 }
2618 }
2619}
2620
2621#[derive(Debug)]
2622pub struct CommitWhenDrop<'a> {
2623 doc: &'a LoroDoc,
2624 default_options: CommitOptions,
2625}
2626
2627impl Drop for CommitWhenDrop<'_> {
2628 fn drop(&mut self) {
2629 {
2630 let mut guard = self.doc.txn.lock();
2631 if let Some(txn) = guard.as_mut() {
2632 txn.set_default_options(std::mem::take(&mut self.default_options));
2633 };
2634 }
2635
2636 self.doc.commit_then_renew();
2637 }
2638}
2639
2640#[derive(Debug, Clone)]
2642pub struct CommitOptions {
2643 pub origin: Option<InternalString>,
2646
2647 pub immediate_renew: bool,
2650
2651 pub timestamp: Option<Timestamp>,
2654
2655 pub commit_msg: Option<Arc<str>>,
2657}
2658
2659impl CommitOptions {
2660 pub fn new() -> Self {
2662 Self {
2663 origin: None,
2664 immediate_renew: true,
2665 timestamp: None,
2666 commit_msg: None,
2667 }
2668 }
2669
2670 pub fn origin(mut self, origin: &str) -> Self {
2672 self.origin = Some(origin.into());
2673 self
2674 }
2675
2676 pub fn immediate_renew(mut self, immediate_renew: bool) -> Self {
2678 self.immediate_renew = immediate_renew;
2679 self
2680 }
2681
2682 pub fn timestamp(mut self, timestamp: Timestamp) -> Self {
2686 self.timestamp = Some(timestamp);
2687 self
2688 }
2689
2690 pub fn commit_msg(mut self, commit_msg: &str) -> Self {
2692 self.commit_msg = Some(commit_msg.into());
2693 self
2694 }
2695
2696 pub fn set_origin(&mut self, origin: Option<&str>) {
2698 self.origin = origin.map(|x| x.into())
2699 }
2700
2701 pub fn set_timestamp(&mut self, timestamp: Option<Timestamp>) {
2703 self.timestamp = timestamp;
2704 }
2705}
2706
2707impl Default for CommitOptions {
2708 fn default() -> Self {
2709 Self::new()
2710 }
2711}
2712
2713#[cfg(test)]
2714mod test {
2715 use std::{
2716 panic::AssertUnwindSafe,
2717 sync::{
2718 atomic::{AtomicUsize, Ordering},
2719 Arc,
2720 },
2721 };
2722
2723 use crate::{
2724 cursor::PosType,
2725 encoding::json_schema::json::{JsonOpContent, JsonSchema, ListOp},
2726 encoding::{fast_snapshot::EMPTY_MARK, EncodeMode},
2727 loro::ExportMode,
2728 version::{Frontiers, VersionVector},
2729 LoroDoc, ToJson, TreeParentId,
2730 };
2731 use bytes::{BufMut, Bytes};
2732 use loro_common::{ContainerID, ContainerType, ID};
2733 use loro_kv_store::{mem_store::MemKvConfig, MemKvStore};
2734
2735 const XXH_SEED: u32 = u32::from_le_bytes(*b"LORO");
2736
2737 fn encode_import_blob(mode: EncodeMode, body: &[u8]) -> Vec<u8> {
2738 let mut ans = Vec::new();
2739 ans.extend_from_slice(b"loro");
2740 ans.extend_from_slice(&[0; 16]);
2741 ans.extend_from_slice(&mode.to_bytes());
2742 ans.extend_from_slice(body);
2743 let checksum = xxhash_rust::xxh32::xxh32(&ans[20..], XXH_SEED);
2744 ans[16..20].copy_from_slice(&checksum.to_le_bytes());
2745 ans
2746 }
2747
2748 fn encode_fast_snapshot_import(oplog_bytes: &[u8]) -> Vec<u8> {
2749 let mut body = Vec::new();
2750 body.put_u32_le(oplog_bytes.len() as u32);
2751 body.extend_from_slice(oplog_bytes);
2752 body.put_u32_le(EMPTY_MARK.len() as u32);
2753 body.extend_from_slice(EMPTY_MARK);
2754 body.put_u32_le(0);
2755 encode_import_blob(EncodeMode::FastSnapshot, &body)
2756 }
2757
2758 fn sstable_with_huge_meta_block_count() -> Vec<u8> {
2759 let mut bytes = Vec::new();
2760 bytes.extend_from_slice(b"LORO");
2761 bytes.push(0);
2762 bytes.put_u32_le(10_000_000);
2763 bytes.put_u32_le(xxhash_rust::xxh32::xxh32(&[], XXH_SEED));
2764 bytes.put_u32_le(5);
2765 bytes
2766 }
2767
2768 fn snapshot_oplog_with_malformed_block() -> Vec<u8> {
2769 let peer = 1;
2770 let id = ID::new(peer, 0);
2771 let vv = VersionVector::from_iter([(peer, 1)]);
2772 let frontiers = Frontiers::from_id(id);
2773 let mut store = MemKvStore::new(MemKvConfig::default());
2774 store.set(b"vv", vv.encode().into());
2775 store.set(b"fr", frontiers.encode().into());
2776 store.set(&id.to_bytes(), Bytes::from_static(&[0]));
2777 store.export_all().to_vec()
2778 }
2779
2780 fn make_json_import_stress_doc(peer: u64) -> LoroDoc {
2781 let doc = LoroDoc::new_auto_commit();
2782 doc.set_peer_id(peer).unwrap();
2783
2784 let text = doc.get_text("text");
2785 let mut text_pos = 0;
2786 for i in 0..32 {
2787 let chunk = format!("segment-{i}-abcdefghijklmnopqrstuvwxyz;");
2788 text.insert_unicode(text_pos, &chunk).unwrap();
2789 text_pos += chunk.chars().count();
2790 }
2791
2792 let list = doc.get_list("list");
2793 for i in 0..32 {
2794 list.insert(i, format!("item-{i}")).unwrap();
2795 }
2796
2797 let map = doc.get_map("map");
2798 for i in 0..32 {
2799 let key = format!("key-{i}");
2800 map.insert(&key, format!("value-{i}")).unwrap();
2801 }
2802
2803 let tree = doc.get_tree("tree");
2804 let mut parent = TreeParentId::Root;
2805 for i in 0..16 {
2806 let node = tree.create(parent).unwrap();
2807 let meta = tree.get_meta(node).unwrap();
2808 meta.insert("name", format!("node-{i}")).unwrap();
2809 meta.insert("payload", format!("payload-{i}-{}", "x".repeat(16)))
2810 .unwrap();
2811 parent = TreeParentId::Node(node);
2812 }
2813
2814 doc
2815 }
2816
2817 fn make_json_list_update_with_four_ops(peer: u64) -> (LoroDoc, JsonSchema) {
2818 let doc = LoroDoc::new();
2819 doc.set_peer_id(peer).unwrap();
2820 let map = doc.get_map("map");
2821 let list = doc.get_list("list");
2822 let text = doc.get_text("text");
2823
2824 let mut txn = doc.txn().unwrap();
2825 map.insert_with_txn(&mut txn, "prefix", "map-value".into())
2826 .unwrap();
2827 list.insert_with_txn(&mut txn, 0, "seed".into()).unwrap();
2828 text.insert_with_txn(&mut txn, 0, "text-value", PosType::Unicode)
2829 .unwrap();
2830 list.insert_with_txn(&mut txn, 1, "tail".into()).unwrap();
2831 txn.commit().unwrap();
2832
2833 let json = doc.export_json_updates(&Default::default(), &doc.oplog_vv(), false);
2834 assert_eq!(json.changes.len(), 1);
2835 assert_eq!(json.changes[0].ops.len(), 4);
2836 (doc, json)
2837 }
2838
2839 fn move_last_list_insert_far_out_of_bounds(json: &mut JsonSchema) {
2840 let last_change = json.changes.last_mut().unwrap();
2841 let last_op = last_change.ops.last_mut().unwrap();
2842 match &mut last_op.content {
2843 JsonOpContent::List(ListOp::Insert { pos, .. }) => {
2844 *pos = 1_000;
2845 }
2846 other => panic!("expected list insert op, got {other:?}"),
2847 }
2848 }
2849
2850 #[test]
2851 fn test_sync() {
2852 fn is_send_sync<T: Send + Sync>(_v: T) {}
2853 let loro = super::LoroDoc::new();
2854 is_send_sync(loro)
2855 }
2856
2857 #[test]
2858 fn import_rejects_huge_sstable_meta_block_count_without_panic() {
2859 let bytes = encode_fast_snapshot_import(&sstable_with_huge_meta_block_count());
2860
2861 let result = std::panic::catch_unwind(AssertUnwindSafe(|| LoroDoc::new().import(&bytes)));
2862 assert!(result.is_ok(), "malformed import should not panic");
2863 assert!(result.unwrap().is_err());
2864 }
2865
2866 #[test]
2867 fn import_rejects_malformed_change_block_without_panic() {
2868 let bytes = encode_fast_snapshot_import(&snapshot_oplog_with_malformed_block());
2869
2870 let result = std::panic::catch_unwind(AssertUnwindSafe(|| LoroDoc::new().import(&bytes)));
2871 assert!(result.is_ok(), "malformed import should not panic");
2872 assert!(result.unwrap().is_err());
2873 }
2874
2875 #[test]
2876 fn failed_import_rolls_back_oplog_and_arena() {
2877 let src = LoroDoc::new();
2878 src.set_peer_id(1).unwrap();
2879 let text = src.get_text("text");
2880 let mut txn = src.txn().unwrap();
2881 text.insert_with_txn(&mut txn, 0, "hello", PosType::Unicode)
2882 .unwrap();
2883 txn.commit().unwrap();
2884 let update = src.export(ExportMode::all_updates()).unwrap();
2885
2886 let dst = LoroDoc::new();
2887 let vv_before_import = dst.oplog_vv();
2888 let state_before_import = dst.get_deep_value();
2889 let err = dst
2890 .import_with(&update, "__loro_fail_import_state_apply".into())
2891 .unwrap_err();
2892 assert!(err.to_string().contains("state apply failpoint"));
2893 assert_eq!(dst.oplog_vv(), vv_before_import);
2894 assert_eq!(dst.get_deep_value(), state_before_import);
2895 assert!(dst.oplog().lock().is_empty());
2896
2897 dst.import(&update).unwrap();
2898 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2899 }
2900
2901 #[test]
2902 fn failed_incremental_import_restores_previous_change_store_block() {
2903 let src = LoroDoc::new();
2904 src.set_peer_id(1).unwrap();
2905 let text = src.get_text("text");
2906 let mut txn = src.txn().unwrap();
2907 text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode)
2908 .unwrap();
2909 txn.commit().unwrap();
2910 let first_update = src.export(ExportMode::all_updates()).unwrap();
2911 let first_vv = src.oplog_vv();
2912
2913 let mut txn = src.txn().unwrap();
2914 text.insert_with_txn(&mut txn, 1, "b", PosType::Unicode)
2915 .unwrap();
2916 txn.commit().unwrap();
2917 let second_update = src.export(ExportMode::updates(&first_vv)).unwrap();
2918
2919 let dst = LoroDoc::new();
2920 dst.import(&first_update).unwrap();
2921 let vv_before_import = dst.oplog_vv();
2922 let state_before_import = dst.get_deep_value();
2923 dst.import_with(&second_update, "__loro_fail_import_state_apply".into())
2924 .unwrap_err();
2925 assert_eq!(dst.oplog_vv(), vv_before_import);
2926 assert_eq!(dst.get_deep_value(), state_before_import);
2927
2928 dst.import(&second_update).unwrap();
2929 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2930 }
2931
2932 #[test]
2933 fn failed_import_json_updates_rolls_back_complex_empty_doc() {
2934 let src = make_json_import_stress_doc(11);
2935 let json = src.export_json_updates(&Default::default(), &src.oplog_vv(), false);
2936
2937 let dst = LoroDoc::new();
2938 let vv_before_import = dst.oplog_vv();
2939 let frontiers_before_import = dst.oplog_frontiers();
2940 let state_before_import = dst.get_deep_value();
2941 for _ in 0..3 {
2942 crate::state::fail_next_import_state_apply_for_test();
2943 let err = dst.import_json_updates(json.clone()).unwrap_err();
2944 assert!(err.to_string().contains("state apply failpoint"));
2945 assert_eq!(dst.oplog_vv(), vv_before_import);
2946 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
2947 assert_eq!(dst.get_deep_value(), state_before_import);
2948 assert!(dst.oplog().lock().is_empty());
2949 }
2950
2951 dst.import_json_updates(json).unwrap();
2952 assert_eq!(dst.oplog_vv(), src.oplog_vv());
2953 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
2954 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2955 }
2956
2957 #[test]
2958 fn failed_incremental_import_json_updates_restores_previous_change_store_block() {
2959 let src = LoroDoc::new_auto_commit();
2960 src.set_peer_id(12).unwrap();
2961 let text = src.get_text("text");
2962 text.insert_unicode(0, "a").unwrap();
2963 let list = src.get_list("list");
2964 list.push("seed").unwrap();
2965 let map = src.get_map("map");
2966 map.insert("seed", "value").unwrap();
2967 let tree = src.get_tree("tree");
2968 let root = tree.create(TreeParentId::Root).unwrap();
2969 tree.get_meta(root).unwrap().insert("name", "root").unwrap();
2970
2971 let first_vv = src.oplog_vv();
2972 let first_json = src.export_json_updates(&Default::default(), &first_vv, false);
2973
2974 let mut text_pos = text.len_unicode();
2975 for i in 0..64 {
2976 let chunk = format!("chunk-{i};");
2977 text.insert_unicode(text_pos, &chunk).unwrap();
2978 text_pos += chunk.chars().count();
2979 }
2980 for i in 0..32 {
2981 list.push(format!("after-{i}")).unwrap();
2982 let key = format!("after-{i}");
2983 map.insert(&key, format!("value-{i}")).unwrap();
2984 }
2985 let child = tree.create(TreeParentId::Node(root)).unwrap();
2986 tree.get_meta(child)
2987 .unwrap()
2988 .insert("name", "child")
2989 .unwrap();
2990
2991 let second_json = src.export_json_updates(&first_vv, &src.oplog_vv(), false);
2992
2993 let dst = LoroDoc::new();
2994 dst.import_json_updates(first_json).unwrap();
2995 let vv_before_import = dst.oplog_vv();
2996 let frontiers_before_import = dst.oplog_frontiers();
2997 let state_before_import = dst.get_deep_value();
2998
2999 for _ in 0..2 {
3000 crate::state::fail_next_import_state_apply_for_test();
3001 let err = dst.import_json_updates(second_json.clone()).unwrap_err();
3002 assert!(err.to_string().contains("state apply failpoint"));
3003 assert_eq!(dst.oplog_vv(), vv_before_import);
3004 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
3005 assert_eq!(dst.get_deep_value(), state_before_import);
3006 }
3007
3008 dst.import_json_updates(second_json).unwrap();
3009 assert_eq!(dst.oplog_vv(), src.oplog_vv());
3010 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
3011 assert_eq!(dst.get_deep_value(), src.get_deep_value());
3012 }
3013
3014 #[test]
3015 fn malformed_later_import_json_update_rolls_back_after_valid_prefix_enters_oplog() {
3016 let peer = 13;
3017 let (src, good_json) = make_json_list_update_with_four_ops(peer);
3018 let mut bad_json = good_json.clone();
3019 move_last_list_insert_far_out_of_bounds(&mut bad_json);
3020
3021 let good_dst = LoroDoc::new();
3022 good_dst.import_json_updates(good_json.clone()).unwrap();
3023 assert_eq!(good_dst.get_deep_value(), src.get_deep_value());
3024
3025 let last_op_counter = good_json.changes[0].ops.last().unwrap().counter;
3026 let prefix_vv = VersionVector::from_iter([(peer, last_op_counter)]);
3027 let prefix_json = src.export_json_updates(&Default::default(), &prefix_vv, false);
3028 assert_eq!(
3029 prefix_json.changes[0].ops.len(),
3030 good_json.changes[0].ops.len() - 1
3031 );
3032 let good_suffix_json = src.export_json_updates(&prefix_vv, &src.oplog_vv(), false);
3033 assert_eq!(good_suffix_json.changes[0].ops.len(), 1);
3034 let mut bad_suffix_json = good_suffix_json.clone();
3035 move_last_list_insert_far_out_of_bounds(&mut bad_suffix_json);
3036
3037 let prefix_dst = LoroDoc::new();
3038 prefix_dst.import_json_updates(prefix_json.clone()).unwrap();
3039 let vv_before_bad_suffix = prefix_dst.oplog_vv();
3040 let frontiers_before_bad_suffix = prefix_dst.oplog_frontiers();
3041 let state_before_bad_suffix = prefix_dst.get_deep_value();
3042
3043 let bad_suffix_json = serde_json::to_string(&bad_suffix_json).unwrap();
3044 let err = prefix_dst
3045 .import_json_updates(&bad_suffix_json)
3046 .unwrap_err();
3047 assert!(
3048 err.to_string().contains("list diff"),
3049 "expected state list bounds validation, got {err:?}"
3050 );
3051 assert_eq!(prefix_dst.oplog_vv(), vv_before_bad_suffix);
3052 assert_eq!(prefix_dst.oplog_frontiers(), frontiers_before_bad_suffix);
3053 assert_eq!(prefix_dst.get_deep_value(), state_before_bad_suffix);
3054
3055 prefix_dst.import_json_updates(good_suffix_json).unwrap();
3056 assert_eq!(prefix_dst.get_deep_value(), src.get_deep_value());
3057 assert_eq!(prefix_dst.oplog_vv(), src.oplog_vv());
3058
3059 let dst = LoroDoc::new();
3060 let vv_before_import = dst.oplog_vv();
3061 let frontiers_before_import = dst.oplog_frontiers();
3062 let state_before_import = dst.get_deep_value();
3063 let bad_json = serde_json::to_string(&bad_json).unwrap();
3064 let err = dst.import_json_updates(&bad_json).unwrap_err();
3065 assert!(
3066 err.to_string().contains("list diff"),
3067 "expected state list bounds validation, got {err:?}"
3068 );
3069 assert_eq!(dst.oplog_vv(), vv_before_import);
3070 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
3071 assert_eq!(dst.get_deep_value(), state_before_import);
3072 assert!(dst.oplog().lock().is_empty());
3073 }
3074
3075 #[test]
3076 fn failed_import_restores_pending_changes_that_were_applied_during_import() {
3077 let src = LoroDoc::new();
3078 src.set_peer_id(14).unwrap();
3079 let text = src.get_text("text");
3080
3081 let mut txn = src.txn().unwrap();
3082 text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode)
3083 .unwrap();
3084 txn.commit().unwrap();
3085 let first_update = src.export(ExportMode::all_updates()).unwrap();
3086 let first_vv = src.oplog_vv();
3087
3088 let mut txn = src.txn().unwrap();
3089 text.insert_with_txn(&mut txn, 1, "b", PosType::Unicode)
3090 .unwrap();
3091 txn.commit().unwrap();
3092 let second_update = src.export(ExportMode::updates(&first_vv)).unwrap();
3093
3094 let dst = LoroDoc::new();
3095 let status = dst.import(&second_update).unwrap();
3096 assert!(status.success.is_empty());
3097 assert!(status.pending.is_some());
3098 let vv_before_dependency = dst.oplog_vv();
3099 let frontiers_before_dependency = dst.oplog_frontiers();
3100 let state_before_dependency = dst.get_deep_value();
3101
3102 crate::state::fail_next_import_state_apply_for_test();
3103 let err = dst.import(&first_update).unwrap_err();
3104 assert!(err.to_string().contains("state apply failpoint"));
3105 assert_eq!(dst.oplog_vv(), vv_before_dependency);
3106 assert_eq!(dst.oplog_frontiers(), frontiers_before_dependency);
3107 assert_eq!(dst.get_deep_value(), state_before_dependency);
3108
3109 dst.import(&first_update).unwrap();
3110 assert_eq!(dst.oplog_vv(), src.oplog_vv());
3111 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
3112 assert_eq!(dst.get_deep_value(), src.get_deep_value());
3113 }
3114
3115 #[test]
3116 fn failed_import_json_updates_does_not_emit_or_leave_events() {
3117 let (src, good_json) = make_json_list_update_with_four_ops(15);
3118 let mut bad_json = good_json.clone();
3119 move_last_list_insert_far_out_of_bounds(&mut bad_json);
3120
3121 let dst = LoroDoc::new();
3122 let event_count = Arc::new(AtomicUsize::new(0));
3123 let event_count_cloned = event_count.clone();
3124 let _sub = dst.subscribe_root(Arc::new(move |_| {
3125 event_count_cloned.fetch_add(1, Ordering::SeqCst);
3126 }));
3127
3128 let bad_json = serde_json::to_string(&bad_json).unwrap();
3129 let err = dst.import_json_updates(&bad_json).unwrap_err();
3130 assert!(
3131 err.to_string().contains("list diff"),
3132 "expected state list bounds validation, got {err:?}"
3133 );
3134 assert_eq!(event_count.load(Ordering::SeqCst), 0);
3135 assert!(dst.drop_pending_events().is_empty());
3136 assert!(dst.oplog().lock().is_empty());
3137
3138 dst.import_json_updates(good_json).unwrap();
3139 assert_eq!(event_count.load(Ordering::SeqCst), 1);
3140 assert_eq!(dst.get_deep_value(), src.get_deep_value());
3141 }
3142
3143 #[test]
3144 fn test_checkout() {
3145 let loro = LoroDoc::new();
3146 loro.set_peer_id(1).unwrap();
3147 let text = loro.get_text("text");
3148 let map = loro.get_map("map");
3149 let list = loro.get_list("list");
3150 let mut txn = loro.txn().unwrap();
3151 for i in 0..10 {
3152 map.insert_with_txn(&mut txn, "key", i.into()).unwrap();
3153 text.insert_with_txn(&mut txn, 0, &i.to_string(), PosType::Unicode)
3154 .unwrap();
3155 list.insert_with_txn(&mut txn, 0, i.into()).unwrap();
3156 }
3157 txn.commit().unwrap();
3158 let b = LoroDoc::new();
3159 b.import(&loro.export(ExportMode::Snapshot).unwrap())
3160 .unwrap();
3161 loro.checkout(&Frontiers::default()).unwrap();
3162 {
3163 let json = &loro.get_deep_value();
3164 assert_eq!(
3165 json.to_json_value(),
3166 serde_json::json!({"text":"","list":[],"map":{}})
3167 );
3168 }
3169
3170 b.checkout(&ID::new(1, 2).into()).unwrap();
3171 {
3172 let json = &b.get_deep_value();
3173 assert_eq!(
3174 json.to_json_value(),
3175 serde_json::json!({"text":"0","list":[0],"map":{"key":0}})
3176 );
3177 }
3178
3179 loro.checkout(&ID::new(1, 3).into()).unwrap();
3180 {
3181 let json = &loro.get_deep_value();
3182 assert_eq!(
3183 json.to_json_value(),
3184 serde_json::json!({"text":"0","list":[0],"map":{"key":1}})
3185 );
3186 }
3187
3188 b.checkout(&ID::new(1, 29).into()).unwrap();
3189 {
3190 let json = &b.get_deep_value();
3191 assert_eq!(
3192 json.to_json_value(),
3193 serde_json::json!({"text":"9876543210","list":[9,8,7,6,5,4,3,2,1,0],"map":{"key":9}})
3194 );
3195 }
3196 }
3197
3198 #[test]
3199 fn import_batch_err_181() {
3200 let a = LoroDoc::new_auto_commit();
3201 let update_a = a.export(ExportMode::Snapshot);
3202 let b = LoroDoc::new_auto_commit();
3203 b.import_batch(&[update_a.unwrap()]).unwrap();
3204 b.get_text("text")
3205 .insert(0, "hello", PosType::Unicode)
3206 .unwrap();
3207 b.commit_then_renew();
3208 let oplog = b.oplog().lock();
3209 drop(oplog);
3210 b.export(ExportMode::all_updates()).unwrap();
3211 }
3212
3213 #[test]
3214 fn poisoned_mutex_keeps_follow_up_operations_failed() {
3215 let doc = LoroDoc::new();
3216 let oplog = doc.oplog.clone();
3217 let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
3218 let _guard = oplog.lock();
3219 panic!("poison oplog");
3220 }));
3221
3222 let err = std::panic::catch_unwind(AssertUnwindSafe(|| doc.oplog_vv()))
3223 .expect_err("poisoned lock should continue to fail fast");
3224 let msg = if let Some(msg) = err.downcast_ref::<&str>() {
3225 (*msg).to_string()
3226 } else if let Some(msg) = err.downcast_ref::<String>() {
3227 msg.clone()
3228 } else {
3229 String::new()
3230 };
3231 assert!(msg.contains("poisoned LoroMutex"), "{msg}");
3232 }
3233
3234 #[test]
3235 fn repeated_independent_scalar_root_imports_scan_history_once() {
3236 let base = LoroDoc::new_auto_commit();
3237 base.set_peer_id(1).unwrap();
3238 for i in 0..64 {
3239 base.get_map("existing")
3240 .insert(&format!("key-{i}"), i)
3241 .unwrap();
3242 base.commit_then_renew();
3243 }
3244
3245 let target = LoroDoc::new();
3246 target
3247 .import(&base.export(ExportMode::Snapshot).unwrap())
3248 .unwrap();
3249 assert!(!target.has_history_cache());
3250
3251 for i in 0_u64..8 {
3252 let remote = LoroDoc::new_auto_commit();
3253 remote.set_peer_id(2 + i).unwrap();
3254 remote
3255 .get_map(format!("isolated-{i}"))
3256 .insert("value", i as i32)
3257 .unwrap();
3258 let update = remote.export(ExportMode::all_updates()).unwrap();
3259 target.import(&update).unwrap();
3260 assert_eq!(
3261 target.get_map(format!("isolated-{i}")).get("value"),
3262 Some((i as i32).into())
3263 );
3264 }
3265
3266 assert!(!target.has_history_cache());
3267 assert_eq!(
3268 target
3269 .oplog
3270 .lock()
3271 .change_store()
3272 .root_history_scan_count_for_test(),
3273 1
3274 );
3275 }
3276
3277 #[test]
3278 fn independent_fast_path_rejects_root_present_only_in_snapshot_state() {
3279 fn snapshot_sections(doc: &LoroDoc) -> crate::encoding::fast_snapshot::Snapshot {
3280 let (_, txn_guard) = doc.implicit_commit_then_stop();
3281 drop(txn_guard);
3282 crate::encoding::fast_snapshot::encode_snapshot_inner(doc).unwrap()
3283 }
3284
3285 let base = LoroDoc::new_auto_commit();
3286 base.set_peer_id(1).unwrap();
3287 base.get_map("existing").insert("value", "base").unwrap();
3288 let base_sections = snapshot_sections(&base);
3289
3290 let state_donor = LoroDoc::new_auto_commit();
3291 state_donor.set_peer_id(999).unwrap();
3292 state_donor
3293 .get_map("isolated")
3294 .insert("value", "stale")
3295 .unwrap();
3296 let donor_sections = snapshot_sections(&state_donor);
3297
3298 let target = LoroDoc::new();
3299 crate::encoding::fast_snapshot::decode_snapshot_inner(
3300 crate::encoding::fast_snapshot::Snapshot {
3301 oplog_bytes: base_sections.oplog_bytes,
3302 state_bytes: donor_sections.state_bytes,
3303 shallow_root_state_bytes: Bytes::new(),
3304 },
3305 &target,
3306 Default::default(),
3307 )
3308 .unwrap();
3309 assert!(!target.has_history_cache());
3310
3311 let left = LoroDoc::new_auto_commit();
3312 left.set_peer_id(2).unwrap();
3313 left.get_map("isolated").insert("value", "left").unwrap();
3314 let right = LoroDoc::new_auto_commit();
3315 right.set_peer_id(3).unwrap();
3316 right.get_map("isolated").insert("value", "winner").unwrap();
3317 let aggregate = LoroDoc::new();
3318 aggregate
3319 .import(&left.export(ExportMode::all_updates()).unwrap())
3320 .unwrap();
3321 aggregate
3322 .import(&right.export(ExportMode::all_updates()).unwrap())
3323 .unwrap();
3324
3325 target
3326 .import(&aggregate.export(ExportMode::all_updates()).unwrap())
3327 .unwrap();
3328
3329 assert_eq!(
3330 target.get_map("isolated").get("value"),
3331 Some("winner".into())
3332 );
3333 assert!(target.has_history_cache());
3334 }
3335
3336 #[test]
3337 fn independent_scalar_root_import_rolls_back_after_state_failure() {
3338 let base = LoroDoc::new_auto_commit();
3339 base.set_peer_id(1).unwrap();
3340 base.get_map("existing").insert("value", "base").unwrap();
3341
3342 let target = LoroDoc::new();
3343 target
3344 .import(&base.export(ExportMode::Snapshot).unwrap())
3345 .unwrap();
3346 let vv_before = target.oplog_vv();
3347 let state_before = target.get_deep_value();
3348
3349 let remote = LoroDoc::new_auto_commit();
3350 remote.set_peer_id(2).unwrap();
3351 remote.get_map("isolated").insert("value", 7).unwrap();
3352 let update = remote.export(ExportMode::all_updates()).unwrap();
3353
3354 crate::state::fail_next_import_state_apply_for_test();
3355 let err = target.import(&update).unwrap_err();
3356 assert!(err.to_string().contains("state apply failpoint"));
3357 assert_eq!(target.oplog_vv(), vv_before);
3358 assert_eq!(target.get_deep_value(), state_before);
3359 assert!(!target.has_history_cache());
3360
3361 target.import(&update).unwrap();
3362 assert_eq!(target.get_map("isolated").get("value"), Some(7.into()));
3363 }
3364
3365 #[test]
3366 fn independent_fast_path_rejects_root_name_seen_in_history() {
3367 let base = LoroDoc::new_auto_commit();
3368 base.set_peer_id(1).unwrap();
3369 base.get_map("shared").insert("value", "base").unwrap();
3370
3371 let target = LoroDoc::new();
3372 target
3373 .import(&base.export(ExportMode::Snapshot).unwrap())
3374 .unwrap();
3375 assert!(!target.has_history_cache());
3376
3377 let remote = LoroDoc::new_auto_commit();
3378 remote.set_peer_id(999).unwrap();
3379 remote.get_map("shared").insert("value", "remote").unwrap();
3380 target
3381 .import(&remote.export(ExportMode::all_updates()).unwrap())
3382 .unwrap();
3383
3384 assert_eq!(target.get_map("shared").get("value"), Some("remote".into()));
3385 assert!(target.has_history_cache());
3386 }
3387
3388 #[test]
3389 fn independent_fast_path_rejects_deleted_root_name() {
3390 let base = LoroDoc::new_auto_commit();
3391 base.set_peer_id(1).unwrap();
3392 let root_id = ContainerID::new_root("deleted", ContainerType::Map);
3393 base.get_map("deleted").insert("value", "base").unwrap();
3394 base.delete_root_container(root_id);
3395
3396 let target = LoroDoc::new();
3397 target
3398 .import(&base.export(ExportMode::Snapshot).unwrap())
3399 .unwrap();
3400 assert!(!target.has_history_cache());
3401
3402 let remote = LoroDoc::new_auto_commit();
3403 remote.set_peer_id(2).unwrap();
3404 remote.get_map("deleted").insert("value", "remote").unwrap();
3405 target
3406 .import(&remote.export(ExportMode::all_updates()).unwrap())
3407 .unwrap();
3408
3409 assert!(target.has_history_cache());
3410 }
3411}