use crate::types::explain::ExplainResponse;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InsightSeverity {
Warning,
Info,
}
#[derive(Debug, Clone, PartialEq)]
pub struct QueryInsight<'a> {
pub severity: InsightSeverity,
pub message: String,
pub operation_type: &'a str,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct QueryInsights<'a> {
pub evaluated_plans: usize,
pub insights: Vec<QueryInsight<'a>>,
pub lowest_cost: f64,
pub best_operation_type: Option<&'a str>,
}
#[must_use]
pub fn analyze_query_plan(response: &ExplainResponse) -> QueryInsights<'_> {
let mut insights = QueryInsights {
insights: Vec::with_capacity(response.plans.len() * 2), evaluated_plans: response.plans.len(),
lowest_cost: f64::MAX,
best_operation_type: None,
};
if response.plans.is_empty() {
return insights;
}
for plan in &response.plans {
if plan.relative_cost < insights.lowest_cost {
insights.lowest_cost = plan.relative_cost;
insights.best_operation_type = Some(&plan.leading_operation_type);
}
if plan
.leading_operation_type
.eq_ignore_ascii_case("TableScan")
{
insights.insights.push(QueryInsight {
severity: InsightSeverity::Warning,
message: "Full table scan detected. This query will not scale well. Consider adding filters on indexed fields.".to_string(),
operation_type: &plan.leading_operation_type,
});
}
if plan.relative_cost > 1.0 {
insights.insights.push(QueryInsight {
severity: InsightSeverity::Warning,
message: format!("High relative cost detected ({:.2}). The query optimizer is unlikely to use this path efficiently.", plan.relative_cost),
operation_type: &plan.leading_operation_type,
});
}
for note in &plan.notes {
insights.insights.push(QueryInsight {
severity: InsightSeverity::Info,
message: format!(
"Optimizer Note on {}: {}",
note.table_enum_or_id, note.description
),
operation_type: &plan.leading_operation_type,
});
}
if plan.sobject_cardinality > 0 {
let selectivity = (plan.cardinality as f64) / (plan.sobject_cardinality as f64);
if selectivity > 0.3
&& !plan
.leading_operation_type
.eq_ignore_ascii_case("TableScan")
{
insights.insights.push(QueryInsight {
severity: InsightSeverity::Warning,
message: format!("Query lacks selectivity (pulling {:.0}% of table). Indexes may be ignored by the optimizer in favor of a TableScan.", selectivity * 100.0),
operation_type: &plan.leading_operation_type,
});
}
}
}
insights
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::explain::{PlanNote, QueryPlan};
fn create_plan(
op_type: &str,
cost: f64,
cardinality: u64,
table_size: u64,
notes: Vec<PlanNote>,
) -> QueryPlan {
QueryPlan {
cardinality,
fields: vec![],
leading_operation_type: op_type.to_string(),
notes,
relative_cost: cost,
sobject_cardinality: table_size,
sobject_type: "Account".to_string(),
}
}
#[test]
fn test_analyze_query_plan_empty() {
let response = ExplainResponse { plans: vec![] };
let insights = analyze_query_plan(&response);
assert_eq!(insights.evaluated_plans, 0);
assert!(insights.insights.is_empty());
assert_eq!(insights.best_operation_type, None);
}
#[test]
fn test_analyze_query_plan_table_scan() {
let response = ExplainResponse {
plans: vec![create_plan("TableScan", 1.5, 500, 1000, vec![])],
};
let insights = analyze_query_plan(&response);
assert_eq!(insights.evaluated_plans, 1);
assert_eq!(insights.best_operation_type, Some("TableScan"));
assert!((insights.lowest_cost - 1.5).abs() < f64::EPSILON);
assert_eq!(insights.insights.len(), 2);
let warnings: Vec<_> = insights.insights.iter().map(|i| &i.message).collect();
assert!(warnings.iter().any(|msg| msg.contains("Full table scan")));
assert!(
warnings
.iter()
.any(|msg| msg.contains("High relative cost"))
);
}
#[test]
fn test_analyze_query_plan_index_scan_optimal() {
let response = ExplainResponse {
plans: vec![create_plan("IndexScan", 0.1, 10, 10000, vec![])],
};
let insights = analyze_query_plan(&response);
assert_eq!(insights.evaluated_plans, 1);
assert_eq!(insights.best_operation_type, Some("IndexScan"));
assert!((insights.lowest_cost - 0.1).abs() < f64::EPSILON);
assert!(insights.insights.is_empty()); }
#[test]
fn test_analyze_query_plan_poor_selectivity() {
let response = ExplainResponse {
plans: vec![create_plan("IndexScan", 0.8, 4000, 10000, vec![])],
};
let insights = analyze_query_plan(&response);
assert_eq!(insights.insights.len(), 1);
assert_eq!(insights.insights[0].severity, InsightSeverity::Warning);
assert!(insights.insights[0].message.contains("lacks selectivity"));
}
#[test]
fn test_analyze_query_plan_with_notes() {
let note = PlanNote {
description: "Not considered for indexing".to_string(),
fields: vec!["Name".to_string()],
table_enum_or_id: "Account".to_string(),
};
let response = ExplainResponse {
plans: vec![create_plan("TableScan", 2.0, 100, 1000, vec![note])],
};
let insights = analyze_query_plan(&response);
let infos: Vec<_> = insights
.insights
.iter()
.filter(|i| i.severity == InsightSeverity::Info)
.collect();
assert_eq!(infos.len(), 1);
assert!(infos[0].message.contains("Not considered for indexing"));
}
}