Skip to main content

khive_db/backend/
code_map.rs

1//! Dedicated code-map store construction and its core-schema migration hook.
2
3use std::path::Path;
4use std::sync::atomic::AtomicUsize;
5use std::sync::Arc;
6
7use super::{StorageBackend, StoreSchemaGate};
8use crate::error::SqliteError;
9use crate::pool::{ConnectionPool, PoolConfig};
10use crate::stores::blob::DatabaseGcOwnerGuard;
11use rusqlite::{Connection, OpenFlags};
12
13impl StorageBackend {
14    #[cfg(any(test, feature = "test-support"))]
15    #[doc(hidden)]
16    pub fn active_code_map_handles_for_test(path: impl AsRef<Path>) -> usize {
17        crate::code_map_vfs::active_main_handle_count_for(path.as_ref())
18    }
19
20    /// Open a registered rollback-mode map through the handle-proving VFS.
21    /// This opens an existing file read-only and does not run migrations.
22    pub fn open_code_map_read_only(
23        path: impl AsRef<Path>,
24        expected_identity: crate::file_identity::DatabaseFileIdentity,
25        protected_databases: &[std::path::PathBuf],
26    ) -> Result<Connection, SqliteError> {
27        crate::extension::ensure_extensions_loaded();
28        let path = path.as_ref().to_path_buf();
29        let protected = protected_databases
30            .iter()
31            .map(|path| crate::code_map_vfs::ProductionBase {
32                path: path.clone(),
33                kind: crate::code_map_vfs::ProductionKind::Main,
34            })
35            .collect();
36        let vfs = crate::code_map_vfs::register_read_only_rollback(
37            path.clone(),
38            expected_identity,
39            protected,
40        )
41        .map_err(|error| SqliteError::InvalidData(error.to_string()))?;
42        let connection = crate::code_map_vfs::naming_refusal(&vfs, || {
43            Connection::open_with_flags_and_vfs(
44                &path,
45                OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
46                vfs.as_str(),
47            )
48            .map_err(Into::into)
49        })?;
50        connection.pragma_update(None, "query_only", true)?;
51        Ok(connection)
52    }
53
54    /// Open the dedicated code-map store through the native handle-proving
55    /// VFS. `protected_main` and `protected_events` are configured production
56    /// names; the guard also samples each name's SQLite companions. Disk policy
57    /// and the volume-lock directory come from the process environment, with the
58    /// per-user default lock directory of [`crate::default_volume_lock_dir`];
59    /// callers with captured configuration use [`Self::sqlite_code_map_with_policies`].
60    pub fn sqlite_code_map(
61        path: impl AsRef<Path>,
62        protected_main: &[std::path::PathBuf],
63        protected_events: &[std::path::PathBuf],
64    ) -> Result<Self, SqliteError> {
65        let policy = crate::migrations::MigrationWritePolicy::from_environment()?;
66        Self::sqlite_code_map_with_policies(
67            path,
68            protected_main,
69            protected_events,
70            policy.disk_guard_config(),
71            policy.volume_lock_dir().to_path_buf(),
72        )
73    }
74
75    /// Open a guarded code-map store using the host's captured disk policy and
76    /// absolute shared volume-lock directory. Neither value rereads environment.
77    pub fn sqlite_code_map_with_policies(
78        path: impl AsRef<Path>,
79        protected_main: &[std::path::PathBuf],
80        protected_events: &[std::path::PathBuf],
81        disk_guard_config: crate::EffectiveDiskGuardConfig,
82        volume_lock_dir: std::path::PathBuf,
83    ) -> Result<Self, SqliteError> {
84        let policy =
85            crate::migrations::MigrationWritePolicy::new(disk_guard_config, volume_lock_dir)?;
86        crate::extension::ensure_extensions_loaded();
87        let resolved = path.as_ref().to_path_buf();
88        let protected: Vec<_> = protected_main
89            .iter()
90            .map(|path| crate::code_map_vfs::ProductionBase {
91                path: path.clone(),
92                kind: crate::code_map_vfs::ProductionKind::Main,
93            })
94            .chain(
95                protected_events
96                    .iter()
97                    .map(|path| crate::code_map_vfs::ProductionBase {
98                        path: path.clone(),
99                        kind: crate::code_map_vfs::ProductionKind::Events,
100                    }),
101            )
102            .collect();
103        crate::code_map_vfs::prepare_rollback_target(resolved.clone(), protected.clone()).map_err(
104            |error| {
105                let detail = if error.is_busy() {
106                    format!("{error}; retry after other SQLite clients release the target")
107                } else {
108                    error.to_string()
109                };
110                SqliteError::InvalidData(detail)
111            },
112        )?;
113        let vfs_name = crate::code_map_vfs::register_rollback(resolved.clone(), protected)
114            .map_err(|error| SqliteError::InvalidData(error.to_string()))?;
115        let pool = ConnectionPool::new(PoolConfig {
116            path: Some(resolved.clone()),
117            code_map_vfs: Some(vfs_name),
118            wal_mode: false,
119            disk_guard_config: Some(policy.disk_guard_config()),
120            volume_lock_dir: Some(policy.volume_lock_dir().to_path_buf()),
121            ..PoolConfig::default()
122        })?;
123        Ok(Self {
124            pool: Arc::new(pool),
125            is_file_backed: true,
126            path: Some(resolved),
127            vector_tables_ready: Default::default(),
128            text_tables_ready: Default::default(),
129            notes_seq_repair_runs: AtomicUsize::new(0),
130            store_schemas: std::array::from_fn(|_| Arc::new(StoreSchemaGate::default())),
131        })
132    }
133
134    /// Run the core-schema migrations through this backend's pooled writer,
135    /// one admitted unit per migration transaction.
136    pub(super) fn run_core_migrations(
137        &self,
138        owner: &DatabaseGcOwnerGuard,
139    ) -> Result<u32, SqliteError> {
140        let admission = self.pool.write_admission();
141        let run = || {
142            crate::migrations::run_migrations_with_database_gc_owner(
143                &mut self.pool.migration_transactions(),
144                owner,
145                &admission,
146            )
147        };
148        match self.pool.config().code_map_vfs.as_deref() {
149            // A guarded journal or main open refused mid-migration
150            // surfaces as SQLITE_CANTOPEN; name the refusal.
151            Some(vfs) => crate::code_map_vfs::naming_refusal(vfs, run),
152            None => run(),
153        }
154    }
155}