sqlite_graphrag/constants/storage.rs
1//! SQLite pragmas, busy-retry policy and schema versions.
2//!
3//! Split out of the former single-file `constants.rs` in v1.2.5;
4//! every item is re-exported by the parent module, so `crate::constants::X`
5//! resolves exactly as before.
6
7/// Maximum attempts when a statement returns `SQLITE_BUSY`.
8pub const MAX_SQLITE_BUSY_RETRIES: u32 = 5;
9
10/// Base delay in milliseconds for the first SQLITE_BUSY retry.
11///
12/// Each subsequent attempt doubles the delay (exponential backoff):
13/// 300 ms → 600 ms → 1200 ms → 2400 ms → 4800 ms (≈ 9.3 s total).
14pub const SQLITE_BUSY_BASE_DELAY_MS: u64 = 300;
15
16/// Ceiling on ONE busy-retry sleep, in milliseconds.
17///
18/// Doubling without a ceiling turns two configuration knobs into an unbounded
19/// wait, and both are operator-settable: `db.busy_retries` and
20/// `db.busy_base_delay_ms`. Measured on a workstation carrying `12` and `600`,
21/// the twelfth attempt alone sleeps past twenty minutes and the full schedule
22/// costs roughly half an hour — for a single contended statement. That is not a
23/// tuning choice anyone made; it is what exponential growth does to a knob whose
24/// range nobody bounded.
25///
26/// Five seconds matches [`BUSY_TIMEOUT_MILLIS`], so the longest a retry waits is
27/// the same order as the lock timeout SQLite itself applies. Attempts are still
28/// capped by `db.busy_retries`; only the growth of each sleep stops here.
29pub const SQLITE_BUSY_MAX_DELAY_MS: u64 = 5_000;
30
31/// Query timeout applied to statements in milliseconds.
32pub const QUERY_TIMEOUT_MILLIS: u64 = 5_000;
33
34/// `PRAGMA busy_timeout` value applied on every connection.
35pub const BUSY_TIMEOUT_MILLIS: i32 = 5_000;
36
37/// `PRAGMA cache_size` value in kibibytes (negative means KiB).
38pub const CACHE_SIZE_KB: i32 = -64_000;
39
40/// `PRAGMA mmap_size` value in bytes applied to each connection.
41pub const MMAP_SIZE_BYTES: i64 = 268_435_456;
42
43/// `PRAGMA wal_autocheckpoint` threshold in pages.
44pub const WAL_AUTOCHECKPOINT_PAGES: i32 = 1_000;
45
46/// Canonical value of `PRAGMA user_version` written after migrations.
47///
48/// **Why 50 instead of `CURRENT_SCHEMA_VERSION` (15)?**
49/// `user_version` is a 32-bit integer that SQLite reserves for application use.
50/// We deliberately set it to a project-specific marker (50 = decimal) so external
51/// inspection tools (`sqlite3 db.sqlite "PRAGMA user_version"`, the `file` command,
52/// SQLite browser GUIs) can distinguish a sqlite-graphrag database from a generic
53/// SQLite file at a glance. The application-level schema version (15, matching
54/// `CURRENT_SCHEMA_VERSION`) is stored in the `schema_meta` table and exposed via
55/// `health --json`/`stats --json`. Bumping migrations does NOT change this constant.
56/// Refinery uses its own `refinery_schema_history` table for migration bookkeeping.
57pub const SCHEMA_USER_VERSION: i64 = 50;
58
59/// Current schema version, equal to the highest migration number in `migrations/Vnnn__*.sql`.
60///
61/// Added in v1.0.27 as a runtime and test sanity check.
62/// Must be bumped in sync with new Refinery migrations; the unit test
63/// `schema_version_matches_migrations_count` validates this automatically.
64pub const CURRENT_SCHEMA_VERSION: u32 = 17;
65
66/// Pause, in milliseconds, between `sqlite3_backup_step` retries after a
67/// transient `Busy`/`Locked`.
68///
69/// The backup loop is already bounded by the caller's own deadline; this value
70/// only stops the retry from becoming a busy-spin. Coordination wait, so it
71/// takes no XDG key.
72pub const BACKUP_BUSY_RETRY_DELAY_MS: u64 = 50;
73
74#[cfg(test)]
75mod tests_schema_version {
76 use super::CURRENT_SCHEMA_VERSION;
77
78 #[test]
79 fn schema_version_matches_migrations_count() {
80 let manifest_dir = env!("CARGO_MANIFEST_DIR");
81 let migrations_dir = std::path::Path::new(manifest_dir).join("migrations");
82 let count = std::fs::read_dir(&migrations_dir)
83 .expect("migrations directory must exist")
84 .filter_map(|entry| entry.ok())
85 .filter(|entry| entry.file_name().to_string_lossy().starts_with('V'))
86 .count() as u32;
87 assert_eq!(
88 CURRENT_SCHEMA_VERSION, count,
89 "CURRENT_SCHEMA_VERSION ({CURRENT_SCHEMA_VERSION}) must equal the number of V*.sql migrations ({count})"
90 );
91 }
92}