Skip to main content

we_trust_sqlite/writer/
mod.rs

1use yykv_types::DsError;
2type Result<T> = std::result::Result<T, DsError>;
3use crate::utils::parse_varint;
4use tokio::fs::{File, OpenOptions};
5use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom};
6
7mod btree;
8mod pager;
9mod payload;
10mod wal;
11
12pub use wal::WalWriter;
13
14/// 原生 SQLite 写入器
15pub struct NativeWriter {
16    path: String,
17}
18
19impl NativeWriter {
20    pub fn new(path: impl Into<String>) -> Self {
21        Self { path: path.into() }
22    }
23
24    /// 确保数据库文件已初始化
25    pub async fn ensure_initialized(&self) -> Result<()> {
26        if tokio::fs::metadata(&self.path).await.is_ok() {
27            return Ok(());
28        }
29
30        let mut file = OpenOptions::new()
31            .create(true)
32            .write(true)
33            .open(&self.path)
34            .await
35            .map_err(|e| DsError::io(format!("Failed to create database file: {}", e)))?;
36
37        let page_size: u32 = 4096;
38        let mut header = vec![0u8; page_size as usize];
39
40        // 1. Magic Header String: "SQLite format 3\0"
41        header[0..16].copy_from_slice(b"SQLite format 3\0");
42
43        // 2. Page Size (bytes 16-17)
44        header[16..18].copy_from_slice(&(page_size as u16).to_be_bytes());
45
46        // 3. File format write version (1: legacy, 2: WAL)
47        header[18] = 1;
48        // 4. File format read version (1: legacy, 2: WAL)
49        header[19] = 1;
50
51        // 5. Reserved space at end of each page
52        header[20] = 0;
53
54        // 6. Max embedded payload fraction (64)
55        header[21] = 64;
56        // 7. Min embedded payload fraction (32)
57        header[22] = 32;
58        // 8. Leaf payload fraction (32)
59        header[23] = 32;
60
61        // 9. File change counter
62        header[24..28].copy_from_slice(&1u32.to_be_bytes());
63
64        // 10. Size of database in pages
65        header[28..32].copy_from_slice(&1u32.to_be_bytes());
66
67        // 11. First freelist trunk page
68        header[32..36].copy_from_slice(&0u32.to_be_bytes());
69        // 12. Total number of freelist pages
70        header[36..40].copy_from_slice(&0u32.to_be_bytes());
71
72        // 13. Schema cookie
73        header[40..44].copy_from_slice(&1u32.to_be_bytes());
74        // 14. Schema format number (4 is recommended)
75        header[44..48].copy_from_slice(&4u32.to_be_bytes());
76
77        // 15. Default page cache size
78        header[48..52].copy_from_slice(&0u32.to_be_bytes());
79
80        // 16. Largest root b-tree page number (0 for vacuum, else 0)
81        header[52..56].copy_from_slice(&0u32.to_be_bytes());
82
83        // 17. Text encoding (1: UTF-8)
84        header[56..60].copy_from_slice(&1u32.to_be_bytes());
85
86        // 18. User version
87        header[60..64].copy_from_slice(&0u32.to_be_bytes());
88
89        // 19. Incremental vacuum mode (0)
90        header[64..68].copy_from_slice(&0u32.to_be_bytes());
91
92        // 20. Application ID
93        header[68..72].copy_from_slice(&0u32.to_be_bytes());
94
95        // 21. Reserved for expansion
96        // ... all zeros ...
97
98        // 22. Version-valid-for number
99        header[92..96].copy_from_slice(&1u32.to_be_bytes());
100
101        // 23. SQLITE_VERSION_NUMBER
102        header[96..100].copy_from_slice(&3034000u32.to_be_bytes());
103
104        // Page 1 Header (Starts at 100)
105        // 0x0D: Leaf Table Page
106        header[100] = 0x0D;
107        // Cell content start (at end of page)
108        header[105] = (page_size >> 8) as u8;
109        header[106] = (page_size & 0xFF) as u8;
110
111        file.write_all(&header)
112            .await
113            .map_err(|e| DsError::io(format!("Failed to write database header: {}", e)))?;
114
115        Ok(())
116    }
117}