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