noxu_engine/lib.rs
1#![forbid(unsafe_code)]
2// Copyright (C) 2024-2025 Greg Burd. Licensed under either of the
3// Apache License, Version 2.0 or the MIT license, at your option.
4// See LICENSE-APACHE and LICENSE-MIT at the root of this repository.
5// SPDX-License-Identifier: Apache-2.0 OR MIT
6#![allow(dead_code, clippy::type_complexity, clippy::too_many_arguments)]
7//! > **Internal component of the [`noxu`](https://crates.io/crates/noxu) database.**
8//! >
9//! > This crate is published only so the `noxu` umbrella crate can depend on it.
10//! > Use `noxu` (`noxu = "3"`) in applications; depend on this crate directly only
11//! > if you are extending the engine internals. Its API may change without a major
12//! > version bump.
13//!
14//! Engine orchestration for Noxu DB.
15//!
16//! Wires together all internal subsystems, manages daemon thread lifecycle,
17//! and coordinates environment open/close.
18//!
19//! # Overview
20//!
21//! The `noxu-engine` crate is the internal orchestration layer for Noxu DB.
22//! It brings together all the subsystems built in earlier phases:
23//!
24//! - **noxu-dbi**: EnvironmentImpl, DatabaseImpl, CursorImpl
25//! - **noxu-txn**: Transaction and lock management
26//! - **noxu-evictor**: Cache eviction
27//! - **noxu-cleaner**: Log garbage collection
28//! - **noxu-recovery**: Checkpointing and recovery
29//!
30//! # Architecture
31//!
32//! The [`Engine`] struct is the central coordination point. It:
33//! 1. Creates and owns all subsystems
34//! 2. Runs recovery on environment open
35//! 3. Starts background daemon threads (evictor, cleaner, checkpointer)
36//! 4. Coordinates orderly shutdown
37//! 5. Provides unified access to all subsystems
38//!
39//! # Usage
40//!
41//! ```rust,ignore
42//! use noxu_engine::{Engine, EngineConfig};
43//!
44//! // Open an environment
45//! let config = EngineConfig::new("/data/mydb")
46//! .cache_size(128 * 1024 * 1024)
47//! .transactional(true);
48//! let engine = Engine::open(config)?;
49//!
50//! // Use the engine...
51//! let stats = engine.get_stats();
52//! println!("Cache usage: {:.1}%", stats.cache_utilization_percent());
53//!
54//! // Explicitly close (or drop will close)
55//! engine.close()?;
56//! ```
57//!
58//! # Configuration
59//!
60//! Environment behavior is controlled via [`EngineConfig`]:
61//!
62//! - Cache size and eviction settings
63//! - Transaction and lock timeouts
64//! - Daemon thread intervals
65//! - Read-only mode
66//! - Checkpoint and cleaning thresholds
67//!
68//! # Background Daemons
69//!
70//! Three daemon threads run in the background:
71//!
72//! 1. **Evictor**: Evicts nodes from cache when memory budget is exceeded
73//! 2. **Cleaner**: Garbage collects log files when utilization is low
74//! 3. **Checkpointer**: Flushes dirty nodes to bound recovery time
75//!
76//! All daemons can be individually enabled/disabled via configuration.
77//!
78//! # Statistics
79//!
80//! [`EnvironmentStats`] provides a unified view of all subsystem statistics:
81//!
82//! - Cache usage and eviction metrics
83//! - Cleaning progress and file statistics
84//! - Checkpoint frequency and flush counts
85//! - Database and transaction counts
86
87pub mod daemon_manager;
88pub mod engine;
89pub mod engine_config;
90pub mod env_stats;
91pub mod error;
92pub mod verify;
93
94// Re-export main types at crate root
95pub use daemon_manager::DaemonManager;
96pub use engine::Engine;
97pub use engine_config::EngineConfig;
98pub use env_stats::EnvironmentStats;
99pub use error::{EngineError, Result};
100pub use verify::{
101 VerifyConfig, VerifyError, VerifyResult, verify_database,
102 verify_database_impl, verify_environment, verify_tree,
103};
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use tempfile::TempDir;
109
110 #[test]
111 fn test_crate_public_api() {
112 // Verify all main types are re-exported
113 let _: EngineConfig;
114 let _: Engine;
115 let _: EnvironmentStats;
116 let _: EngineError;
117 let _: Result<()>;
118 }
119
120 #[test]
121 fn test_basic_engine_lifecycle() {
122 let dir = TempDir::new().unwrap();
123 let config = EngineConfig::new(dir.path())
124 .cache_size(10 * 1024 * 1024)
125 .evictor_wakeup_interval_ms(100)
126 .cleaner_wakeup_interval_ms(100)
127 .checkpointer_wakeup_interval_ms(100);
128
129 let engine = Engine::open(config).unwrap();
130 assert!(engine.is_open());
131
132 let stats = engine.get_stats();
133 assert_eq!(stats.cache_size, 10 * 1024 * 1024);
134
135 engine.close().unwrap();
136 assert!(!engine.is_open());
137 }
138
139 #[test]
140 fn test_config_validation() {
141 let dir = TempDir::new().unwrap();
142
143 // Valid config
144 let config = EngineConfig::new(dir.path());
145 assert!(config.validate().is_ok());
146
147 // Invalid: cache too small
148 let config = EngineConfig::new(dir.path()).cache_size(1024);
149 assert!(config.validate().is_err());
150
151 // Invalid: zero lock tables
152 let config = EngineConfig::new(dir.path()).lock_table_count(0);
153 assert!(config.validate().is_err());
154 }
155
156 #[test]
157 fn test_readonly_mode() {
158 let dir = TempDir::new().unwrap();
159 let config = EngineConfig::new(dir.path())
160 .read_only(true)
161 .cleaner_enabled(false)
162 .checkpointer_enabled(false);
163
164 let engine = Engine::open(config).unwrap();
165 assert!(engine.get_config().read_only);
166
167 // Write operations should fail
168 assert!(engine.checkpoint("test").is_err());
169 assert!(engine.clean(5).is_err());
170 }
171
172 #[test]
173 fn test_subsystem_access() {
174 let dir = TempDir::new().unwrap();
175 let config = EngineConfig::new(dir.path()).cache_size(10 * 1024 * 1024);
176
177 let engine = Engine::open(config).unwrap();
178
179 // Verify subsystem access
180 let env_impl = engine.get_env_impl();
181 assert!(env_impl.lock().is_open());
182
183 let evictor = engine.get_evictor();
184 let _ = evictor.get_stats();
185
186 let cleaner = engine.get_cleaner();
187 let _ = cleaner.get_stats();
188
189 let checkpointer = engine.get_checkpointer();
190 let _ = checkpointer.get_stats();
191 }
192}