Skip to main content

mj_controller/
database.rs

1pub use mj_core::storage::*;
2// Normalized controller state and composer history stored in SQLite.
3
4use std::collections::{BTreeMap, BTreeSet, HashSet};
5use std::fs;
6use std::path::{Path, PathBuf};
7use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
9use std::sync::{Arc, Mutex, OnceLock, PoisonError};
10use std::thread::{self, JoinHandle};
11use std::time::Duration;
12
13use anyhow::{Context, Result, bail, ensure};
14use chrono::Utc;
15use rusqlite::types::Type;
16use rusqlite::{Connection, OptionalExtension, Transaction, params};
17
18use mj_core::config::data_dir;
19use mj_core::state::{
20    CheckpointMetadata, HostContainerSize, ManagedWorktree, MaterializedExecutionState,
21    MaterializedQueuedPrompt, MaterializedSession, MaterializedSessionSummary, MaterializedTurn,
22    MaterializedTurnOutcome, ProjectionWindow, SessionRecord, SessionResourceAllocation,
23    SessionState, State, TargetLocator, TranscriptBody, TranscriptItem,
24    validate_relay_event_digest, validate_relay_event_frontier,
25};
26use mj_core::subagent::SubagentRecord;
27
28use crate::targets::{AdditionalMount, MountAccess};
29use mj_core::workspace::{
30    ConversationLayout, DEFAULT_WORKSPACE_ID, DetachedDraft, PaneSize, PaneSizes, WorkspaceRecord,
31    new_workspace_id, normalize_workspace_name,
32};
33
34const SCHEMA_VERSION: i64 = 82;
35
36mod session_move;
37pub use session_move::*;
38mod session_restart;
39pub(crate) use session_restart::*;
40mod github_watch;
41pub(crate) use github_watch::*;
42mod mailbox;
43pub(crate) use mailbox::*;
44
45mod legacy_schema;
46mod schema;
47mod turn_outcome;
48use turn_outcome::{deserialize_turn_outcome, serialize_turn_outcome};
49mod usage;
50pub use usage::*;
51mod usage_report;
52pub use usage_report::load_session_usage_report;
53mod events;
54pub use events::*;
55
56#[cfg(test)]
57use schema::forget_verified_schema;
58pub use schema::{check_read_compatibility, database_path};
59use schema::{open, open_reader};
60
61mod writer;
62pub use writer::*;
63mod committed;
64pub use committed::{CommittedState, CommittedTurn};
65mod workspaces;
66pub use workspaces::*;
67mod client_state;
68pub use client_state::*;
69mod state_io;
70pub use state_io::*;
71mod sessions;
72pub use sessions::*;
73mod native_agents;
74pub use native_agents::*;
75mod materialized;
76pub use materialized::*;
77mod mounts;
78pub use mounts::*;
79mod reviews;
80pub use reviews::*;
81mod startup;
82pub(crate) use startup::*;
83mod delegation;
84pub(crate) use delegation::*;
85mod worker_restart;
86pub(crate) use worker_restart::*;
87mod prompts;
88pub use prompts::*;
89mod values;
90use values::*;
91mod profile_cache;
92pub use profile_cache::*;
93mod projects;
94pub(crate) use projects::*;
95
96#[cfg(test)]
97mod tests;
98#[cfg(test)]
99pub(crate) fn test_session(id: &str, bundle: &str) -> SessionRecord {
100    tests::session(id, bundle)
101}
102
103mod quota_cache;
104pub(crate) use quota_cache::*;
105
106/// Whether a failure is SQLite reporting another writer, which a later trigger
107/// simply retries.
108pub(crate) fn is_busy_error(error: &anyhow::Error) -> bool {
109    error.chain().any(|cause| {
110        matches!(
111            cause.downcast_ref::<rusqlite::Error>(),
112            Some(rusqlite::Error::SqliteFailure(failure, _))
113                if matches!(
114                    failure.code,
115                    rusqlite::ErrorCode::DatabaseBusy | rusqlite::ErrorCode::DatabaseLocked
116                )
117        )
118    })
119}