sql_cli/sql/
hybrid_parser.rs1use 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 let result = self.parser.get_completions(query, cursor_pos);
54
55 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 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 let method_calls = query.matches('.').count();
93 if method_calls > 0 {
94 complexity_factors.push(format!("{method_calls}x methods"));
95 }
96
97 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 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 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 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 let partial_word_info = if result.context.contains("(partial:") {
170 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}