sqlite_graphrag/storage/
connection.rs1use crate::errors::AppError;
9use crate::paths::AppPaths;
10use crate::pragmas::{apply_connection_pragmas, apply_init_pragmas, ensure_wal_mode};
11use rusqlite::Connection;
12use std::path::Path;
13
14pub fn register_vec_extension() {}
19
20pub fn open_rw(path: &Path) -> Result<Connection, AppError> {
21 let conn = Connection::open(path)?;
22 apply_connection_pragmas(&conn)?;
23 apply_secure_permissions(path);
24 Ok(conn)
25}
26
27pub fn ensure_schema(conn: &mut Connection) -> Result<(), AppError> {
28 crate::migrations::runner()
29 .run(conn)
30 .map_err(|e| AppError::Internal(anyhow::anyhow!("migration failed: {e}")))?;
31 conn.execute_batch(&format!(
32 "PRAGMA user_version = {};",
33 crate::constants::SCHEMA_USER_VERSION
34 ))?;
35 Ok(())
36}
37
38pub fn ensure_db_ready(paths: &AppPaths) -> Result<(), AppError> {
52 register_vec_extension();
53 paths.ensure_dirs()?;
54
55 let db_existed = paths.db.exists();
56
57 if !db_existed {
58 tracing::info!(target: "storage",
59 path = %paths.db.display(),
60 schema_version = crate::constants::CURRENT_SCHEMA_VERSION,
61 "creating database (auto-init)"
62 );
63 }
64
65 let mut conn = open_rw(&paths.db)?;
66
67 if !db_existed {
68 apply_init_pragmas(&conn)?;
69 }
70
71 let current_user_version: i64 = conn
72 .query_row("PRAGMA user_version", [], |row| row.get(0))
73 .unwrap_or(0);
74 let target_user_version = crate::constants::SCHEMA_USER_VERSION;
75
76 if current_user_version < target_user_version {
77 if db_existed {
78 tracing::warn!(target: "storage",
79 from = current_user_version,
80 to = target_user_version,
81 path = %paths.db.display(),
82 "auto-migrating database schema"
83 );
84 }
85 crate::migrations::runner()
86 .run(&mut conn)
87 .map_err(|e| AppError::Internal(anyhow::anyhow!("auto-migration failed: {e}")))?;
88 conn.execute_batch(&format!("PRAGMA user_version = {target_user_version};"))?;
89
90 if !db_existed {
91 insert_default_schema_meta(&conn)?;
92 }
93
94 ensure_wal_mode(&conn)?;
99 }
100
101 crate::commands::migrate::ensure_v013_tables_exist(&conn)?;
105
106 Ok(())
107}
108
109fn insert_default_schema_meta(conn: &Connection) -> Result<(), AppError> {
110 conn.execute(
111 "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', ?1)",
112 rusqlite::params![crate::constants::CURRENT_SCHEMA_VERSION.to_string()],
113 )?;
114 conn.execute(
115 "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('model', 'multilingual-e5-small')",
116 [],
117 )?;
118 conn.execute(
119 "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('dim', '384')",
120 [],
121 )?;
122 conn.execute(
123 "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('created_at', CAST(unixepoch() AS TEXT))",
124 [],
125 )?;
126 conn.execute(
127 "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('sqlite-graphrag_version', ?1)",
128 rusqlite::params![crate::constants::SQLITE_GRAPHRAG_VERSION],
129 )?;
130 Ok(())
131}
132
133#[allow(unused_variables)]
140fn apply_secure_permissions(path: &Path) {
141 #[cfg(unix)]
142 {
143 use std::os::unix::fs::PermissionsExt;
144 let candidates = [
145 path.to_path_buf(),
146 path.with_extension(format!(
147 "{}-wal",
148 path.extension()
149 .and_then(|e| e.to_str())
150 .unwrap_or("sqlite")
151 )),
152 path.with_extension(format!(
153 "{}-shm",
154 path.extension()
155 .and_then(|e| e.to_str())
156 .unwrap_or("sqlite")
157 )),
158 ];
159 for file in candidates.iter() {
160 if file.exists() {
161 if let Ok(meta) = std::fs::metadata(file) {
162 let mut perms = meta.permissions();
163 perms.set_mode(0o600);
164 let _ = std::fs::set_permissions(file, perms);
165 }
166 }
167 }
168 }
169 #[cfg(windows)]
170 {
171 tracing::debug!(target: "storage",
172 path = %path.display(),
173 "skipping Unix mode 0o600 on Windows; NTFS DACL default is private-to-user"
174 );
175 }
176}
177
178pub fn open_ro(path: &Path) -> Result<Connection, AppError> {
179 let conn = Connection::open_with_flags(
180 path,
181 rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_URI,
182 )?;
183 conn.execute_batch("PRAGMA foreign_keys = ON;")?;
184 Ok(conn)
185}