Skip to main content

common/database/
rope_store.rs

1//! Ropey-backed storage backend.
2//!
3//! Holds character data in a single document-wide `ropey::Rope`,
4//! with structural entities (Frames, Tables, Lists, Resources) in
5//! `im::HashMap` tables and per-block character formatting in
6//! `format_runs`. See the migration plan §1.5 for the relationship
7//! inlining and §1.6 for the rope layout (block boundary `\n` +
8//! U+FFFC table anchor).
9
10use crate::database::block_offset_index::BlockOffsetIndex;
11use crate::entities::*;
12use crate::format_runs::{FormatRun, ImageAnchor};
13use crate::snapshot::{StoreSnapshot, StoreSnapshotTrait};
14use crate::types::EntityId;
15use im::HashMap;
16use ropey::Rope;
17use std::collections::HashMap as StdHashMap;
18use std::sync::RwLock;
19
20// ─────────────────────────────────────────────────────────────────────────────
21// The Store
22// ─────────────────────────────────────────────────────────────────────────────
23
24#[derive(Debug, Default)]
25pub struct RopeStore {
26    // ── Character content (shared across all blocks, including cells) ──
27    pub rope: RwLock<Rope>,
28
29    // ── Structural entity tables ──────────────────────────────────────
30    pub roots: RwLock<HashMap<EntityId, Root>>,
31    pub documents: RwLock<HashMap<EntityId, Document>>,
32    pub frames: RwLock<HashMap<EntityId, Frame>>,
33    pub blocks: RwLock<HashMap<EntityId, Block>>,
34    pub lists: RwLock<HashMap<EntityId, List>>,
35    pub resources: RwLock<HashMap<EntityId, Resource>>,
36    pub tables: RwLock<HashMap<EntityId, Table>>,
37    pub table_cells: RwLock<HashMap<EntityId, TableCell>>,
38
39    // ── Per-block character formatting + image anchors ────────────────
40    pub format_runs: RwLock<HashMap<EntityId, Vec<FormatRun>>>,
41    pub block_images: RwLock<HashMap<EntityId, Vec<ImageAnchor>>>,
42
43    // ── Document-wide block ordering (sorted by rope position) ────────
44    pub block_offsets: RwLock<BlockOffsetIndex>,
45
46    // ── ID counters ───────────────────────────────────────────────────
47    // Never restored by undo (only by transaction rollback).
48    pub counters: RwLock<StdHashMap<String, EntityId>>,
49
50    // ── Savepoints (in-memory, transaction-scoped) ────────────────────
51    savepoints: RwLock<StdHashMap<u64, RopeStoreSnapshot>>,
52    next_savepoint_id: RwLock<u64>,
53}
54
55impl RopeStore {
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// O(1) snapshot of the entire store (rope is Arc-shared, all
61    /// `im::HashMap`s are HAMT-shared; `BlockOffsetIndex` is a small
62    /// `Vec` cloned outright).
63    pub fn snapshot(&self) -> RopeStoreSnapshot {
64        RopeStoreSnapshot {
65            rope: self.rope.read().unwrap().clone(),
66            roots: self.roots.read().unwrap().clone(),
67            documents: self.documents.read().unwrap().clone(),
68            frames: self.frames.read().unwrap().clone(),
69            blocks: self.blocks.read().unwrap().clone(),
70            lists: self.lists.read().unwrap().clone(),
71            resources: self.resources.read().unwrap().clone(),
72            tables: self.tables.read().unwrap().clone(),
73            table_cells: self.table_cells.read().unwrap().clone(),
74            format_runs: self.format_runs.read().unwrap().clone(),
75            block_images: self.block_images.read().unwrap().clone(),
76            block_offsets: self.block_offsets.read().unwrap().clone(),
77            counters: self.counters.read().unwrap().clone(),
78        }
79    }
80
81    /// Restore from a snapshot. Overwrites counters too — used for
82    /// transaction rollback (`Drop` of an uncommitted write txn).
83    pub fn restore(&self, snap: &RopeStoreSnapshot) {
84        *self.rope.write().unwrap() = snap.rope.clone();
85        *self.roots.write().unwrap() = snap.roots.clone();
86        *self.documents.write().unwrap() = snap.documents.clone();
87        *self.frames.write().unwrap() = snap.frames.clone();
88        *self.blocks.write().unwrap() = snap.blocks.clone();
89        *self.lists.write().unwrap() = snap.lists.clone();
90        *self.resources.write().unwrap() = snap.resources.clone();
91        *self.tables.write().unwrap() = snap.tables.clone();
92        *self.table_cells.write().unwrap() = snap.table_cells.clone();
93        *self.format_runs.write().unwrap() = snap.format_runs.clone();
94        *self.block_images.write().unwrap() = snap.block_images.clone();
95        *self.block_offsets.write().unwrap() = snap.block_offsets.clone();
96        *self.counters.write().unwrap() = snap.counters.clone();
97    }
98
99    /// Restore everything *except* counters — used for undo, where IDs
100    /// must remain monotonically increasing across undo/redo cycles.
101    pub fn restore_without_counters(&self, snap: &RopeStoreSnapshot) {
102        *self.rope.write().unwrap() = snap.rope.clone();
103        *self.roots.write().unwrap() = snap.roots.clone();
104        *self.documents.write().unwrap() = snap.documents.clone();
105        *self.frames.write().unwrap() = snap.frames.clone();
106        *self.blocks.write().unwrap() = snap.blocks.clone();
107        *self.lists.write().unwrap() = snap.lists.clone();
108        *self.resources.write().unwrap() = snap.resources.clone();
109        *self.tables.write().unwrap() = snap.tables.clone();
110        *self.table_cells.write().unwrap() = snap.table_cells.clone();
111        *self.format_runs.write().unwrap() = snap.format_runs.clone();
112        *self.block_images.write().unwrap() = snap.block_images.clone();
113        *self.block_offsets.write().unwrap() = snap.block_offsets.clone();
114        // counters intentionally not restored
115    }
116
117    pub fn create_savepoint(&self) -> u64 {
118        let snap = self.snapshot();
119        let mut id_counter = self.next_savepoint_id.write().unwrap();
120        let id = *id_counter;
121        *id_counter += 1;
122        self.savepoints.write().unwrap().insert(id, snap);
123        id
124    }
125
126    pub fn restore_savepoint(&self, savepoint_id: u64) {
127        let snap = self
128            .savepoints
129            .read()
130            .unwrap()
131            .get(&savepoint_id)
132            .expect("savepoint not found")
133            .clone();
134        self.restore(&snap);
135    }
136
137    pub fn discard_savepoint(&self, savepoint_id: u64) {
138        self.savepoints.write().unwrap().remove(&savepoint_id);
139    }
140
141    /// Get-and-increment counter for an entity type.
142    pub(crate) fn next_id(&self, entity_name: &str) -> EntityId {
143        let mut counters = self.counters.write().unwrap();
144        let counter = counters.entry(entity_name.to_string()).or_insert(1);
145        let id = *counter;
146        *counter += 1;
147        id
148    }
149
150    /// Type-erased store snapshot (for the generic undo path).
151    pub fn store_snapshot(&self) -> StoreSnapshot {
152        StoreSnapshot::new(self.snapshot())
153    }
154
155    /// Restore from a type-erased store snapshot (undo semantic —
156    /// counters preserved).
157    pub fn restore_store_snapshot(&self, snap: &StoreSnapshot) {
158        let s = snap
159            .downcast_ref::<RopeStoreSnapshot>()
160            .expect("StoreSnapshot must contain RopeStoreSnapshot");
161        self.restore_without_counters(s);
162    }
163}
164
165// ─────────────────────────────────────────────────────────────────────────────
166// Snapshot
167// ─────────────────────────────────────────────────────────────────────────────
168
169/// O(1)-clone snapshot. `Rope::clone()` shares the Arc-d B+ tree root;
170/// every `im::HashMap::clone()` is HAMT-structural.
171#[derive(Debug, Clone, Default)]
172pub struct RopeStoreSnapshot {
173    pub(crate) rope: Rope,
174    pub(crate) roots: HashMap<EntityId, Root>,
175    pub(crate) documents: HashMap<EntityId, Document>,
176    pub(crate) frames: HashMap<EntityId, Frame>,
177    pub(crate) blocks: HashMap<EntityId, Block>,
178    pub(crate) lists: HashMap<EntityId, List>,
179    pub(crate) resources: HashMap<EntityId, Resource>,
180    pub(crate) tables: HashMap<EntityId, Table>,
181    pub(crate) table_cells: HashMap<EntityId, TableCell>,
182    pub(crate) format_runs: HashMap<EntityId, Vec<FormatRun>>,
183    pub(crate) block_images: HashMap<EntityId, Vec<ImageAnchor>>,
184    pub(crate) block_offsets: BlockOffsetIndex,
185    pub(crate) counters: StdHashMap<String, EntityId>,
186}
187
188impl StoreSnapshotTrait for RopeStoreSnapshot {
189    fn clone_box(&self) -> Box<dyn StoreSnapshotTrait> {
190        Box::new(self.clone())
191    }
192
193    fn as_any(&self) -> &dyn std::any::Any {
194        self
195    }
196}