use crate::persist::global::{DatabaseConfig, DatabaseService};
use crate::prelude::*;
use chrono::{DateTime, Utc};
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use rusqlite::params;
use sha2::{Digest, Sha256};
use std::io::Write;
use tokio::fs as async_fs;
use tokio::task::spawn_blocking;
pub type DatabasePool = crate::persist::global::ConnectionPool;
pub fn initialize_database(config: &DatabaseConfig) -> Result<DatabaseService> {
DatabaseService::new(&config)
}
pub fn initialize_database_in_memory() -> Result<DatabaseService> {
DatabaseService::new_in_memory()
}
pub async fn initialize_database_async(db_path: &Path) -> Result<DatabaseService> {
let config = DatabaseConfig::new(db_path);
let service = spawn_blocking(move || initialize_database(&config)).await??;
Ok(service)
}
pub fn run_migrations(service: &DatabaseService) -> Result<()> {
service.run_migrations()
}
pub fn health_check_sync(service: &DatabaseService) -> Result<DatabaseHealth> {
let start_time = std::time::Instant::now();
let conn = service.get_connection()?;
let _: i32 = conn.query_row("SELECT 1", [], |row| row.get(0))?;
let response_time = start_time.elapsed();
let project_count = count_table_sync(&conn, "projects").unwrap_or(0);
let feature_count = count_table_sync(&conn, "features").unwrap_or(0);
let task_count = count_table_sync(&conn, "tasks").unwrap_or(0);
let session_count = count_table_sync(&conn, "work_sessions").unwrap_or(0);
let note_count = count_table_sync(&conn, "notes").unwrap_or(0);
let mut fk_check_stmt = conn.prepare("PRAGMA foreign_key_check")?;
let column_count = fk_check_stmt.column_count();
let fk_rows: Vec<Vec<rusqlite::types::Value>> = fk_check_stmt
.query_map([], |row| {
let mut values = Vec::new();
for i in 0..column_count {
values.push(row.get(i)?);
}
Ok(values)
})?
.filter_map(|r| r.ok())
.collect();
let fk_violations_count = fk_rows.len();
let schema_version = get_schema_version_sync(&conn)?;
Ok(DatabaseHealth {
connected: true,
response_time_ms: response_time.as_millis() as u64,
project_count: project_count as i64,
feature_count: feature_count as i64,
task_count: task_count as i64,
session_count: session_count as i64,
note_count: note_count as i64,
foreign_key_violations: fk_violations_count,
schema_version,
})
}
pub async fn health_check(service: &DatabaseService) -> Result<DatabaseHealth> {
let service = service.clone();
spawn_blocking(move || health_check_sync(&service)).await?
}
fn count_table_sync(conn: &rusqlite::Connection, table: &str) -> Result<usize> {
let count: i64 = conn.query_row(&format!("SELECT COUNT(*) FROM {}", table), [], |row| {
row.get(0)
})?;
Ok(count as usize)
}
fn get_schema_version_sync(conn: &rusqlite::Connection) -> Result<i32> {
conn.execute(
"CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
)",
[],
)?;
let result: Option<i32> = conn
.query_row("SELECT MAX(version) FROM schema_version", [], |row| {
row.get(0)
})
.ok();
Ok(result.unwrap_or(0))
}
pub fn analyze_index_performance_sync(service: &DatabaseService) -> Result<IndexPerformanceReport> {
let conn = service.get_connection()?;
let index_stats = fetch_index_stats(&conn);
let query_plans = analyze_query_plans(&conn);
let total_indexes = index_stats.len();
let large_indexes = index_stats.iter().filter(|s| s.payload > 1024).count();
let avg_depth = if !index_stats.is_empty() {
index_stats.iter().map(|s| s.depth).sum::<i32>() as f64 / index_stats.len() as f64
} else {
0.0
};
let optimization_suggestions = generate_optimization_suggestions(&query_plans);
Ok(IndexPerformanceReport {
total_indexes,
index_stats,
query_plans,
large_indexes,
average_index_depth: avg_depth,
optimization_suggestions,
})
}
pub async fn analyze_index_performance(
service: &DatabaseService,
) -> Result<IndexPerformanceReport> {
let service = service.clone();
spawn_blocking(move || analyze_index_performance_sync(&service)).await?
}
fn fetch_index_stats(conn: &rusqlite::Connection) -> Vec<IndexUsageStats> {
let mut stats = Vec::new();
if let Ok(mut stmt) = conn.prepare(
"SELECT name, tbl, rootpage, ncell, payload, unused, mx_payload, pgsize, depth
FROM dbstat WHERE name LIKE 'idx_%' ORDER BY payload DESC",
) {
let rows = stmt.query_map([], |row| {
Ok(IndexUsageStats {
name: row.get(0)?,
tbl: row.get(1)?,
rootpage: row.get(2)?,
ncell: row.get(3)?,
payload: row.get(4)?,
unused: row.get(5)?,
mx_payload: row.get(6)?,
pgsize: row.get(7)?,
depth: row.get(8)?,
})
});
if let Ok(rows) = rows {
for row in rows.flatten() {
stats.push(row);
}
}
}
stats
}
fn analyze_query_plans(conn: &rusqlite::Connection) -> Vec<QueryPlanAnalysis> {
let mut plans = Vec::new();
let feature_queries = vec![
(
"SELECT * FROM features WHERE project_id = ?",
"Feature lookup by project",
),
(
"SELECT * FROM features WHERE state = ?",
"Feature lookup by state",
),
(
"SELECT * FROM features WHERE project_id = ? AND state = ?",
"Feature lookup by project and state",
),
(
"SELECT * FROM features ORDER BY updated_at DESC LIMIT 10",
"Recent features",
),
];
for (query, description) in feature_queries {
if let Ok(plan_lines) = get_explain_plan(conn, query) {
plans.push(QueryPlanAnalysis {
query: query.to_string(),
description: description.to_string(),
uses_index: plan_lines.iter().any(|line| line.contains("USING INDEX")),
plan_summary: plan_lines.join(" | "),
});
}
}
let task_queries = vec![
(
"SELECT * FROM tasks WHERE project_id = ?",
"Task lookup by project",
),
(
"SELECT * FROM tasks WHERE status = ?",
"Task lookup by status",
),
(
"SELECT * FROM tasks WHERE status = ? AND priority = ?",
"Task lookup by status and priority",
),
(
"SELECT * FROM tasks ORDER BY created_at DESC LIMIT 20",
"Recent tasks",
),
];
for (query, description) in task_queries {
if let Ok(plan_lines) = get_explain_plan(conn, query) {
plans.push(QueryPlanAnalysis {
query: query.to_string(),
description: description.to_string(),
uses_index: plan_lines.iter().any(|line| line.contains("USING INDEX")),
plan_summary: plan_lines.join(" | "),
});
}
}
plans
}
fn get_explain_plan(conn: &rusqlite::Connection, query: &str) -> Result<Vec<String>> {
let explain_query = format!("EXPLAIN QUERY PLAN {}", query);
let mut stmt = conn.prepare(&explain_query)?;
let rows = stmt.query_map([], |row| {
let detail: String = row.get(0)?;
Ok(detail)
})?;
let mut lines = Vec::new();
for row in rows {
if let Ok(line) = row {
lines.push(line);
}
}
Ok(lines)
}
fn generate_optimization_suggestions(query_plans: &[QueryPlanAnalysis]) -> Vec<String> {
let mut suggestions = Vec::new();
let queries_without_index = query_plans.iter().filter(|p| !p.uses_index).count();
if queries_without_index > 0 {
suggestions.push(format!(
"{} queries are not using indexes - consider adding covering indexes",
queries_without_index
));
}
if query_plans.iter().any(|p| p.plan_summary.contains("SCAN")) {
suggestions
.push("Some queries are performing table scans - review indexing strategy".to_string());
}
if suggestions.is_empty() {
suggestions.push("Index strategy appears optimal for analyzed queries".to_string());
}
suggestions
}
pub fn optimize_database_sync(service: &DatabaseService) -> Result<()> {
let conn = service.get_connection()?;
conn.execute("ANALYZE", [])?;
tracing::debug!("Updated index statistics");
conn.execute("VACUUM", [])?;
tracing::debug!("Database vacuumed and optimized");
conn.execute("ANALYZE", [])?;
tracing::debug!("Re-analyzed index statistics after vacuum");
Ok(())
}
pub async fn optimize_database(service: &DatabaseService) -> Result<()> {
let service = service.clone();
spawn_blocking(move || optimize_database_sync(&service)).await?
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupConfig {
pub backup_directory: PathBuf,
pub max_backups: usize,
pub compression_enabled: bool,
pub automatic_cleanup: bool,
}
impl Default for BackupConfig {
fn default() -> Self {
Self {
backup_directory: PathBuf::from(".wsb/backups"),
max_backups: 10,
compression_enabled: true,
automatic_cleanup: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupMetadata {
pub backup_id: String,
pub timestamp: DateTime<Utc>,
pub database_path: PathBuf,
pub backup_path: PathBuf,
pub size_bytes: u64,
pub checksum: String,
pub compression: bool,
}
pub async fn create_backup(
service: &DatabaseService,
db_path: &Path,
config: &BackupConfig,
) -> Result<BackupMetadata> {
let timestamp = Utc::now();
let backup_id = format!("backup_{}", timestamp.format("%Y%m%d_%H%M%S"));
async_fs::create_dir_all(&config.backup_directory).await?;
let backup_filename = if config.compression_enabled {
format!("{}.db.gz", backup_id)
} else {
format!("{}.db", backup_id)
};
let backup_path = config.backup_directory.join(&backup_filename);
let backup_temp_path = config
.backup_directory
.join(format!("{}_temp.db", backup_id));
let vacuum_query = format!("VACUUM INTO '{}'", backup_temp_path.display());
let service_clone = service.clone();
spawn_blocking(move || {
let conn = service_clone.get_connection()?;
conn.execute(&vacuum_query, [])?;
Ok::<(), anyhow::Error>(())
})
.await??;
let size_bytes = if config.compression_enabled {
let compressed_data = compress_file_sync(&backup_temp_path)?;
async_fs::write(&backup_path, compressed_data).await?;
async_fs::remove_file(&backup_temp_path).await?;
async_fs::metadata(&backup_path).await?.len()
} else {
async_fs::rename(&backup_temp_path, &backup_path).await?;
async_fs::metadata(&backup_path).await?.len()
};
let checksum = calculate_file_checksum_sync(&backup_path)?;
let metadata = BackupMetadata {
backup_id,
timestamp,
database_path: db_path.to_path_buf(),
backup_path: backup_path.clone(),
size_bytes,
checksum,
compression: config.compression_enabled,
};
save_backup_metadata(&metadata, config).await?;
if config.automatic_cleanup {
cleanup_old_backups(config).await?;
}
tracing::info!(
"Created database backup: {} ({} bytes)",
backup_path.display(),
size_bytes
);
Ok(metadata)
}
pub async fn restore_backup(backup_metadata: &BackupMetadata, target_path: &Path) -> Result<()> {
if !backup_metadata.backup_path.exists() {
return Err(anyhow::anyhow!(
"Backup file not found: {}",
backup_metadata.backup_path.display()
));
}
let current_checksum = calculate_file_checksum(&backup_metadata.backup_path).await?;
if current_checksum != backup_metadata.checksum {
return Err(anyhow::anyhow!("Backup file corrupted: checksum mismatch"));
}
if backup_metadata.compression {
let compressed_data = async_fs::read(&backup_metadata.backup_path).await?;
let decompressed_data = decompress_data(&compressed_data).await?;
async_fs::write(target_path, decompressed_data).await?;
} else {
async_fs::copy(&backup_metadata.backup_path, target_path).await?;
}
tracing::info!(
"Restored database from backup: {}",
backup_metadata.backup_path.display()
);
Ok(())
}
pub async fn list_backups(config: &BackupConfig) -> Result<Vec<BackupMetadata>> {
let mut backups = Vec::new();
if !config.backup_directory.exists() {
return Ok(backups);
}
let mut dir_entries = async_fs::read_dir(&config.backup_directory).await?;
while let Some(entry) = dir_entries.next_entry().await? {
let path = entry.path();
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if stem.starts_with("backup_") && stem.ends_with("_metadata") {
if let Ok(metadata_content) = async_fs::read_to_string(&path).await {
if let Ok(metadata) = serde_json::from_str::<BackupMetadata>(&metadata_content)
{
backups.push(metadata);
}
}
}
}
}
backups.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
Ok(backups)
}
pub async fn cleanup_old_backups(config: &BackupConfig) -> Result<()> {
let backups = list_backups(config).await?;
if backups.len() <= config.max_backups {
return Ok(());
}
for backup in backups.iter().skip(config.max_backups) {
if backup.backup_path.exists() {
async_fs::remove_file(&backup.backup_path).await?;
}
let metadata_path = config
.backup_directory
.join(format!("{}_metadata.json", backup.backup_id));
if metadata_path.exists() {
async_fs::remove_file(&metadata_path).await?;
}
tracing::info!("Cleaned up old backup: {}", backup.backup_id);
}
Ok(())
}
async fn save_backup_metadata(metadata: &BackupMetadata, config: &BackupConfig) -> Result<()> {
let metadata_path = config
.backup_directory
.join(format!("{}_metadata.json", metadata.backup_id));
let metadata_json = serde_json::to_string_pretty(metadata)?;
async_fs::write(&metadata_path, metadata_json).await?;
Ok(())
}
fn compress_file_sync(file_path: &Path) -> Result<Vec<u8>> {
let file_data = std::fs::read(file_path)?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&file_data)?;
Ok(encoder.finish()?)
}
pub async fn compress_file(file_path: &Path) -> Result<Vec<u8>> {
let path = file_path.to_path_buf();
spawn_blocking(move || compress_file_sync(&path)).await?
}
pub async fn decompress_data(compressed_data: &[u8]) -> Result<Vec<u8>> {
let data = compressed_data.to_vec();
spawn_blocking(move || {
let mut decoder = GzDecoder::new(data.as_slice());
let mut decompressed = Vec::new();
std::io::Read::read_to_end(&mut decoder, &mut decompressed)?;
Ok(decompressed)
})
.await?
}
pub async fn calculate_file_checksum(file_path: &Path) -> Result<String> {
let path = file_path.to_path_buf();
spawn_blocking(move || calculate_file_checksum_sync(&path)).await?
}
fn calculate_file_checksum_sync(file_path: &Path) -> Result<String> {
let file_data = std::fs::read(file_path)?;
let mut hasher = Sha256::new();
hasher.update(&file_data);
Ok(format!("{:x}", hasher.finalize()))
}
pub async fn schedule_automatic_backup(
service: &DatabaseService,
db_path: &Path,
config: &BackupConfig,
) -> Result<BackupMetadata> {
tracing::info!("Creating scheduled automatic backup");
create_backup(service, db_path, config).await
}
pub async fn point_in_time_recovery(
target_time: DateTime<Utc>,
config: &BackupConfig,
) -> Result<Option<BackupMetadata>> {
let backups = list_backups(config).await?;
for backup in &backups {
if backup.timestamp <= target_time {
return Ok(Some(backup.clone()));
}
}
Ok(None)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionContinuityState {
pub session_id: String,
pub project_id: String,
pub context_snapshot: ContextSnapshot,
pub active_features: Vec<String>,
pub in_progress_tasks: Vec<String>,
pub session_focus: String,
pub conversation_context: String,
pub working_directory: String,
pub environment_state: HashMap<String, String>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextSnapshot {
pub current_phase: String,
pub recent_achievements: Vec<String>,
pub active_issues: Vec<String>,
pub next_priorities: Vec<String>,
pub context_usage_percent: f32,
pub files_modified: Vec<String>,
pub conversation_messages: Vec<ConversationMessage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationMessage {
pub role: String,
pub content: String,
pub timestamp: DateTime<Utc>,
pub tool_calls: Vec<String>,
}
pub fn save_session_continuity_sync(
service: &DatabaseService,
state: &SessionContinuityState,
) -> Result<()> {
let conn = service.get_connection()?;
let state_json = serde_json::to_string(state)?;
conn.execute(
"INSERT OR REPLACE INTO session_continuity_states
(session_id, project_id, state_data, timestamp)
VALUES (?1, ?2, ?3, ?4)",
params![
&state.session_id,
&state.project_id,
state_json,
state.timestamp.to_rfc3339()
],
)?;
tracing::debug!(
"Saved session continuity state for session {}",
state.session_id
);
Ok(())
}
pub async fn save_session_continuity(
service: &DatabaseService,
state: &SessionContinuityState,
) -> Result<()> {
let state = state.clone();
let service = service.clone();
spawn_blocking(move || save_session_continuity_sync(&service, &state)).await?
}
pub fn load_session_continuity_sync(
service: &DatabaseService,
session_id: &str,
) -> Result<Option<SessionContinuityState>> {
let conn = service.get_connection()?;
let result: rusqlite::Result<String> = conn.query_row(
"SELECT state_data FROM session_continuity_states
WHERE session_id = ?1
ORDER BY timestamp DESC
LIMIT 1",
params![session_id],
|row| row.get(0),
);
match result {
Ok(state_json) => {
let state = serde_json::from_str::<SessionContinuityState>(&state_json)?;
Ok(Some(state))
}
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e.into()),
}
}
pub async fn load_session_continuity(
service: &DatabaseService,
session_id: &str,
) -> Result<Option<SessionContinuityState>> {
let session_id = session_id.to_string();
let service = service.clone();
spawn_blocking(move || load_session_continuity_sync(&service, &session_id)).await?
}
pub fn get_latest_session_continuity_sync(
service: &DatabaseService,
project_id: &str,
) -> Result<Option<SessionContinuityState>> {
let conn = service.get_connection()?;
let result: rusqlite::Result<String> = conn.query_row(
"SELECT state_data FROM session_continuity_states
WHERE project_id = ?1
ORDER BY timestamp DESC
LIMIT 1",
params![project_id],
|row| row.get(0),
);
match result {
Ok(state_json) => {
let state = serde_json::from_str::<SessionContinuityState>(&state_json)?;
Ok(Some(state))
}
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e.into()),
}
}
pub async fn get_latest_session_continuity(
service: &DatabaseService,
project_id: &str,
) -> Result<Option<SessionContinuityState>> {
let project_id = project_id.to_string();
let service = service.clone();
spawn_blocking(move || get_latest_session_continuity_sync(&service, &project_id)).await?
}
pub fn create_context_snapshot_sync(
service: &DatabaseService,
project_id: &str,
) -> Result<ContextSnapshot> {
let conn = service.get_connection()?;
let mut stmt = conn.prepare(
"SELECT title, notes FROM tasks
WHERE project_id = ?1 AND status = 'completed'
ORDER BY updated_at DESC LIMIT 5",
)?;
let achievement_rows: Vec<(String, Option<String>)> = stmt
.query_map(params![project_id], |row| Ok((row.get(0)?, row.get(1)?)))?
.filter_map(|r| r.ok())
.collect();
let recent_achievements: Vec<String> = achievement_rows
.into_iter()
.map(|(title, notes)| match notes {
Some(n) if !n.is_empty() => format!("{}: {}", title, n),
_ => title,
})
.collect();
let mut stmt = conn.prepare(
"SELECT name, notes FROM features
WHERE project_id = ?1 AND state IN ('tests_broken', 'critical_issue')
ORDER BY updated_at DESC",
)?;
let issue_rows: Vec<(String, Option<String>)> = stmt
.query_map(params![project_id], |row| Ok((row.get(0)?, row.get(1)?)))?
.filter_map(|r| r.ok())
.collect();
let active_issues: Vec<String> = issue_rows
.into_iter()
.map(|(title, notes)| match notes {
Some(n) if !n.is_empty() => format!("{}: {}", title, n),
_ => title,
})
.collect();
let mut stmt = conn.prepare(
"SELECT title, description FROM tasks
WHERE project_id = ?1 AND status = 'pending' AND priority = 'high'
ORDER BY created_at ASC LIMIT 5",
)?;
let priority_rows: Vec<(String, Option<String>)> = stmt
.query_map(params![project_id], |row| Ok((row.get(0)?, row.get(1)?)))?
.filter_map(|r| r.ok())
.collect();
let next_priorities: Vec<String> = priority_rows
.into_iter()
.map(|(title, desc)| match desc {
Some(d) if !d.is_empty() => format!("{}: {}", title, d),
_ => title,
})
.collect();
Ok(ContextSnapshot {
current_phase: "Development in progress".to_string(),
recent_achievements,
active_issues,
next_priorities,
context_usage_percent: 0.0,
files_modified: Vec::new(),
conversation_messages: Vec::new(),
})
}
pub async fn create_context_snapshot(
service: &DatabaseService,
project_id: &str,
) -> Result<ContextSnapshot> {
let project_id = project_id.to_string();
let service = service.clone();
spawn_blocking(move || create_context_snapshot_sync(&service, &project_id)).await?
}
pub async fn transfer_session_knowledge(
service: &DatabaseService,
from_session_id: &str,
to_session_id: &str,
) -> Result<()> {
let source_state = load_session_continuity(service, from_session_id).await?;
if let Some(mut state) = source_state {
state.session_id = to_session_id.to_string();
state.timestamp = Utc::now();
let transfer_data = serde_json::json!({
"from_session": from_session_id,
"to_session": to_session_id,
"context_transferred": true,
"active_features": state.active_features,
"in_progress_tasks": state.in_progress_tasks,
"conversation_context": state.conversation_context,
"transfer_timestamp": state.timestamp
});
save_session_continuity(service, &state).await?;
let service_clone = service.clone();
let from_sid = from_session_id.to_string();
let to_sid = to_session_id.to_string();
let transfer_str = transfer_data.to_string();
let ts = Utc::now().to_rfc3339();
spawn_blocking(move || {
let conn = service_clone.get_connection()?;
conn.execute(
"INSERT INTO session_knowledge_transfers
(from_session_id, to_session_id, transfer_data, timestamp)
VALUES (?1, ?2, ?3, ?4)",
params![from_sid, to_sid, transfer_str, ts],
)?;
Ok::<(), anyhow::Error>(())
})
.await??;
tracing::info!(
"Transferred knowledge from session {} to session {}",
from_session_id,
to_session_id
);
}
Ok(())
}
pub fn initialize_continuity_tables_sync(service: &DatabaseService) -> Result<()> {
let conn = service.get_connection()?;
conn.execute(
"CREATE TABLE IF NOT EXISTS session_continuity_states (
id TEXT PRIMARY KEY DEFAULT (hex(randomblob(16))),
session_id TEXT NOT NULL,
project_id TEXT NOT NULL,
state_data TEXT NOT NULL,
timestamp TEXT NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE,
UNIQUE(session_id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS session_knowledge_transfers (
id TEXT PRIMARY KEY DEFAULT (hex(randomblob(16))),
from_session_id TEXT NOT NULL,
to_session_id TEXT NOT NULL,
transfer_data TEXT NOT NULL,
timestamp TEXT NOT NULL
)",
[],
)?;
Ok(())
}
pub async fn initialize_continuity_tables(service: &DatabaseService) -> Result<()> {
let service = service.clone();
spawn_blocking(move || initialize_continuity_tables_sync(&service)).await?
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseHealth {
pub connected: bool,
pub response_time_ms: u64,
pub project_count: i64,
pub feature_count: i64,
pub task_count: i64,
pub session_count: i64,
pub note_count: i64,
pub foreign_key_violations: usize,
pub schema_version: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexPerformanceReport {
pub total_indexes: usize,
pub index_stats: Vec<IndexUsageStats>,
pub query_plans: Vec<QueryPlanAnalysis>,
pub large_indexes: usize,
pub average_index_depth: f64,
pub optimization_suggestions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexUsageStats {
pub name: String,
pub tbl: String,
pub rootpage: i32,
pub ncell: i32,
pub payload: i32,
pub unused: i32,
pub mx_payload: i32,
pub pgsize: i32,
pub depth: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryPlanAnalysis {
pub query: String,
pub description: String,
pub uses_index: bool,
pub plan_summary: String,
}