#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ToolProfile {
Query,
Dba,
Meta,
#[default]
Full,
}
impl ToolProfile {
pub fn as_str(self) -> &'static str {
match self {
Self::Query => "query",
Self::Dba => "dba",
Self::Meta => "meta",
Self::Full => "full",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"query" => Some(Self::Query),
"dba" => Some(Self::Dba),
"meta" => Some(Self::Meta),
"full" => Some(Self::Full),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolName {
ResolveTarget,
Orient,
InspectOrSearch,
SearchAllDatabases,
SearchSchema,
DescribeObject,
GetJoinPath,
SampleValues,
RunSelect,
ExplainQuery,
ListConnections,
ListDatabases,
ListSchemas,
ListObjects,
GetCurrentContext,
SwitchConnection,
GetDdl,
TableStats,
IndexUsage,
ListRunningQueries,
FindBlockingLocks,
SlowQueries,
DbHealthCheck,
GetIndexStatus,
ListExtensions,
ServerSettings,
SuggestIndexes,
FindUnusedIndexes,
BloatReport,
FindMissingFks,
ExportQuery,
ListRoles,
DbDashboard,
DeepPlanAnalysis,
SchemaDiff,
GenerateMigration,
ExecuteSql,
EditRow,
ImportData,
ApplyDdl,
CreateIndexConcurrently,
RunMaintenance,
TerminateQuery,
DiscoverTools,
AutoTuneQuery,
CheckDdlSafety,
RebuildIndex,
RefreshIndex,
RunDoctor,
SetupConnection,
SaveProfile,
TestProfile,
ExportProfile,
ImportProfile,
}
impl ToolName {
pub const PHASE2: &'static [ToolName] = &[
Self::ListConnections,
Self::ListDatabases,
Self::ListSchemas,
Self::ListObjects,
Self::GetCurrentContext,
Self::SwitchConnection,
Self::RunSelect,
Self::ExplainQuery,
Self::DiscoverTools,
Self::RunDoctor,
Self::SetupConnection,
Self::SaveProfile,
Self::TestProfile,
Self::ExportProfile,
Self::ImportProfile,
];
pub const PHASE3: &'static [ToolName] = &[
Self::ResolveTarget,
Self::Orient,
Self::InspectOrSearch,
Self::SearchAllDatabases,
Self::SearchSchema,
Self::DescribeObject,
Self::GetJoinPath,
Self::SampleValues,
Self::RebuildIndex,
Self::RefreshIndex,
];
pub const PHASE4: &'static [ToolName] = &[
Self::GetDdl,
Self::TableStats,
Self::IndexUsage,
Self::ListRunningQueries,
Self::FindBlockingLocks,
Self::SlowQueries,
Self::DbHealthCheck,
Self::GetIndexStatus,
Self::ListExtensions,
Self::ServerSettings,
Self::SuggestIndexes,
Self::FindUnusedIndexes,
Self::BloatReport,
Self::FindMissingFks,
];
pub const PHASE4B: &'static [ToolName] = &[
Self::ExportQuery,
Self::ListRoles,
Self::DbDashboard,
Self::DeepPlanAnalysis,
Self::SchemaDiff,
Self::GenerateMigration,
Self::AutoTuneQuery,
Self::CheckDdlSafety,
];
pub const PHASE9: &'static [ToolName] = &[
Self::ExecuteSql,
Self::EditRow,
Self::ImportData,
Self::ApplyDdl,
Self::CreateIndexConcurrently,
Self::RunMaintenance,
Self::TerminateQuery,
];
pub const ACTIVE: &'static [ToolName] = &[
Self::ListConnections,
Self::ListDatabases,
Self::ListSchemas,
Self::ListObjects,
Self::GetCurrentContext,
Self::SwitchConnection,
Self::RunSelect,
Self::ExplainQuery,
Self::DiscoverTools,
Self::RunDoctor,
Self::SetupConnection,
Self::SaveProfile,
Self::TestProfile,
Self::ExportProfile,
Self::ImportProfile,
Self::ResolveTarget,
Self::Orient,
Self::InspectOrSearch,
Self::SearchAllDatabases,
Self::SearchSchema,
Self::DescribeObject,
Self::GetJoinPath,
Self::SampleValues,
Self::RebuildIndex,
Self::RefreshIndex,
Self::GetDdl,
Self::TableStats,
Self::IndexUsage,
Self::ListRunningQueries,
Self::FindBlockingLocks,
Self::SlowQueries,
Self::DbHealthCheck,
Self::GetIndexStatus,
Self::ListExtensions,
Self::ServerSettings,
Self::SuggestIndexes,
Self::FindUnusedIndexes,
Self::BloatReport,
Self::FindMissingFks,
Self::ExportQuery,
Self::ListRoles,
Self::DbDashboard,
Self::DeepPlanAnalysis,
Self::SchemaDiff,
Self::GenerateMigration,
Self::AutoTuneQuery,
Self::CheckDdlSafety,
Self::ExecuteSql,
Self::EditRow,
Self::ImportData,
Self::ApplyDdl,
Self::CreateIndexConcurrently,
Self::RunMaintenance,
Self::TerminateQuery,
];
pub const READ_ONLY: &'static [ToolName] = &[
Self::ListConnections,
Self::ListDatabases,
Self::ListSchemas,
Self::ListObjects,
Self::GetCurrentContext,
Self::SwitchConnection,
Self::RunSelect,
Self::ExplainQuery,
Self::RunDoctor,
Self::SetupConnection,
Self::SaveProfile,
Self::TestProfile,
Self::ExportProfile,
Self::ImportProfile,
Self::ResolveTarget,
Self::Orient,
Self::InspectOrSearch,
Self::SearchAllDatabases,
Self::SearchSchema,
Self::DescribeObject,
Self::GetJoinPath,
Self::SampleValues,
Self::RebuildIndex,
Self::RefreshIndex,
Self::GetDdl,
Self::TableStats,
Self::IndexUsage,
Self::ListRunningQueries,
Self::FindBlockingLocks,
Self::SlowQueries,
Self::DbHealthCheck,
Self::GetIndexStatus,
Self::ListExtensions,
Self::ServerSettings,
Self::SuggestIndexes,
Self::FindUnusedIndexes,
Self::BloatReport,
Self::FindMissingFks,
Self::ExportQuery,
Self::ListRoles,
Self::DbDashboard,
Self::DeepPlanAnalysis,
Self::SchemaDiff,
Self::GenerateMigration,
Self::AutoTuneQuery,
];
pub const QUERY_PROFILE: &'static [ToolName] = &[
Self::ListConnections,
Self::ListDatabases,
Self::ListSchemas,
Self::ListObjects,
Self::GetCurrentContext,
Self::SwitchConnection,
Self::RunSelect,
Self::ExplainQuery,
Self::ResolveTarget,
Self::Orient,
Self::InspectOrSearch,
Self::SearchAllDatabases,
Self::SearchSchema,
Self::DescribeObject,
Self::GetJoinPath,
Self::SampleValues,
Self::RebuildIndex,
Self::RefreshIndex,
Self::RunDoctor,
Self::GetDdl,
Self::ExportQuery,
];
pub const DBA_PROFILE: &'static [ToolName] = &[
Self::ListConnections,
Self::GetCurrentContext,
Self::TableStats,
Self::IndexUsage,
Self::ListRunningQueries,
Self::FindBlockingLocks,
Self::SlowQueries,
Self::DbHealthCheck,
Self::GetIndexStatus,
Self::ListExtensions,
Self::ServerSettings,
Self::SuggestIndexes,
Self::FindUnusedIndexes,
Self::BloatReport,
Self::FindMissingFks,
Self::DbDashboard,
Self::DeepPlanAnalysis,
Self::SchemaDiff,
Self::GenerateMigration,
Self::AutoTuneQuery,
Self::CheckDdlSafety,
Self::RunMaintenance,
Self::TerminateQuery,
Self::RebuildIndex,
Self::RefreshIndex,
Self::RunDoctor,
];
pub const META_PROFILE: &'static [ToolName] = &[
Self::ListConnections,
Self::GetCurrentContext,
Self::Orient,
Self::InspectOrSearch,
Self::SearchAllDatabases,
Self::SearchSchema,
Self::DescribeObject,
Self::RunSelect,
Self::DiscoverTools,
Self::RunDoctor,
Self::SetupConnection,
Self::SaveProfile,
Self::TestProfile,
];
pub fn for_profile(profile: ToolProfile) -> &'static [ToolName] {
match profile {
ToolProfile::Query => Self::QUERY_PROFILE,
ToolProfile::Dba => Self::DBA_PROFILE,
ToolProfile::Meta => Self::META_PROFILE,
ToolProfile::Full => Self::ACTIVE,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::ResolveTarget => "resolve_target",
Self::Orient => "orient",
Self::InspectOrSearch => "inspect_or_search",
Self::SearchAllDatabases => "search_all_databases",
Self::SearchSchema => "search_schema",
Self::DescribeObject => "describe_object",
Self::GetJoinPath => "get_join_path",
Self::SampleValues => "sample_values",
Self::RunSelect => "run_select",
Self::ExplainQuery => "explain_query",
Self::ListConnections => "list_connections",
Self::ListDatabases => "list_databases",
Self::ListSchemas => "list_schemas",
Self::ListObjects => "list_objects",
Self::GetCurrentContext => "get_current_context",
Self::SwitchConnection => "switch_connection",
Self::GetDdl => "get_ddl",
Self::TableStats => "table_stats",
Self::IndexUsage => "index_usage",
Self::ListRunningQueries => "list_running_queries",
Self::FindBlockingLocks => "find_blocking_locks",
Self::SlowQueries => "slow_queries",
Self::DbHealthCheck => "db_health_check",
Self::GetIndexStatus => "get_index_status",
Self::ListExtensions => "list_extensions",
Self::ServerSettings => "server_settings",
Self::SuggestIndexes => "suggest_indexes",
Self::FindUnusedIndexes => "find_unused_indexes",
Self::BloatReport => "bloat_report",
Self::FindMissingFks => "find_missing_fks",
Self::ExportQuery => "export_query",
Self::ListRoles => "list_roles",
Self::DbDashboard => "db_dashboard",
Self::DeepPlanAnalysis => "deep_plan_analysis",
Self::SchemaDiff => "schema_diff",
Self::GenerateMigration => "generate_migration",
Self::AutoTuneQuery => "auto_tune_query",
Self::CheckDdlSafety => "check_ddl_safety",
Self::ExecuteSql => "execute_sql",
Self::EditRow => "edit_row",
Self::ImportData => "import_data",
Self::ApplyDdl => "apply_ddl",
Self::CreateIndexConcurrently => "create_index_concurrently",
Self::RunMaintenance => "run_maintenance",
Self::TerminateQuery => "terminate_query",
Self::DiscoverTools => "discover_tools",
Self::RebuildIndex => "rebuild_index",
Self::RefreshIndex => "refresh_index",
Self::RunDoctor => "run_doctor",
Self::SetupConnection => "setup_connection",
Self::SaveProfile => "save_profile",
Self::TestProfile => "test_profile",
Self::ExportProfile => "export_profile",
Self::ImportProfile => "import_profile",
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ACTIVE.iter().copied().find(|t| t.as_str() == s)
}
pub fn hints(self) -> ToolHints {
let read_only = Self::READ_ONLY.contains(&self);
let destructive = matches!(
self,
Self::ApplyDdl
| Self::EditRow
| Self::ImportData
| Self::ExecuteSql
| Self::RunMaintenance
| Self::TerminateQuery
);
let idempotent = read_only
&& !matches!(
self,
Self::RebuildIndex
| Self::RefreshIndex
| Self::RunDoctor
| Self::SetupConnection
| Self::SaveProfile
| Self::ImportProfile
| Self::SwitchConnection
| Self::DiscoverTools
);
ToolHints {
read_only,
destructive,
idempotent,
open_world: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ToolHints {
pub read_only: bool,
pub destructive: bool,
pub idempotent: bool,
pub open_world: bool,
}
#[cfg(test)]
mod tests {
use super::ToolName;
#[test]
fn read_only_is_forty_one_tools() {
assert_eq!(ToolName::READ_ONLY.len(), 45);
}
#[test]
fn phase9_has_seven_tools() {
assert_eq!(ToolName::PHASE9.len(), 7);
}
#[test]
fn active_surface_is_fifty_four_tools() {
assert_eq!(ToolName::ACTIVE.len(), 54);
}
#[test]
fn read_only_subset_of_active() {
for tool in ToolName::READ_ONLY {
assert!(ToolName::ACTIVE.contains(tool));
}
assert_ne!(ToolName::READ_ONLY.len(), ToolName::ACTIVE.len());
}
#[test]
fn tool_hints_classify_read_and_write_tools() {
assert!(ToolName::RunSelect.hints().read_only);
assert!(!ToolName::RunSelect.hints().destructive);
assert!(!ToolName::ApplyDdl.hints().read_only);
assert!(ToolName::ApplyDdl.hints().destructive);
}
#[test]
fn docs_tools_readme_matches_active_surface() {
let doc = include_str!("../../../docs/tools/README.md");
let declared: usize = doc
.lines()
.next()
.and_then(|line| line.split('(').nth(1))
.and_then(|rest| rest.split_whitespace().next())
.and_then(|n| n.parse().ok())
.expect("first line must read \"Active catalog (<N> tools ...)\"");
assert_eq!(
declared,
ToolName::ACTIVE.len(),
"docs/tools/README.md's declared tool count is stale"
);
for tool in ToolName::ACTIVE {
let needle = format!("`{}`", tool.as_str());
assert!(
doc.contains(&needle),
"docs/tools/README.md is missing `{}` (tool #{:?})",
tool.as_str(),
tool
);
}
}
}