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;
11
12impl StorageBackend {
13    /// Open the dedicated code-map store through the native handle-proving
14    /// VFS. `protected_main` and `protected_events` are configured production
15    /// names; the guard also samples each name's SQLite companions. Disk policy
16    /// and the volume-lock directory come from the process environment, with the
17    /// per-user default lock directory of [`crate::default_volume_lock_dir`];
18    /// callers with captured configuration use [`Self::sqlite_code_map_with_policies`].
19    pub fn sqlite_code_map(
20        path: impl AsRef<Path>,
21        protected_main: &[std::path::PathBuf],
22        protected_events: &[std::path::PathBuf],
23    ) -> Result<Self, SqliteError> {
24        let policy = crate::migrations::MigrationWritePolicy::from_environment()?;
25        Self::sqlite_code_map_with_policies(
26            path,
27            protected_main,
28            protected_events,
29            policy.disk_guard_config(),
30            policy.volume_lock_dir().to_path_buf(),
31        )
32    }
33
34    /// Open a guarded code-map store using the host's captured disk policy and
35    /// absolute shared volume-lock directory. Neither value rereads environment.
36    pub fn sqlite_code_map_with_policies(
37        path: impl AsRef<Path>,
38        protected_main: &[std::path::PathBuf],
39        protected_events: &[std::path::PathBuf],
40        disk_guard_config: crate::EffectiveDiskGuardConfig,
41        volume_lock_dir: std::path::PathBuf,
42    ) -> Result<Self, SqliteError> {
43        let policy =
44            crate::migrations::MigrationWritePolicy::new(disk_guard_config, volume_lock_dir)?;
45        crate::extension::ensure_extensions_loaded();
46        let resolved = path.as_ref().to_path_buf();
47        let protected: Vec<_> = protected_main
48            .iter()
49            .map(|path| crate::code_map_vfs::ProductionBase {
50                path: path.clone(),
51                kind: crate::code_map_vfs::ProductionKind::Main,
52            })
53            .chain(
54                protected_events
55                    .iter()
56                    .map(|path| crate::code_map_vfs::ProductionBase {
57                        path: path.clone(),
58                        kind: crate::code_map_vfs::ProductionKind::Events,
59                    }),
60            )
61            .collect();
62        crate::code_map_vfs::prepare_rollback_target(resolved.clone(), protected.clone()).map_err(
63            |error| {
64                let detail = if error.is_busy() {
65                    format!("{error}; retry after other SQLite clients release the target")
66                } else {
67                    error.to_string()
68                };
69                SqliteError::InvalidData(detail)
70            },
71        )?;
72        let vfs_name = crate::code_map_vfs::register_rollback(resolved.clone(), protected)
73            .map_err(|error| SqliteError::InvalidData(error.to_string()))?;
74        let pool = ConnectionPool::new(PoolConfig {
75            path: Some(resolved.clone()),
76            code_map_vfs: Some(vfs_name),
77            wal_mode: false,
78            disk_guard_config: Some(policy.disk_guard_config()),
79            volume_lock_dir: Some(policy.volume_lock_dir().to_path_buf()),
80            ..PoolConfig::default()
81        })?;
82        Ok(Self {
83            pool: Arc::new(pool),
84            is_file_backed: true,
85            path: Some(resolved),
86            vector_tables_ready: Default::default(),
87            notes_seq_repair_runs: AtomicUsize::new(0),
88            store_schemas: std::array::from_fn(|_| Arc::new(StoreSchemaGate::default())),
89        })
90    }
91
92    /// Run the core-schema migrations through this backend's pooled writer,
93    /// one admitted unit per migration transaction.
94    pub(super) fn run_core_migrations(
95        &self,
96        owner: &DatabaseGcOwnerGuard,
97    ) -> Result<u32, SqliteError> {
98        let admission = self.pool.write_admission();
99        let run = || {
100            crate::migrations::run_migrations_with_database_gc_owner(
101                &mut self.pool.migration_transactions(),
102                owner,
103                &admission,
104            )
105        };
106        match self.pool.config().code_map_vfs.as_deref() {
107            // A guarded journal or main open refused mid-migration
108            // surfaces as SQLITE_CANTOPEN; name the refusal.
109            Some(vfs) => crate::code_map_vfs::naming_refusal(vfs, run),
110            None => run(),
111        }
112    }
113}