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