1use crate::dialect::Dialect;
2use crate::parser::SqlParser;
3use crate::schema::Schema;
4use async_trait::async_trait;
5use tower_lsp::lsp_types::{
6 CompletionItem, CompletionItemKind, Diagnostic, Hover, Location, MarkedString, Position,
7};
8
9pub struct ClickHouseDialect {
10 parser: std::sync::Mutex<SqlParser>,
11}
12
13impl Default for ClickHouseDialect {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl ClickHouseDialect {
20 pub fn new() -> Self {
21 Self {
22 parser: std::sync::Mutex::new(SqlParser::new()),
23 }
24 }
25
26 fn create_keyword_item(&self, keyword: &str) -> CompletionItem {
27 CompletionItem {
28 label: keyword.to_string(),
29 kind: Some(CompletionItemKind::KEYWORD),
30 detail: Some(format!("ClickHouse keyword: {}", keyword)),
31 documentation: None,
32 deprecated: None,
33 preselect: None,
34 sort_text: Some(format!("0{}", keyword)),
35 filter_text: None,
36 insert_text: Some(keyword.to_string()),
37 insert_text_format: None,
38 insert_text_mode: None,
39 text_edit: None,
40 additional_text_edits: None,
41 commit_characters: None,
42 command: None,
43 data: None,
44 tags: None,
45 label_details: None,
46 }
47 }
48
49 fn create_table_item(&self, table: &crate::schema::Table) -> CompletionItem {
50 CompletionItem {
51 label: table.name.clone(),
52 kind: Some(CompletionItemKind::CLASS),
53 detail: Some(format!("Table: {}", table.name)),
54 documentation: table
55 .comment
56 .clone()
57 .map(tower_lsp::lsp_types::Documentation::String),
58 deprecated: None,
59 preselect: None,
60 sort_text: Some(format!("1{}", table.name)),
61 filter_text: None,
62 insert_text: Some(table.name.clone()),
63 insert_text_format: None,
64 insert_text_mode: None,
65 text_edit: None,
66 additional_text_edits: None,
67 commit_characters: None,
68 command: None,
69 data: None,
70 tags: None,
71 label_details: None,
72 }
73 }
74
75 fn create_column_item(
76 &self,
77 column: &crate::schema::Column,
78 table_name: Option<&str>,
79 ) -> CompletionItem {
80 let label = if let Some(table) = table_name {
81 format!("{}.{}", table, column.name)
82 } else {
83 column.name.clone()
84 };
85
86 CompletionItem {
87 label,
88 kind: Some(CompletionItemKind::FIELD),
89 detail: Some(format!("Column: {} ({})", column.name, column.data_type)),
90 documentation: column
91 .comment
92 .clone()
93 .map(tower_lsp::lsp_types::Documentation::String),
94 deprecated: None,
95 preselect: None,
96 sort_text: Some(format!("2{}", column.name)),
97 filter_text: None,
98 insert_text: Some(column.name.clone()),
99 insert_text_format: None,
100 insert_text_mode: None,
101 text_edit: None,
102 additional_text_edits: None,
103 commit_characters: None,
104 command: None,
105 data: None,
106 tags: None,
107 label_details: None,
108 }
109 }
110}
111
112#[async_trait]
113impl Dialect for ClickHouseDialect {
114 fn name(&self) -> &str {
115 "clickhouse"
116 }
117
118 async fn parse(&self, sql: &str, _schema: Option<&Schema>) -> Vec<Diagnostic> {
119 let mut parser = self.parser.lock().unwrap();
120 let parse_result = parser.parse(sql);
121 parse_result.diagnostics
122 }
123
124 async fn completion(
125 &self,
126 sql: &str,
127 position: Position,
128 schema: Option<&Schema>,
129 ) -> Vec<CompletionItem> {
130 let mut parser = self.parser.lock().unwrap();
131 let parse_result = parser.parse(sql);
132
133 let context = if let Some(tree) = &parse_result.tree {
134 if let Some(node) = parser.get_node_at_position(tree, position) {
135 parser.analyze_completion_context(node, sql, position)
136 } else {
137 crate::parser::CompletionContext::Default
138 }
139 } else {
140 crate::parser::CompletionContext::Default
141 };
142
143 let mut items = Vec::new();
144 let keywords = &[
145 "SELECT",
146 "FROM",
147 "WHERE",
148 "INSERT",
149 "INTO",
150 "VALUES",
151 "CREATE",
152 "DROP",
153 "ALTER",
154 "TABLE",
155 "DATABASE",
156 "ENGINE",
157 "MergeTree",
158 "ReplacingMergeTree",
159 "SummingMergeTree",
160 "AggregatingMergeTree",
161 "CollapsingMergeTree",
162 "VersionedCollapsingMergeTree",
163 "JOIN",
164 "INNER",
165 "LEFT",
166 "RIGHT",
167 "FULL",
168 "OUTER",
169 "ON",
170 "GROUP",
171 "BY",
172 "ORDER",
173 "HAVING",
174 "LIMIT",
175 "OFFSET",
176 "UNION",
177 "ALL",
178 "DISTINCT",
179 "AS",
180 "AND",
181 "OR",
182 "NOT",
183 "IN",
184 "LIKE",
185 "ILIKE",
186 "BETWEEN",
187 "IS",
188 "NULL",
189 "CAST",
190 "ARRAY",
191 "TUPLE",
192 "MAP",
193 "Nested",
194 "AggregateFunction",
195 "Array",
196 "String",
197 "Int8",
198 "Int16",
199 "Int32",
200 "Int64",
201 "UInt8",
202 "UInt16",
203 "UInt32",
204 "UInt64",
205 "Float32",
206 "Float64",
207 "Date",
208 "DateTime",
209 ];
210
211 match context {
212 crate::parser::CompletionContext::FromClause
213 | crate::parser::CompletionContext::JoinClause => {
214 let join_keywords: Vec<&str> = keywords
215 .iter()
216 .filter(|&&k| matches!(k, "JOIN" | "INNER" | "LEFT" | "RIGHT" | "OUTER" | "ON"))
217 .copied()
218 .collect();
219 for keyword in join_keywords {
220 items.push(self.create_keyword_item(keyword));
221 }
222 if let Some(schema) = schema {
223 for table in &schema.tables {
224 items.push(self.create_table_item(table));
225 }
226 }
227 }
228 crate::parser::CompletionContext::SelectClause => {
229 let select_keywords: Vec<&str> = keywords
230 .iter()
231 .filter(|&&k| matches!(k, "SELECT" | "DISTINCT" | "AS" | "FROM"))
232 .copied()
233 .collect();
234 for keyword in select_keywords {
235 items.push(self.create_keyword_item(keyword));
236 }
237 if let Some(schema) = schema {
238 for table in &schema.tables {
239 for column in &table.columns {
240 items.push(self.create_column_item(
241 column,
242 Some(&format!("{}.{}", schema.database, table.name)),
243 ));
244 }
245 }
246 }
247 }
248 crate::parser::CompletionContext::WhereClause => {
249 let where_keywords: Vec<&str> = keywords
250 .iter()
251 .filter(|&&k| {
252 matches!(
253 k,
254 "AND"
255 | "OR"
256 | "NOT"
257 | "IN"
258 | "LIKE"
259 | "ILIKE"
260 | "BETWEEN"
261 | "IS"
262 | "NULL"
263 )
264 })
265 .copied()
266 .collect();
267 for keyword in where_keywords {
268 items.push(self.create_keyword_item(keyword));
269 }
270 let operators = vec!["=", "<>", "!=", ">", "<", ">=", "<="];
271 for op in operators {
272 items.push(CompletionItem {
273 label: op.to_string(),
274 kind: Some(CompletionItemKind::OPERATOR),
275 detail: Some(format!("Operator: {}", op)),
276 documentation: None,
277 deprecated: None,
278 preselect: None,
279 sort_text: Some(format!("1{}", op)),
280 filter_text: None,
281 insert_text: Some(op.to_string()),
282 insert_text_format: None,
283 insert_text_mode: None,
284 text_edit: None,
285 additional_text_edits: None,
286 commit_characters: None,
287 command: None,
288 data: None,
289 tags: None,
290 label_details: None,
291 });
292 }
293 if let Some(schema) = schema {
294 for table in &schema.tables {
295 for column in &table.columns {
296 items.push(self.create_column_item(
297 column,
298 Some(&format!("{}.{}", schema.database, table.name)),
299 ));
300 }
301 }
302 }
303 }
304 crate::parser::CompletionContext::OrderByClause
305 | crate::parser::CompletionContext::GroupByClause => {
306 let keywords_list: Vec<&str> = keywords
307 .iter()
308 .filter(|&&k| matches!(k, "ASC" | "DESC" | "BY"))
309 .copied()
310 .collect();
311 for keyword in keywords_list {
312 items.push(self.create_keyword_item(keyword));
313 }
314 if let Some(schema) = schema {
315 for table in &schema.tables {
316 for column in &table.columns {
317 items.push(self.create_column_item(
318 column,
319 Some(&format!("{}.{}", schema.database, table.name)),
320 ));
321 }
322 }
323 }
324 }
325 crate::parser::CompletionContext::HavingClause => {
326 let having_keywords: Vec<&str> = keywords
327 .iter()
328 .filter(|&&k| {
329 matches!(
330 k,
331 "AND"
332 | "OR"
333 | "NOT"
334 | "IN"
335 | "LIKE"
336 | "ILIKE"
337 | "BETWEEN"
338 | "IS"
339 | "NULL"
340 )
341 })
342 .copied()
343 .collect();
344 for keyword in having_keywords {
345 items.push(self.create_keyword_item(keyword));
346 }
347 let aggregate_functions = vec!["COUNT", "SUM", "AVG", "MIN", "MAX"];
348 for func in aggregate_functions {
349 items.push(self.create_keyword_item(func));
350 }
351 if let Some(schema) = schema {
352 for table in &schema.tables {
353 for column in &table.columns {
354 items.push(self.create_column_item(
355 column,
356 Some(&format!("{}.{}", schema.database, table.name)),
357 ));
358 }
359 }
360 }
361 }
362 crate::parser::CompletionContext::TableColumn => {
363 if let Some(tree) = &parse_result.tree {
364 if let Some(node) = parser.get_node_at_position(tree, position) {
365 if let Some(table_name) = parser.get_table_name_for_column(node, sql) {
366 if let Some(schema) = schema {
367 if let Some(table) = schema.tables.iter().find(|t| {
368 t.name == table_name
369 || format!("{}.{}", schema.database, t.name) == table_name
370 }) {
371 for column in &table.columns {
372 items.push(self.create_column_item(column, None));
373 }
374 }
375 }
376 }
377 }
378 }
379 }
380 crate::parser::CompletionContext::Default => {
381 for keyword in keywords {
382 items.push(self.create_keyword_item(keyword));
383 }
384 if let Some(schema) = schema {
385 for table in &schema.tables {
386 items.push(self.create_table_item(table));
387 }
388 }
389 }
390 }
391
392 items
393 }
394
395 async fn hover(
396 &self,
397 sql: &str,
398 _position: Position,
399 schema: Option<&Schema>,
400 ) -> Option<Hover> {
401 if let Some(schema) = schema {
402 for table in &schema.tables {
403 if sql.contains(&table.name) {
404 return Some(Hover {
405 contents: tower_lsp::lsp_types::HoverContents::Scalar(
406 MarkedString::String(format!(
407 "ClickHouse Table: {}.{}\n{}",
408 schema.database,
409 table.name,
410 table.comment.as_deref().unwrap_or("No description")
411 )),
412 ),
413 range: None,
414 });
415 }
416 }
417 }
418 None
419 }
420
421 async fn goto_definition(
422 &self,
423 _sql: &str,
424 _position: Position,
425 _schema: Option<&Schema>,
426 ) -> Option<Location> {
427 None
428 }
429
430 async fn references(
431 &self,
432 _sql: &str,
433 _position: Position,
434 _schema: Option<&Schema>,
435 ) -> Vec<Location> {
436 Vec::new()
437 }
438
439 async fn format(&self, sql: &str) -> String {
440 sql.split_whitespace().collect::<Vec<_>>().join(" ")
441 }
442
443 async fn validate(&self, sql: &str, schema: Option<&Schema>) -> Vec<Diagnostic> {
444 self.parse(sql, schema).await
445 }
446}