1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum ToolProfile {
9 Query,
11 Dba,
13 Meta,
15 #[default]
17 Full,
18}
19
20impl ToolProfile {
21 pub fn as_str(self) -> &'static str {
22 match self {
23 Self::Query => "query",
24 Self::Dba => "dba",
25 Self::Meta => "meta",
26 Self::Full => "full",
27 }
28 }
29
30 pub fn parse(s: &str) -> Option<Self> {
31 match s.to_lowercase().as_str() {
32 "query" => Some(Self::Query),
33 "dba" => Some(Self::Dba),
34 "meta" => Some(Self::Meta),
35 "full" => Some(Self::Full),
36 _ => None,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum ToolName {
44 ResolveTarget,
45 SearchSchema,
46 DescribeObject,
47 GetJoinPath,
48 SampleValues,
49 RunSelect,
50 ExplainQuery,
51 ListConnections,
52 ListDatabases,
53 ListSchemas,
54 ListObjects,
55 GetCurrentContext,
56 SwitchConnection,
57 GetDdl,
58 TableStats,
59 IndexUsage,
60 ListRunningQueries,
61 FindBlockingLocks,
62 SlowQueries,
63 DbHealthCheck,
64 ExplainAnalyze,
65 AnalyzeQueryPlan,
66 GetIndexStatus,
67 ListExtensions,
68 ServerSettings,
69 SuggestIndexes,
70 FindUnusedIndexes,
71 BloatReport,
72 FindMissingFks,
73 ExportQuery,
74 ListRoles,
75 DbDashboard,
76 DeepPlanAnalysis,
77 SchemaDiff,
78 GenerateMigration,
79 ExecuteSql,
80 EditRow,
81 ImportData,
82 ApplyDdl,
83 CreateIndexConcurrently,
84 RunMaintenance,
85 TerminateQuery,
86 DiscoverTools,
87 AutoTuneQuery,
88 CheckDdlSafety,
89 RebuildIndex,
90 RefreshIndex,
91 RunDoctor,
92 SetupConnection,
93 SaveProfile,
94 TestProfile,
95 ExportProfile,
96 ImportProfile,
97}
98
99impl ToolName {
100 pub const PHASE2: &'static [ToolName] = &[
101 Self::ListConnections,
102 Self::ListDatabases,
103 Self::ListSchemas,
104 Self::ListObjects,
105 Self::GetCurrentContext,
106 Self::SwitchConnection,
107 Self::RunSelect,
108 Self::ExplainQuery,
109 Self::DiscoverTools,
110 Self::RunDoctor,
111 Self::SetupConnection,
112 Self::SaveProfile,
113 Self::TestProfile,
114 Self::ExportProfile,
115 Self::ImportProfile,
116 ];
117
118 pub const PHASE3: &'static [ToolName] = &[
120 Self::ResolveTarget,
121 Self::SearchSchema,
122 Self::DescribeObject,
123 Self::GetJoinPath,
124 Self::SampleValues,
125 Self::RebuildIndex,
126 Self::RefreshIndex,
127 ];
128
129 pub const PHASE4: &'static [ToolName] = &[
131 Self::GetDdl,
132 Self::TableStats,
133 Self::IndexUsage,
134 Self::ListRunningQueries,
135 Self::FindBlockingLocks,
136 Self::SlowQueries,
137 Self::DbHealthCheck,
138 Self::ExplainAnalyze,
139 Self::AnalyzeQueryPlan,
140 Self::GetIndexStatus,
141 Self::ListExtensions,
142 Self::ServerSettings,
143 Self::SuggestIndexes,
144 Self::FindUnusedIndexes,
145 Self::BloatReport,
146 Self::FindMissingFks,
147 ];
148
149 pub const PHASE4B: &'static [ToolName] = &[
151 Self::ExportQuery,
152 Self::ListRoles,
153 Self::DbDashboard,
154 Self::DeepPlanAnalysis,
155 Self::SchemaDiff,
156 Self::GenerateMigration,
157 Self::AutoTuneQuery,
158 Self::CheckDdlSafety,
159 ];
160
161 pub const PHASE9: &'static [ToolName] = &[
163 Self::ExecuteSql,
164 Self::EditRow,
165 Self::ImportData,
166 Self::ApplyDdl,
167 Self::CreateIndexConcurrently,
168 Self::RunMaintenance,
169 Self::TerminateQuery,
170 ];
171
172 pub const ACTIVE: &'static [ToolName] = &[
174 Self::ListConnections,
175 Self::ListDatabases,
176 Self::ListSchemas,
177 Self::ListObjects,
178 Self::GetCurrentContext,
179 Self::SwitchConnection,
180 Self::RunSelect,
181 Self::ExplainQuery,
182 Self::DiscoverTools,
183 Self::RunDoctor,
184 Self::SetupConnection,
185 Self::SaveProfile,
186 Self::TestProfile,
187 Self::ExportProfile,
188 Self::ImportProfile,
189 Self::ResolveTarget,
190 Self::SearchSchema,
191 Self::DescribeObject,
192 Self::GetJoinPath,
193 Self::SampleValues,
194 Self::RebuildIndex,
195 Self::RefreshIndex,
196 Self::GetDdl,
197 Self::TableStats,
198 Self::IndexUsage,
199 Self::ListRunningQueries,
200 Self::FindBlockingLocks,
201 Self::SlowQueries,
202 Self::DbHealthCheck,
203 Self::ExplainAnalyze,
204 Self::AnalyzeQueryPlan,
205 Self::GetIndexStatus,
206 Self::ListExtensions,
207 Self::ServerSettings,
208 Self::SuggestIndexes,
209 Self::FindUnusedIndexes,
210 Self::BloatReport,
211 Self::FindMissingFks,
212 Self::ExportQuery,
213 Self::ListRoles,
214 Self::DbDashboard,
215 Self::DeepPlanAnalysis,
216 Self::SchemaDiff,
217 Self::GenerateMigration,
218 Self::AutoTuneQuery,
219 Self::CheckDdlSafety,
220 Self::ExecuteSql,
221 Self::EditRow,
222 Self::ImportData,
223 Self::ApplyDdl,
224 Self::CreateIndexConcurrently,
225 Self::RunMaintenance,
226 Self::TerminateQuery,
227 ];
228
229 pub const READ_ONLY: &'static [ToolName] = &[
231 Self::ListConnections,
232 Self::ListDatabases,
233 Self::ListSchemas,
234 Self::ListObjects,
235 Self::GetCurrentContext,
236 Self::SwitchConnection,
237 Self::RunSelect,
238 Self::ExplainQuery,
239 Self::RunDoctor,
240 Self::SetupConnection,
241 Self::SaveProfile,
242 Self::TestProfile,
243 Self::ExportProfile,
244 Self::ImportProfile,
245 Self::ResolveTarget,
246 Self::SearchSchema,
247 Self::DescribeObject,
248 Self::GetJoinPath,
249 Self::SampleValues,
250 Self::RebuildIndex,
251 Self::RefreshIndex,
252 Self::GetDdl,
253 Self::TableStats,
254 Self::IndexUsage,
255 Self::ListRunningQueries,
256 Self::FindBlockingLocks,
257 Self::SlowQueries,
258 Self::DbHealthCheck,
259 Self::ExplainAnalyze,
260 Self::AnalyzeQueryPlan,
261 Self::GetIndexStatus,
262 Self::ListExtensions,
263 Self::ServerSettings,
264 Self::SuggestIndexes,
265 Self::FindUnusedIndexes,
266 Self::BloatReport,
267 Self::FindMissingFks,
268 Self::ExportQuery,
269 Self::ListRoles,
270 Self::DbDashboard,
271 Self::DeepPlanAnalysis,
272 Self::SchemaDiff,
273 Self::GenerateMigration,
274 ];
275
276 pub const QUERY_PROFILE: &'static [ToolName] = &[
278 Self::ListConnections,
279 Self::ListDatabases,
280 Self::ListSchemas,
281 Self::ListObjects,
282 Self::GetCurrentContext,
283 Self::SwitchConnection,
284 Self::RunSelect,
285 Self::ExplainQuery,
286 Self::ResolveTarget,
287 Self::SearchSchema,
288 Self::DescribeObject,
289 Self::GetJoinPath,
290 Self::SampleValues,
291 Self::RebuildIndex,
292 Self::RefreshIndex,
293 Self::RunDoctor,
294 Self::GetDdl,
295 Self::ExportQuery,
296 ];
297
298 pub const DBA_PROFILE: &'static [ToolName] = &[
300 Self::ListConnections,
301 Self::GetCurrentContext,
302 Self::TableStats,
303 Self::IndexUsage,
304 Self::ListRunningQueries,
305 Self::FindBlockingLocks,
306 Self::SlowQueries,
307 Self::DbHealthCheck,
308 Self::ExplainAnalyze,
309 Self::AnalyzeQueryPlan,
310 Self::GetIndexStatus,
311 Self::ListExtensions,
312 Self::ServerSettings,
313 Self::SuggestIndexes,
314 Self::FindUnusedIndexes,
315 Self::BloatReport,
316 Self::FindMissingFks,
317 Self::DbDashboard,
318 Self::DeepPlanAnalysis,
319 Self::SchemaDiff,
320 Self::GenerateMigration,
321 Self::AutoTuneQuery,
322 Self::CheckDdlSafety,
323 Self::RunMaintenance,
324 Self::TerminateQuery,
325 Self::RebuildIndex,
326 Self::RefreshIndex,
327 Self::RunDoctor,
328 ];
329
330 pub const META_PROFILE: &'static [ToolName] = &[
332 Self::ListConnections,
333 Self::GetCurrentContext,
334 Self::SearchSchema,
335 Self::DescribeObject,
336 Self::RunSelect,
337 Self::DiscoverTools,
338 Self::RunDoctor,
339 Self::SetupConnection,
340 Self::SaveProfile,
341 Self::TestProfile,
342 ];
343
344 pub fn for_profile(profile: ToolProfile) -> &'static [ToolName] {
345 match profile {
346 ToolProfile::Query => Self::QUERY_PROFILE,
347 ToolProfile::Dba => Self::DBA_PROFILE,
348 ToolProfile::Meta => Self::META_PROFILE,
349 ToolProfile::Full => Self::ACTIVE,
350 }
351 }
352
353 pub fn as_str(self) -> &'static str {
354 match self {
355 Self::ResolveTarget => "resolve_target",
356 Self::SearchSchema => "search_schema",
357 Self::DescribeObject => "describe_object",
358 Self::GetJoinPath => "get_join_path",
359 Self::SampleValues => "sample_values",
360 Self::RunSelect => "run_select",
361 Self::ExplainQuery => "explain_query",
362 Self::ListConnections => "list_connections",
363 Self::ListDatabases => "list_databases",
364 Self::ListSchemas => "list_schemas",
365 Self::ListObjects => "list_objects",
366 Self::GetCurrentContext => "get_current_context",
367 Self::SwitchConnection => "switch_connection",
368 Self::GetDdl => "get_ddl",
369 Self::TableStats => "table_stats",
370 Self::IndexUsage => "index_usage",
371 Self::ListRunningQueries => "list_running_queries",
372 Self::FindBlockingLocks => "find_blocking_locks",
373 Self::SlowQueries => "slow_queries",
374 Self::DbHealthCheck => "db_health_check",
375 Self::ExplainAnalyze => "explain_analyze",
376 Self::AnalyzeQueryPlan => "analyze_query_plan",
377 Self::GetIndexStatus => "get_index_status",
378 Self::ListExtensions => "list_extensions",
379 Self::ServerSettings => "server_settings",
380 Self::SuggestIndexes => "suggest_indexes",
381 Self::FindUnusedIndexes => "find_unused_indexes",
382 Self::BloatReport => "bloat_report",
383 Self::FindMissingFks => "find_missing_fks",
384 Self::ExportQuery => "export_query",
385 Self::ListRoles => "list_roles",
386 Self::DbDashboard => "db_dashboard",
387 Self::DeepPlanAnalysis => "deep_plan_analysis",
388 Self::SchemaDiff => "schema_diff",
389 Self::GenerateMigration => "generate_migration",
390 Self::AutoTuneQuery => "auto_tune_query",
391 Self::CheckDdlSafety => "check_ddl_safety",
392 Self::ExecuteSql => "execute_sql",
393 Self::EditRow => "edit_row",
394 Self::ImportData => "import_data",
395 Self::ApplyDdl => "apply_ddl",
396 Self::CreateIndexConcurrently => "create_index_concurrently",
397 Self::RunMaintenance => "run_maintenance",
398 Self::TerminateQuery => "terminate_query",
399 Self::DiscoverTools => "discover_tools",
400 Self::RebuildIndex => "rebuild_index",
401 Self::RefreshIndex => "refresh_index",
402 Self::RunDoctor => "run_doctor",
403 Self::SetupConnection => "setup_connection",
404 Self::SaveProfile => "save_profile",
405 Self::TestProfile => "test_profile",
406 Self::ExportProfile => "export_profile",
407 Self::ImportProfile => "import_profile",
408 }
409 }
410
411 pub fn parse(s: &str) -> Option<Self> {
412 Self::ACTIVE.iter().copied().find(|t| t.as_str() == s)
413 }
414
415 pub fn hints(self) -> ToolHints {
417 let read_only = Self::READ_ONLY.contains(&self);
418 let destructive = matches!(
419 self,
420 Self::ApplyDdl
421 | Self::EditRow
422 | Self::ImportData
423 | Self::ExecuteSql
424 | Self::RunMaintenance
425 | Self::TerminateQuery
426 );
427 let idempotent = read_only
428 && !matches!(
429 self,
430 Self::RebuildIndex
431 | Self::RefreshIndex
432 | Self::RunDoctor
433 | Self::SetupConnection
434 | Self::SaveProfile
435 | Self::ImportProfile
436 | Self::SwitchConnection
437 | Self::ExplainAnalyze
438 | Self::DiscoverTools
439 );
440 ToolHints {
441 read_only,
442 destructive,
443 idempotent,
444 open_world: true,
445 }
446 }
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub struct ToolHints {
452 pub read_only: bool,
453 pub destructive: bool,
454 pub idempotent: bool,
455 pub open_world: bool,
456}
457
458#[cfg(test)]
459mod tests {
460 use super::ToolName;
461
462 #[test]
463 fn read_only_is_forty_one_tools() {
464 assert_eq!(ToolName::READ_ONLY.len(), 43);
465 }
466
467 #[test]
468 fn phase9_has_seven_tools() {
469 assert_eq!(ToolName::PHASE9.len(), 7);
470 }
471
472 #[test]
473 fn active_surface_is_fifty_one_tools() {
474 assert_eq!(ToolName::ACTIVE.len(), 53);
475 }
476
477 #[test]
478 fn read_only_subset_of_active() {
479 for tool in ToolName::READ_ONLY {
480 assert!(ToolName::ACTIVE.contains(tool));
481 }
482 assert_ne!(ToolName::READ_ONLY.len(), ToolName::ACTIVE.len());
483 }
484
485 #[test]
486 fn tool_hints_classify_read_and_write_tools() {
487 assert!(ToolName::RunSelect.hints().read_only);
488 assert!(!ToolName::RunSelect.hints().destructive);
489 assert!(!ToolName::ApplyDdl.hints().read_only);
490 assert!(ToolName::ApplyDdl.hints().destructive);
491 }
492
493 #[test]
497 fn docs_tools_readme_matches_active_surface() {
498 let doc = include_str!("../../../docs/tools/README.md");
499
500 let declared: usize = doc
501 .lines()
502 .next()
503 .and_then(|line| line.split('(').nth(1))
504 .and_then(|rest| rest.split_whitespace().next())
505 .and_then(|n| n.parse().ok())
506 .expect("first line must read \"Active catalog (<N> tools ...)\"");
507 assert_eq!(
508 declared,
509 ToolName::ACTIVE.len(),
510 "docs/tools/README.md's declared tool count is stale"
511 );
512
513 for tool in ToolName::ACTIVE {
514 let needle = format!("`{}`", tool.as_str());
515 assert!(
516 doc.contains(&needle),
517 "docs/tools/README.md is missing `{}` (tool #{:?})",
518 tool.as_str(),
519 tool
520 );
521 }
522 }
523}