agg/cmds/codex/
compact.rs1use std::io::ErrorKind;
2use std::path::Path;
3use std::path::PathBuf;
4use std::time::Duration;
5
6use owo_colors::OwoColorize;
7use rootcause::prelude::ResultExt;
8use rootcause::report;
9use rusqlite::Connection;
10use rusqlite::OpenFlags;
11
12const DATABASE_FILE_NAME: &str = "logs_2.sqlite";
13const SQLITE_BUSY_TIMEOUT: Duration = Duration::from_secs(3);
14
15pub fn run() -> rootcause::Result<()> {
22 let database_path = database_path()?;
23 let connection = open_database(&database_path)?;
24
25 verify_integrity(&connection, "pre-vacuum")?;
26
27 println!("{}", "Checkpointing the Codex log database".blue().bold());
28 let checkpoint = checkpoint_wal(&connection)?;
29 println!(
30 "{} busy={} log_frames={} checkpointed_frames={}",
31 "Checkpoint complete:".green().bold(),
32 checkpoint.busy,
33 checkpoint.log_frames,
34 checkpoint.checkpointed_frames
35 );
36
37 println!("{}", "Compacting the active Codex log database".blue().bold());
38 connection.execute_batch("VACUUM;")?;
39
40 verify_integrity(&connection, "post-vacuum")?;
41 let stats = database_stats(&connection)?;
42 println!(
43 "{} page_count={} freelist_count={}",
44 "Compaction complete:".green().bold(),
45 stats.page_count,
46 stats.freelist_count
47 );
48
49 Ok(())
50}
51
52fn database_path() -> rootcause::Result<PathBuf> {
53 let codex_home = std::env::var_os("CODEX_HOME")
54 .filter(|value| !value.is_empty())
55 .map_or_else(
56 || ytil_sys::dir::build_home_path(&[".codex"]),
57 |value| Ok(PathBuf::from(value)),
58 )?;
59 let database_path = codex_home.join(DATABASE_FILE_NAME);
60 let metadata = database_path.metadata().map_err(|error| {
61 if error.kind() == ErrorKind::NotFound {
62 report!("Codex log database not found").attach(format!("path={}", database_path.display()))
63 } else {
64 report!("cannot read Codex log database metadata")
65 .attach(format!("path={} error={error}", database_path.display()))
66 }
67 })?;
68
69 if !metadata.is_file() {
70 return Err(
71 report!("Codex log database is not a regular file").attach(format!("path={}", database_path.display()))
72 );
73 }
74
75 Ok(database_path)
76}
77
78fn open_database(database_path: &Path) -> rootcause::Result<Connection> {
79 let connection = Connection::open_with_flags(database_path, OpenFlags::SQLITE_OPEN_READ_WRITE)
80 .attach_with(|| format!("cannot open Codex log database | path={}", database_path.display()))?;
81 connection.busy_timeout(SQLITE_BUSY_TIMEOUT)?;
82 Ok(connection)
83}
84
85fn verify_integrity(connection: &Connection, phase: &str) -> rootcause::Result<()> {
86 let mut statement = connection.prepare("PRAGMA quick_check;")?;
87 let results = statement
88 .query_map([], |row| row.get::<_, String>(0))?
89 .collect::<rusqlite::Result<Vec<_>>>()?;
90
91 if results.as_slice() != ["ok"] {
92 return Err(
93 report!("Codex log database integrity check failed").attach(format!("phase={phase} results={results:#?}"))
94 );
95 }
96
97 Ok(())
98}
99
100fn checkpoint_wal(connection: &Connection) -> rootcause::Result<CheckpointState> {
101 let checkpoint = connection.query_row("PRAGMA wal_checkpoint(TRUNCATE);", [], |row| {
102 Ok(CheckpointState {
103 busy: row.get(0)?,
104 log_frames: row.get(1)?,
105 checkpointed_frames: row.get(2)?,
106 })
107 })?;
108
109 if checkpoint.busy != 0 {
110 return Err(report!("Codex log database WAL checkpoint is busy").attach(format!("checkpoint={checkpoint:#?}")));
111 }
112 if checkpoint.log_frames != checkpoint.checkpointed_frames {
113 return Err(report!("Codex log database WAL truncate checkpoint is incomplete")
114 .attach(format!("checkpoint={checkpoint:#?}")));
115 }
116
117 Ok(checkpoint)
118}
119
120fn database_stats(connection: &Connection) -> rootcause::Result<DatabaseStats> {
121 connection
122 .query_row(
123 "SELECT page_count, freelist_count FROM pragma_page_count(), pragma_freelist_count();",
124 [],
125 |row| {
126 Ok(DatabaseStats {
127 page_count: row.get(0)?,
128 freelist_count: row.get(1)?,
129 })
130 },
131 )
132 .map_err(Into::into)
133}
134
135#[derive(Debug)]
136struct CheckpointState {
137 busy: i64,
138 log_frames: i64,
139 checkpointed_frames: i64,
140}
141
142struct DatabaseStats {
143 page_count: i64,
144 freelist_count: i64,
145}