mf_persistence/
sqlite.rs

1//! 基于 SQLite 的 `EventStore` 实现。
2//!
3//! 默认使用 WAL 日志模式与 IMMEDIATE 事务以获得低延迟写入。
4//! 采用“单连接 + 互斥”顺序写,确保追加顺序并避免写锁竞争;
5//! 读取按需准备语句,受益于 SQLite 页缓存。
6
7use std::path::PathBuf;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use rusqlite::{params, TransactionBehavior};
12use r2d2::Pool;
13use r2d2_sqlite::SqliteConnectionManager;
14use crate::api::{CommitMode, EventStore, PersistedEvent, Snapshot};
15
16/// `EventStore` 的 SQLite 具体实现。
17pub struct SqliteEventStore {
18    _db_path: PathBuf,
19    pool: Pool<SqliteConnectionManager>,
20    commit_mode: CommitMode,
21}
22
23impl SqliteEventStore {
24    /// 打开(或创建)数据库并初始化表结构与 PRAGMA。
25    pub fn open(
26        db_path: impl Into<PathBuf>,
27        commit_mode: CommitMode,
28    ) -> anyhow::Result<Arc<Self>> {
29        let db_path = db_path.into();
30        // 确保父目录存在
31        if let Some(parent) = db_path.parent() {
32            if !parent.as_os_str().is_empty() {
33                std::fs::create_dir_all(parent)?;
34            }
35        }
36        let manager = SqliteConnectionManager::file(&db_path);
37        let pool = Pool::new(manager)?;
38        pool.get()?.execute_batch(
39            r#"
40            CREATE TABLE IF NOT EXISTS events (
41              lsn INTEGER PRIMARY KEY AUTOINCREMENT,
42              tr_id TEXT NOT NULL,
43              doc_id TEXT NOT NULL,
44              ts INTEGER NOT NULL,
45              actor TEXT,
46              idempotency_key TEXT NOT NULL UNIQUE,
47              meta TEXT NOT NULL,
48              payload BLOB NOT NULL,
49              checksum INTEGER NOT NULL
50            );
51            CREATE INDEX IF NOT EXISTS ix_events_doc_lsn ON events(doc_id, lsn);
52
53            CREATE TABLE IF NOT EXISTS snapshots (
54              doc_id TEXT NOT NULL,
55              upto_lsn INTEGER NOT NULL,
56              created_at INTEGER NOT NULL,
57              state_blob BLOB NOT NULL,
58              version INTEGER NOT NULL,
59              PRIMARY KEY (doc_id, upto_lsn)
60            );
61            CREATE INDEX IF NOT EXISTS ix_snapshots_doc_created ON snapshots(doc_id, created_at DESC);
62            "#,
63        )?;
64
65        Ok(Arc::new(Self { _db_path: db_path, pool, commit_mode }))
66    }
67}
68
69#[async_trait]
70impl EventStore for SqliteEventStore {
71    /// 以独立事务追加一条事件记录。
72    async fn append(
73        &self,
74        ev: PersistedEvent,
75    ) -> anyhow::Result<i64> {
76        let mut conn = self.pool.get()?;
77        let tx =
78            conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
79        tx.execute(
80            "INSERT INTO events (tr_id, doc_id, ts, actor, idempotency_key, meta, payload, checksum) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
81            params![
82                ev.tr_id.to_string(), // ✅ UUID 转字符串存储
83                ev.doc_id,
84                { ev.ts },
85                ev.actor,
86                ev.idempotency_key,
87                ev.meta.to_string(),
88                ev.payload,
89                ev.checksum as i64
90            ],
91        )?;
92        tx.commit()?;
93        let lsn = conn.last_insert_rowid();
94        if let CommitMode::SyncDurable = self.commit_mode {
95            conn.pragma_update(None, "wal_checkpoint", "TRUNCATE").ok();
96        }
97        Ok(lsn)
98    }
99
100    /// 在单事务内批量追加多条事件以提升吞吐。
101    async fn append_batch(
102        &self,
103        evs: Vec<PersistedEvent>,
104    ) -> anyhow::Result<i64> {
105        if evs.is_empty() {
106            return Ok(0);
107        }
108        let mut conn = self.pool.get()?;
109        let tx =
110            conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
111        {
112            let mut stmt = tx.prepare("INSERT INTO events (tr_id, doc_id, ts, actor, idempotency_key, meta, payload, checksum) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)")?;
113            for ev in evs.into_iter() {
114                stmt.execute(params![
115                    ev.tr_id.to_string(), // ✅ UUID 转字符串存储
116                    ev.doc_id,
117                    { ev.ts },
118                    ev.actor,
119                    ev.idempotency_key,
120                    ev.meta.to_string(),
121                    ev.payload,
122                    ev.checksum as i64
123                ])?;
124            }
125        }
126        tx.commit()?;
127        let lsn = conn.last_insert_rowid();
128        Ok(lsn)
129    }
130
131    /// 读取指定文档在 `from_lsn` 之后的有序事件流。
132    async fn load_since(
133        &self,
134        doc_id: &str,
135        from_lsn: i64,
136        limit: u32,
137    ) -> anyhow::Result<Vec<PersistedEvent>> {
138        let conn = self.pool.get()?;
139        let mut stmt = conn.prepare("SELECT lsn, tr_id, doc_id, ts, actor, idempotency_key, meta, payload, checksum FROM events WHERE doc_id = ?1 AND lsn > ?2 ORDER BY lsn ASC LIMIT ?3")?;
140        let rows =
141            stmt.query_map(params![doc_id, from_lsn, limit as i64], |row| {
142                let meta_str: String = row.get(6)?;
143                let meta: serde_json::Value = serde_json::from_str(&meta_str)
144                    .unwrap_or(serde_json::json!({}));
145                let tr_id_str: String = row.get(1)?;
146                let tr_id = uuid::Uuid::parse_str(&tr_id_str).map_err(|e| {
147                    rusqlite::Error::FromSqlConversionFailure(
148                        1,
149                        rusqlite::types::Type::Text,
150                        Box::new(e),
151                    )
152                })?;
153                Ok(PersistedEvent {
154                    lsn: row.get(0)?,
155                    tr_id, // ✅ 从字符串解析 UUID
156                    doc_id: row.get(2)?,
157                    ts: row.get(3)?,
158                    actor: row.get(4).ok(),
159                    idempotency_key: row.get(5)?,
160                    meta,
161                    payload: row.get(7)?,
162                    checksum: row.get::<_, i64>(8)? as u32,
163                })
164            })?;
165        Ok(rows.filter_map(|r| r.ok()).collect())
166    }
167
168    /// 返回该文档的最新快照(若存在)。
169    async fn latest_snapshot(
170        &self,
171        doc_id: &str,
172    ) -> anyhow::Result<Option<Snapshot>> {
173        let conn = self.pool.get()?;
174        let mut stmt = conn.prepare("SELECT doc_id, upto_lsn, created_at, state_blob, version FROM snapshots WHERE doc_id = ?1 ORDER BY created_at DESC LIMIT 1")?;
175        let mut rows = stmt.query(params![doc_id])?;
176        if let Some(row) = rows.next()? {
177            Ok(Some(Snapshot {
178                doc_id: row.get(0)?,
179                upto_lsn: row.get(1)?,
180                created_at: row.get(2)?,
181                state_blob: row.get(3)?,
182                version: row.get(4)?,
183            }))
184        } else {
185            Ok(None)
186        }
187    }
188
189    /// 原子写入/替换快照。
190    async fn write_snapshot(
191        &self,
192        snap: Snapshot,
193    ) -> anyhow::Result<()> {
194        let mut conn = self.pool.get()?;
195        let tx =
196            conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
197        tx.execute(
198            "INSERT OR REPLACE INTO snapshots (doc_id, upto_lsn, created_at, state_blob, version) VALUES (?1, ?2, ?3, ?4, ?5)",
199            params![snap.doc_id, snap.upto_lsn, snap.created_at, snap.state_blob, snap.version],
200        )?;
201        tx.commit()?;
202        Ok(())
203    }
204
205    /// 最佳努力的历史压缩:删除最新快照之前的事件。
206    async fn compact(
207        &self,
208        doc_id: &str,
209    ) -> anyhow::Result<()> {
210        let conn = self.pool.get()?;
211        // 删除最老的事件到最近快照
212        let upto: Option<i64> = conn.query_row(
213            "SELECT upto_lsn FROM snapshots WHERE doc_id = ?1 ORDER BY upto_lsn DESC LIMIT 1",
214            params![doc_id],
215            |r| r.get(0),
216        ).ok();
217        if let Some(upto_lsn) = upto {
218            conn.execute(
219                "DELETE FROM events WHERE doc_id = ?1 AND lsn <= ?2",
220                params![doc_id, upto_lsn],
221            )?;
222        }
223        Ok(())
224    }
225}