sql_cli/sql/
hybrid_parser.rs

1use crate::cursor_aware_parser::CursorAwareParser;
2use crate::recursive_parser::{detect_cursor_context, tokenize_query, CursorContext, LogicalOp};
3
4#[derive(Clone)]
5pub struct HybridParser {
6    parser: CursorAwareParser,
7}
8
9impl std::fmt::Debug for HybridParser {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        f.debug_struct("HybridParser")
12            .field("parser", &"<CursorAwareParser>")
13            .finish()
14    }
15}
16
17#[derive(Debug, Clone)]
18pub struct HybridResult {
19    pub suggestions: Vec<String>,
20    pub context: String,
21    pub parser_used: String,
22    pub recursive_context: String,
23    pub cursor_position: usize,
24    pub query_complexity: String,
25}
26
27impl Default for HybridParser {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl HybridParser {
34    #[must_use]
35    pub fn new() -> Self {
36        Self {
37            parser: CursorAwareParser::new(),
38        }
39    }
40
41    pub fn update_single_table(&mut self, table_name: String, columns: Vec<String>) {
42        self.parser.update_single_table(table_name, columns);
43    }
44
45    #[must_use]
46    pub fn get_table_columns(&self, table_name: &str) -> Vec<String> {
47        self.parser.get_table_columns(table_name)
48    }
49
50    #[must_use]
51    pub fn get_completions(&self, query: &str, cursor_pos: usize) -> HybridResult {
52        // Use the improved parser with recursive descent for context detection
53        let result = self.parser.get_completions(query, cursor_pos);
54
55        // Get recursive parser context for debugging
56        let (cursor_context, _) = detect_cursor_context(query, cursor_pos);
57        let recursive_context = match cursor_context {
58            CursorContext::SelectClause => "SelectClause",
59            CursorContext::FromClause => "FromClause",
60            CursorContext::WhereClause => "WhereClause",
61            CursorContext::OrderByClause => "OrderByClause",
62            CursorContext::AfterColumn(_) => "AfterColumn",
63            CursorContext::AfterLogicalOp(LogicalOp::And) => "AfterAND",
64            CursorContext::AfterLogicalOp(LogicalOp::Or) => "AfterOR",
65            CursorContext::AfterComparisonOp(_, _) => "AfterComparisonOp",
66            CursorContext::InMethodCall(_, _) => "InMethodCall",
67            CursorContext::InExpression => "InExpression",
68            CursorContext::Unknown => "Unknown",
69        };
70
71        HybridResult {
72            suggestions: result.suggestions,
73            context: result.context.clone(),
74            parser_used: "RecursiveDescent".to_string(),
75            recursive_context: recursive_context.to_string(),
76            cursor_position: cursor_pos,
77            query_complexity: self.analyze_query_complexity(query),
78        }
79    }
80
81    fn analyze_query_complexity(&self, query: &str) -> String {
82        let mut complexity_factors = Vec::new();
83
84        // Count logical operators
85        let logical_ops = query.to_uppercase().matches(" AND ").count()
86            + query.to_uppercase().matches(" OR ").count();
87        if logical_ops > 0 {
88            complexity_factors.push(format!("{logical_ops}x logical"));
89        }
90
91        // Count method calls
92        let method_calls = query.matches('.').count();
93        if method_calls > 0 {
94            complexity_factors.push(format!("{method_calls}x methods"));
95        }
96
97        // Count parentheses depth
98        let paren_depth = self.max_paren_depth(query);
99        if paren_depth > 1 {
100            complexity_factors.push(format!("{paren_depth}lvl nested"));
101        }
102
103        // Count subqueries (simplified)
104        if query.to_uppercase().contains("SELECT")
105            && query.to_uppercase().matches("SELECT").count() > 1
106        {
107            complexity_factors.push("subquery".to_string());
108        }
109
110        if complexity_factors.is_empty() {
111            "simple".to_string()
112        } else {
113            complexity_factors.join(", ")
114        }
115    }
116
117    fn max_paren_depth(&self, query: &str) -> usize {
118        let mut max_depth = 0;
119        let mut current_depth: usize = 0;
120
121        for ch in query.chars() {
122            match ch {
123                '(' => {
124                    current_depth += 1;
125                    max_depth = max_depth.max(current_depth);
126                }
127                ')' => {
128                    current_depth = current_depth.saturating_sub(1);
129                }
130                _ => {}
131            }
132        }
133
134        max_depth
135    }
136
137    #[must_use]
138    pub fn debug_tree(&self, query: &str) -> String {
139        // Use the AST formatter from recursive_parser
140        crate::recursive_parser::format_ast_tree(query)
141    }
142
143    #[must_use]
144    pub fn get_detailed_debug_info(&self, query: &str, cursor_pos: usize) -> String {
145        let result = self.get_completions(query, cursor_pos);
146
147        let char_at_cursor = if cursor_pos < query.len() {
148            format!("'{}'", query.chars().nth(cursor_pos).unwrap_or(' '))
149        } else {
150            "EOF".to_string()
151        };
152
153        // Get tokenized output
154        let tokens = tokenize_query(query);
155        let tokenized_output = if tokens.is_empty() {
156            "  (no tokens)".to_string()
157        } else {
158            tokens
159                .iter()
160                .enumerate()
161                .map(|(i, t)| format!("  [{i}] {t}"))
162                .collect::<Vec<_>>()
163                .join("\n")
164        };
165
166        let ast_tree = self.debug_tree(query);
167
168        // Extract partial word from context string
169        let partial_word_info = if result.context.contains("(partial:") {
170            // Extract the partial word from the context string
171            if let Some(start) = result.context.find("(partial: ") {
172                let substr = &result.context[start + 10..];
173                if let Some(end) = substr.find(')') {
174                    substr[..end].to_string()
175                } else {
176                    "None".to_string()
177                }
178            } else {
179                "None".to_string()
180            }
181        } else {
182            "None".to_string()
183        };
184
185        format!(
186            "========== PARSER DEBUG ==========\n\
187Query: '{}'\n\
188Query Length: {}\n\
189Cursor: {} {}\n\
190Partial Word: {}\n\
191Complexity: {}\n\
192Parser Used: {}\n\
193Parser Type: Recursive Descent\n\
194\n\
195TOKENIZED OUTPUT:\n{}\n\
196\n\
197CONTEXT: {}\n\
198RECURSIVE PARSER CONTEXT: {}\n\
199\n\
200SUGGESTIONS ({}):\n{}\n\
201\n\
202AST TREE:\n{}\n\
203==================================",
204            query,
205            query.len(),
206            cursor_pos,
207            char_at_cursor,
208            partial_word_info,
209            result.query_complexity,
210            result.parser_used,
211            tokenized_output,
212            result.context,
213            result.recursive_context,
214            result.suggestions.len(),
215            if result.suggestions.is_empty() {
216                "  (no suggestions)".to_string()
217            } else {
218                result
219                    .suggestions
220                    .iter()
221                    .enumerate()
222                    .map(|(i, s)| format!("  {}: {}", i + 1, s))
223                    .collect::<Vec<_>>()
224                    .join("\n")
225            },
226            ast_tree
227        )
228    }
229}