1use crate::config::{Config, Table, TableCatalog};
4use crate::engine::QueryExecutor;
5use crate::lexer::Tokenizer;
6use crate::output::{format_results, OutputFormat};
7use crate::parser::Parser as SqlParser;
8use crate::parser::Statement;
9use anyhow::{anyhow, Result};
10use serde_json::{json, Value};
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use tracing::info;
14
15pub struct ToolHandler {
16 config: Arc<RwLock<Config>>,
17 catalog: Arc<RwLock<TableCatalog>>,
18}
19
20impl ToolHandler {
21 pub fn new(config: Arc<RwLock<Config>>, catalog: Arc<RwLock<TableCatalog>>) -> Self {
22 Self { config, catalog }
23 }
24
25 pub fn list_tools(&self) -> Vec<Value> {
27 vec![
28 json!({
29 "name": "execute_query",
30 "description": "Execute a SQL query against DDB tables or CSV files",
31 "inputSchema": {
32 "type": "object",
33 "properties": {
34 "query": { "type": "string", "description": "SQL query to execute" },
35 "output_format": { "type": "string", "enum": ["json", "yaml", "csv", "table"], "default": "json" }
36 },
37 "required": ["query"]
38 }
39 }),
40 json!({
41 "name": "list_tables",
42 "description": "List all configured tables",
43 "inputSchema": {
44 "type": "object",
45 "properties": {
46 "database": { "type": "string", "description": "Filter by database" }
47 }
48 }
49 }),
50 json!({
51 "name": "describe_table",
52 "description": "Get table schema information",
53 "inputSchema": {
54 "type": "object",
55 "properties": {
56 "table_name": { "type": "string", "description": "Table name" }
57 },
58 "required": ["table_name"]
59 }
60 }),
61 ]
62 }
63
64 pub async fn call_tool(&self, name: &str, arguments: Option<Value>) -> Result<Value> {
66 let args = arguments.unwrap_or(json!({}));
67
68 match name {
69 "execute_query" => self.execute_query(args).await,
70 "list_tables" => self.list_tables(args).await,
71 "describe_table" => self.describe_table(args).await,
72 _ => Err(anyhow!("Unknown tool: {}", name)),
73 }
74 }
75
76 async fn execute_query(&self, args: Value) -> Result<Value> {
77 let query = args["query"].as_str().ok_or_else(|| anyhow!("Missing query"))?;
78 let output_format = args["output_format"].as_str().unwrap_or("json");
79
80 info!("Executing: {}", query);
81
82 let mut tokenizer = Tokenizer::new();
83 let tokens = tokenizer.tokenize(query)?;
84 let mut parser = SqlParser::new(tokens);
85 let statement = parser.parse()?;
86
87 match statement {
88 Statement::Select(select_stmt) => {
89 let catalog = self.catalog.read().await;
90 let table_name = select_stmt.from.as_ref().ok_or_else(|| anyhow!("No FROM clause"))?;
91 let table = catalog.get_table(table_name).ok_or_else(|| anyhow!("Table not found"))?.clone();
92
93 let executor = QueryExecutor::new();
94 let results = executor.execute_select(&select_stmt, &table)?;
95 let format = OutputFormat::from_str(output_format);
96 let formatted = format_results(&results, format)?;
97
98 Ok(json!({
99 "success": true,
100 "rows": results.len(),
101 "data": formatted
102 }))
103 }
104 _ => Ok(json!({ "success": false, "error": "Only SELECT supported" }))
105 }
106 }
107
108 async fn list_tables(&self, _args: Value) -> Result<Value> {
109 let catalog = self.catalog.read().await;
110 let tables = catalog.list_tables();
111 Ok(json!({ "success": true, "tables": tables }))
112 }
113
114 async fn describe_table(&self, args: Value) -> Result<Value> {
115 let table_name = args["table_name"].as_str().ok_or_else(|| anyhow!("Missing table_name"))?;
116 let catalog = self.catalog.read().await;
117 let table = catalog.get_table(table_name).ok_or_else(|| anyhow!("Table not found"))?;
118
119 let columns: Vec<Value> = table.columns.iter().map(|col| {
120 json!({ "name": col.name, "type": format!("{:?}", col.data_type), "nullable": col.nullable })
121 }).collect();
122
123 Ok(json!({
124 "success": true,
125 "table": {
126 "name": table.name,
127 "database": table.database,
128 "file": table.data_file,
129 "columns": columns
130 }
131 }))
132 }
133}