use crate::{Result, Error};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainRequest {
pub query: String,
pub mode: String, pub format: String, pub options: ExplainOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainOptions {
#[serde(default)]
pub include_transaction: bool,
#[serde(default)]
pub include_distributed: bool,
#[serde(default)]
pub include_optimization: bool,
#[serde(default)]
pub enable_what_if: bool,
}
impl Default for ExplainOptions {
fn default() -> Self {
Self {
include_transaction: false,
include_distributed: false,
include_optimization: true,
enable_what_if: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainResponse {
pub request_id: String,
pub query: String,
pub plan: serde_json::Value,
pub transaction_analysis: Option<serde_json::Value>,
pub distributed_analysis: Option<serde_json::Value>,
pub optimization_suggestions: Option<serde_json::Value>,
pub execution_time_ms: f64,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkAnalysisRequest {
pub queries: Vec<String>,
pub mode: String,
pub output_format: BulkOutputFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BulkOutputFormat {
Individual,
Summary,
Report,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkAnalysisResponse {
pub total_queries: usize,
pub successful_analyses: usize,
pub failed_analyses: usize,
pub results: Vec<QueryAnalysisResult>,
pub summary: BulkSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryAnalysisResult {
pub query_index: usize,
pub query: String,
pub status: AnalysisStatus,
pub plan_cost: Option<f64>,
pub estimated_time_ms: Option<f64>,
pub issues: Vec<String>,
pub suggestions: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnalysisStatus {
Success,
Warning,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkSummary {
pub total_cost: f64,
pub total_time_ms: f64,
pub avg_cost: f64,
pub avg_time_ms: f64,
pub most_expensive_query: usize,
pub optimization_opportunities: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScheduledExplainJob {
pub job_id: String,
pub name: String,
pub query_pattern: String,
pub schedule: CronSchedule,
pub notification_config: NotificationConfig,
pub retention_days: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CronSchedule {
pub expression: String,
pub timezone: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationConfig {
pub enabled: bool,
pub email: Option<String>,
pub webhook: Option<String>,
pub alert_on_regression: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportRequest {
pub report_type: ReportType,
pub time_range: TimeRange,
pub include_graphs: bool,
pub format: ReportFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReportType {
PerformanceSummary,
OptimizationOpportunities,
RegressionAnalysis,
ComparisonReport,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRange {
pub start: u64,
pub end: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReportFormat {
PDF,
HTML,
JSON,
Markdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneratedReport {
pub report_id: String,
pub report_type: ReportType,
pub generated_at: u64,
pub content: String,
pub format: ReportFormat,
pub download_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitoringMetrics {
pub avg_query_cost: f64,
pub p50_time_ms: f64,
pub p95_time_ms: f64,
pub p99_time_ms: f64,
pub regression_count: usize,
pub optimization_count: usize,
pub timestamp: u64,
}
pub struct ExplainApiService {
enable_caching: bool,
max_bulk_queries: usize,
cache: HashMap<String, ExplainResponse>,
}
impl ExplainApiService {
pub fn new() -> Self {
Self {
enable_caching: true,
max_bulk_queries: 100,
cache: HashMap::new(),
}
}
pub fn with_cache(mut self, enable: bool) -> Self {
self.enable_caching = enable;
self
}
pub fn explain_query(&mut self, request: ExplainRequest) -> Result<ExplainResponse> {
let start = std::time::Instant::now();
let cache_key = format!("{}:{}:{}", request.query, request.mode, request.format);
if self.enable_caching {
if let Some(cached) = self.cache.get(&cache_key) {
return Ok(cached.clone());
}
}
let request_id = format!("req_{}", std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::query_execution(format!("System time error: {}", e)))?
.as_micros());
let plan = serde_json::json!({
"node_type": "Scan",
"operation": "Sequential Scan on users",
"cost": 1000.0,
"rows": 10000,
"details": {
"table": "users",
"filter": "id > 100"
}
});
let transaction_analysis = if request.options.include_transaction {
Some(serde_json::json!({
"isolation_level": "READ COMMITTED",
"locks": ["users: SHARED READ"],
"deadlock_risk": "LOW"
}))
} else {
None
};
let distributed_analysis = if request.options.include_distributed {
Some(serde_json::json!({
"cluster_nodes": 4,
"partitions": 16,
"network_cost_ms": 45.0
}))
} else {
None
};
let optimization_suggestions = if request.options.include_optimization {
Some(serde_json::json!({
"query_rewrites": 3,
"materialized_views": 1,
"index_recommendations": 2
}))
} else {
None
};
let response = ExplainResponse {
request_id,
query: request.query.clone(),
plan,
transaction_analysis,
distributed_analysis,
optimization_suggestions,
execution_time_ms: start.elapsed().as_secs_f64() * 1000.0,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::query_execution(format!("System time error: {}", e)))?
.as_secs(),
};
if self.enable_caching {
self.cache.insert(cache_key, response.clone());
}
Ok(response)
}
pub fn bulk_analysis(&self, request: BulkAnalysisRequest) -> Result<BulkAnalysisResponse> {
if request.queries.len() > self.max_bulk_queries {
return Err(Error::QueryExecution(format!(
"Too many queries: {} (max: {})",
request.queries.len(),
self.max_bulk_queries
)));
}
let mut results = Vec::new();
let mut successful = 0;
let mut failed = 0;
let mut total_cost = 0.0;
let mut total_time = 0.0;
let mut max_cost_idx = 0;
let mut max_cost = 0.0;
for (idx, query) in request.queries.iter().enumerate() {
let cost = 500.0 + (idx as f64 * 100.0);
let time = 50.0 + (idx as f64 * 10.0);
let has_issues = idx % 3 == 0;
let status = if has_issues {
AnalysisStatus::Warning
} else {
AnalysisStatus::Success
};
if status == AnalysisStatus::Success {
successful += 1;
} else {
failed += 1;
}
total_cost += cost;
total_time += time;
if cost > max_cost {
max_cost = cost;
max_cost_idx = idx;
}
results.push(QueryAnalysisResult {
query_index: idx,
query: query.clone(),
status,
plan_cost: Some(cost),
estimated_time_ms: Some(time),
issues: if has_issues {
vec!["Expensive query detected".to_string()]
} else {
vec![]
},
suggestions: vec!["Add index on id column".to_string()],
});
}
let summary = BulkSummary {
total_cost,
total_time_ms: total_time,
avg_cost: total_cost / request.queries.len() as f64,
avg_time_ms: total_time / request.queries.len() as f64,
most_expensive_query: max_cost_idx,
optimization_opportunities: results.iter().filter(|r| !r.suggestions.is_empty()).count(),
};
Ok(BulkAnalysisResponse {
total_queries: request.queries.len(),
successful_analyses: successful,
failed_analyses: failed,
results,
summary,
})
}
pub fn create_scheduled_job(&self, job: ScheduledExplainJob) -> Result<String> {
Ok(job.job_id.clone())
}
pub fn generate_report(&self, request: ReportRequest) -> Result<GeneratedReport> {
let report_id = format!("report_{}", std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::query_execution(format!("System time error: {}", e)))?
.as_secs());
let content = match request.report_type {
ReportType::PerformanceSummary => {
self.generate_performance_summary(&request.time_range, request.format)
}
ReportType::OptimizationOpportunities => {
self.generate_optimization_report(&request.time_range, request.format)
}
ReportType::RegressionAnalysis => {
self.generate_regression_report(&request.time_range, request.format)
}
ReportType::ComparisonReport => {
self.generate_comparison_report(&request.time_range, request.format)
}
};
Ok(GeneratedReport {
report_id: report_id.clone(),
report_type: request.report_type,
generated_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::query_execution(format!("System time error: {}", e)))?
.as_secs(),
content,
format: request.format,
download_url: Some(format!("/api/reports/{}/download", report_id)),
})
}
fn generate_performance_summary(&self, time_range: &TimeRange, format: ReportFormat) -> String {
match format {
ReportFormat::JSON => serde_json::json!({
"summary": {
"time_range": {
"start": time_range.start,
"end": time_range.end
},
"total_queries": 1234,
"avg_cost": 567.89,
"avg_time_ms": 125.5,
"p95_time_ms": 450.0,
"p99_time_ms": 890.0
}
}).to_string(),
ReportFormat::Markdown => {
format!(
"# Performance Summary\n\n\
- Time Range: {} - {}\n\
- Total Queries: 1234\n\
- Average Cost: 567.89\n\
- Average Time: 125.5ms\n\
- P95 Time: 450.0ms\n\
- P99 Time: 890.0ms\n",
time_range.start, time_range.end
)
}
_ => "Performance Summary Report".to_string(),
}
}
fn generate_optimization_report(&self, _time_range: &TimeRange, _format: ReportFormat) -> String {
"# Optimization Opportunities\n\n- Add indexes: 15 tables\n- Rewrite queries: 23 queries\n- Materialized views: 5 candidates".to_string()
}
fn generate_regression_report(&self, _time_range: &TimeRange, _format: ReportFormat) -> String {
"# Regression Analysis\n\n- Detected Regressions: 3\n- Average Slowdown: 25%".to_string()
}
fn generate_comparison_report(&self, _time_range: &TimeRange, _format: ReportFormat) -> String {
"# Before/After Comparison\n\n- Total Improvement: 45%\n- Cost Reduction: 60%".to_string()
}
pub fn get_monitoring_metrics(&self) -> Result<MonitoringMetrics> {
Ok(MonitoringMetrics {
avg_query_cost: 567.89,
p50_time_ms: 100.0,
p95_time_ms: 450.0,
p99_time_ms: 890.0,
regression_count: 3,
optimization_count: 15,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::query_execution(format!("System time error: {}", e)))?
.as_secs(),
})
}
pub fn graphql_query(&mut self, query: &str) -> Result<serde_json::Value> {
if query.contains("explainQuery") {
let response = self.explain_query(ExplainRequest {
query: "SELECT * FROM users".to_string(),
mode: "standard".to_string(),
format: "json".to_string(),
options: ExplainOptions::default(),
})?;
Ok(serde_json::json!({
"data": {
"explainQuery": {
"requestId": response.request_id,
"cost": 1000.0,
"estimatedTime": 250.0
}
}
}))
} else {
Err(Error::QueryExecution("Unknown GraphQL query".to_string()))
}
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
}
impl Default for ExplainApiService {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_explain_query_rest_api() {
let mut service = ExplainApiService::new();
let request = ExplainRequest {
query: "SELECT * FROM users".to_string(),
mode: "standard".to_string(),
format: "json".to_string(),
options: ExplainOptions::default(),
};
let response = service.explain_query(request).unwrap();
assert!(!response.request_id.is_empty());
assert!(response.execution_time_ms >= 0.0);
}
#[test]
fn test_caching() {
let mut service = ExplainApiService::new().with_cache(true);
let request = ExplainRequest {
query: "SELECT * FROM users".to_string(),
mode: "standard".to_string(),
format: "json".to_string(),
options: ExplainOptions::default(),
};
let response1 = service.explain_query(request.clone()).unwrap();
let response2 = service.explain_query(request).unwrap();
assert_eq!(response1.request_id, response2.request_id);
}
#[test]
fn test_bulk_analysis() {
let service = ExplainApiService::new();
let request = BulkAnalysisRequest {
queries: vec![
"SELECT * FROM users".to_string(),
"SELECT * FROM orders".to_string(),
"SELECT * FROM products".to_string(),
],
mode: "standard".to_string(),
output_format: BulkOutputFormat::Summary,
};
let response = service.bulk_analysis(request).unwrap();
assert_eq!(response.total_queries, 3);
assert_eq!(response.results.len(), 3);
assert!(response.summary.avg_cost > 0.0);
}
#[test]
fn test_scheduled_job() {
let service = ExplainApiService::new();
let job = ScheduledExplainJob {
job_id: "job_123".to_string(),
name: "Daily analysis".to_string(),
query_pattern: "SELECT%".to_string(),
schedule: CronSchedule {
expression: "0 2 * * *".to_string(),
timezone: "UTC".to_string(),
},
notification_config: NotificationConfig {
enabled: true,
email: Some("admin@example.com".to_string()),
webhook: None,
alert_on_regression: true,
},
retention_days: 30,
};
let job_id = service.create_scheduled_job(job).unwrap();
assert_eq!(job_id, "job_123");
}
#[test]
fn test_generate_report() {
let service = ExplainApiService::new();
let request = ReportRequest {
report_type: ReportType::PerformanceSummary,
time_range: TimeRange {
start: 1000000,
end: 2000000,
},
include_graphs: true,
format: ReportFormat::JSON,
};
let report = service.generate_report(request).unwrap();
assert!(!report.report_id.is_empty());
assert!(!report.content.is_empty());
}
#[test]
fn test_monitoring_metrics() {
let service = ExplainApiService::new();
let metrics = service.get_monitoring_metrics().unwrap();
assert!(metrics.avg_query_cost > 0.0);
assert!(metrics.p95_time_ms > 0.0);
}
#[test]
fn test_graphql_query() {
let mut service = ExplainApiService::new();
let result = service.graphql_query("{ explainQuery { cost } }").unwrap();
assert!(result.get("data").is_some());
}
#[test]
fn test_clear_cache() {
let mut service = ExplainApiService::new();
let request = ExplainRequest {
query: "SELECT * FROM users".to_string(),
mode: "standard".to_string(),
format: "json".to_string(),
options: ExplainOptions::default(),
};
service.explain_query(request).unwrap();
assert!(!service.cache.is_empty());
service.clear_cache();
assert!(service.cache.is_empty());
}
}