sql_lsp/dialects/
hive.rs

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 HiveDialect {
10    parser: std::sync::Mutex<SqlParser>,
11}
12
13impl Default for HiveDialect {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl HiveDialect {
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!("Hive 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 HiveDialect {
114    fn name(&self) -> &str {
115        "hive"
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            "OVERWRITE",
150            "INTO",
151            "TABLE",
152            "PARTITION",
153            "CREATE",
154            "DROP",
155            "ALTER",
156            "LOAD",
157            "DATA",
158            "LOCAL",
159            "INPATH",
160            "EXTERNAL",
161            "STORED",
162            "AS",
163            "TEXTFILE",
164            "PARQUET",
165            "ORC",
166            "SEQUENCEFILE",
167            "RCFILE",
168            "JOIN",
169            "INNER",
170            "LEFT",
171            "RIGHT",
172            "FULL",
173            "OUTER",
174            "ON",
175            "GROUP",
176            "BY",
177            "ORDER",
178            "SORT",
179            "CLUSTER",
180            "DISTRIBUTE",
181            "HAVING",
182            "LIMIT",
183            "UNION",
184            "ALL",
185            "DISTINCT",
186            "AND",
187            "OR",
188            "NOT",
189            "IN",
190            "LIKE",
191            "RLIKE",
192            "BETWEEN",
193            "IS",
194            "NULL",
195            "CASE",
196            "WHEN",
197            "THEN",
198            "ELSE",
199            "END",
200            "CAST",
201            "ARRAY",
202            "MAP",
203            "STRUCT",
204        ];
205
206        match context {
207            crate::parser::CompletionContext::FromClause
208            | crate::parser::CompletionContext::JoinClause => {
209                let join_keywords: Vec<&str> = keywords
210                    .iter()
211                    .filter(|&&k| matches!(k, "JOIN" | "INNER" | "LEFT" | "RIGHT" | "OUTER" | "ON"))
212                    .copied()
213                    .collect();
214                for keyword in join_keywords {
215                    items.push(self.create_keyword_item(keyword));
216                }
217                if let Some(schema) = schema {
218                    for table in &schema.tables {
219                        items.push(self.create_table_item(table));
220                    }
221                }
222            }
223            crate::parser::CompletionContext::SelectClause => {
224                let select_keywords: Vec<&str> = keywords
225                    .iter()
226                    .filter(|&&k| matches!(k, "SELECT" | "DISTINCT" | "AS" | "FROM"))
227                    .copied()
228                    .collect();
229                for keyword in select_keywords {
230                    items.push(self.create_keyword_item(keyword));
231                }
232                if let Some(schema) = schema {
233                    for table in &schema.tables {
234                        for column in &table.columns {
235                            items.push(self.create_column_item(
236                                column,
237                                Some(&format!("{}.{}", schema.database, table.name)),
238                            ));
239                        }
240                    }
241                }
242            }
243            crate::parser::CompletionContext::WhereClause => {
244                let where_keywords: Vec<&str> = keywords
245                    .iter()
246                    .filter(|&&k| {
247                        matches!(
248                            k,
249                            "AND"
250                                | "OR"
251                                | "NOT"
252                                | "IN"
253                                | "LIKE"
254                                | "RLIKE"
255                                | "BETWEEN"
256                                | "IS"
257                                | "NULL"
258                        )
259                    })
260                    .copied()
261                    .collect();
262                for keyword in where_keywords {
263                    items.push(self.create_keyword_item(keyword));
264                }
265                let operators = vec!["=", "<>", "!=", ">", "<", ">=", "<="];
266                for op in operators {
267                    items.push(CompletionItem {
268                        label: op.to_string(),
269                        kind: Some(CompletionItemKind::OPERATOR),
270                        detail: Some(format!("Operator: {}", op)),
271                        documentation: None,
272                        deprecated: None,
273                        preselect: None,
274                        sort_text: Some(format!("1{}", op)),
275                        filter_text: None,
276                        insert_text: Some(op.to_string()),
277                        insert_text_format: None,
278                        insert_text_mode: None,
279                        text_edit: None,
280                        additional_text_edits: None,
281                        commit_characters: None,
282                        command: None,
283                        data: None,
284                        tags: None,
285                        label_details: None,
286                    });
287                }
288                if let Some(schema) = schema {
289                    for table in &schema.tables {
290                        for column in &table.columns {
291                            items.push(self.create_column_item(
292                                column,
293                                Some(&format!("{}.{}", schema.database, table.name)),
294                            ));
295                        }
296                    }
297                }
298            }
299            crate::parser::CompletionContext::OrderByClause
300            | crate::parser::CompletionContext::GroupByClause => {
301                let keywords_list: Vec<&str> = keywords
302                    .iter()
303                    .filter(|&&k| {
304                        matches!(k, "ASC" | "DESC" | "BY" | "SORT" | "CLUSTER" | "DISTRIBUTE")
305                    })
306                    .copied()
307                    .collect();
308                for keyword in keywords_list {
309                    items.push(self.create_keyword_item(keyword));
310                }
311                if let Some(schema) = schema {
312                    for table in &schema.tables {
313                        for column in &table.columns {
314                            items.push(self.create_column_item(
315                                column,
316                                Some(&format!("{}.{}", schema.database, table.name)),
317                            ));
318                        }
319                    }
320                }
321            }
322            crate::parser::CompletionContext::HavingClause => {
323                let having_keywords: Vec<&str> = keywords
324                    .iter()
325                    .filter(|&&k| {
326                        matches!(
327                            k,
328                            "AND"
329                                | "OR"
330                                | "NOT"
331                                | "IN"
332                                | "LIKE"
333                                | "RLIKE"
334                                | "BETWEEN"
335                                | "IS"
336                                | "NULL"
337                        )
338                    })
339                    .copied()
340                    .collect();
341                for keyword in having_keywords {
342                    items.push(self.create_keyword_item(keyword));
343                }
344                let aggregate_functions = vec!["COUNT", "SUM", "AVG", "MIN", "MAX"];
345                for func in aggregate_functions {
346                    items.push(self.create_keyword_item(func));
347                }
348                if let Some(schema) = schema {
349                    for table in &schema.tables {
350                        for column in &table.columns {
351                            items.push(self.create_column_item(
352                                column,
353                                Some(&format!("{}.{}", schema.database, table.name)),
354                            ));
355                        }
356                    }
357                }
358            }
359            crate::parser::CompletionContext::TableColumn => {
360                if let Some(tree) = &parse_result.tree {
361                    if let Some(node) = parser.get_node_at_position(tree, position) {
362                        if let Some(table_name) = parser.get_table_name_for_column(node, sql) {
363                            if let Some(schema) = schema {
364                                if let Some(table) = schema.tables.iter().find(|t| {
365                                    t.name == table_name
366                                        || format!("{}.{}", schema.database, t.name) == table_name
367                                }) {
368                                    for column in &table.columns {
369                                        items.push(self.create_column_item(column, None));
370                                    }
371                                }
372                            }
373                        }
374                    }
375                }
376            }
377            crate::parser::CompletionContext::Default => {
378                for keyword in keywords {
379                    items.push(self.create_keyword_item(keyword));
380                }
381                if let Some(schema) = schema {
382                    for table in &schema.tables {
383                        items.push(self.create_table_item(table));
384                    }
385                }
386            }
387        }
388
389        items
390    }
391
392    async fn hover(
393        &self,
394        sql: &str,
395        _position: Position,
396        schema: Option<&Schema>,
397    ) -> Option<Hover> {
398        if let Some(schema) = schema {
399            for table in &schema.tables {
400                if sql.contains(&table.name) {
401                    return Some(Hover {
402                        contents: tower_lsp::lsp_types::HoverContents::Scalar(
403                            MarkedString::String(format!(
404                                "Hive Table: {}.{}\n{}",
405                                schema.database,
406                                table.name,
407                                table.comment.as_deref().unwrap_or("No description")
408                            )),
409                        ),
410                        range: None,
411                    });
412                }
413            }
414        }
415        None
416    }
417
418    async fn goto_definition(
419        &self,
420        _sql: &str,
421        _position: Position,
422        _schema: Option<&Schema>,
423    ) -> Option<Location> {
424        None
425    }
426
427    async fn references(
428        &self,
429        _sql: &str,
430        _position: Position,
431        _schema: Option<&Schema>,
432    ) -> Vec<Location> {
433        Vec::new()
434    }
435
436    async fn format(&self, sql: &str) -> String {
437        sql.split_whitespace().collect::<Vec<_>>().join(" ")
438    }
439
440    async fn validate(&self, sql: &str, schema: Option<&Schema>) -> Vec<Diagnostic> {
441        self.parse(sql, schema).await
442    }
443}