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, Position,
7};
8
9pub struct MysqlDialect {
10 parser: std::sync::Mutex<SqlParser>,
11}
12
13impl Default for MysqlDialect {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl MysqlDialect {
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 {
28 CompletionItem {
29 label: keyword.to_string(),
30 kind: Some(CompletionItemKind::KEYWORD),
31 detail: Some(format!("MySQL keyword: {}", keyword)),
32 documentation: None,
33 deprecated: None,
34 preselect: None,
35 sort_text: Some(format!("0{}", keyword)),
36 filter_text: None,
37 insert_text: Some(keyword.to_string()),
38 insert_text_format: None,
39 insert_text_mode: None,
40 text_edit: None,
41 additional_text_edits: None,
42 commit_characters: None,
43 command: None,
44 data: None,
45 tags: None,
46 label_details: None,
47 }
48 }
49
50 fn create_table_item(&self, table: &crate::schema::Table) -> CompletionItem {
52 CompletionItem {
53 label: table.name.clone(),
54 kind: Some(CompletionItemKind::CLASS),
55 detail: Some(format!("Table: {}", table.name)),
56 documentation: table
57 .comment
58 .clone()
59 .map(tower_lsp::lsp_types::Documentation::String),
60 deprecated: None,
61 preselect: None,
62 sort_text: Some(format!("1{}", table.name)),
63 filter_text: None,
64 insert_text: Some(table.name.clone()),
65 insert_text_format: None,
66 insert_text_mode: None,
67 text_edit: None,
68 additional_text_edits: None,
69 commit_characters: None,
70 command: None,
71 data: None,
72 tags: None,
73 label_details: None,
74 }
75 }
76
77 fn create_column_item(
79 &self,
80 column: &crate::schema::Column,
81 table_name: Option<&str>,
82 ) -> CompletionItem {
83 let label = if let Some(table) = table_name {
84 format!("{}.{}", table, column.name)
85 } else {
86 column.name.clone()
87 };
88
89 let detail = if let Some(table) = table_name {
90 format!("Column: {}.{} ({})", table, column.name, column.data_type)
91 } else {
92 format!("Column: {} ({})", column.name, column.data_type)
93 };
94
95 CompletionItem {
96 label,
97 kind: Some(CompletionItemKind::FIELD),
98 detail: Some(detail),
99 documentation: column
100 .comment
101 .clone()
102 .map(tower_lsp::lsp_types::Documentation::String),
103 deprecated: None,
104 preselect: None,
105 sort_text: Some(format!("2{}", column.name)),
106 filter_text: None,
107 insert_text: Some(column.name.clone()),
108 insert_text_format: None,
109 insert_text_mode: None,
110 text_edit: None,
111 additional_text_edits: None,
112 commit_characters: None,
113 command: None,
114 data: None,
115 tags: None,
116 label_details: None,
117 }
118 }
119}
120
121#[async_trait]
122impl Dialect for MysqlDialect {
123 fn name(&self) -> &str {
124 "mysql"
125 }
126
127 async fn parse(&self, sql: &str, _schema: Option<&Schema>) -> Vec<Diagnostic> {
128 let mut parser = self.parser.lock().unwrap();
130 let parse_result = parser.parse(sql);
131 parse_result.diagnostics
132 }
133
134 async fn completion(
135 &self,
136 sql: &str,
137 position: Position,
138 schema: Option<&Schema>,
139 ) -> Vec<CompletionItem> {
140 let mut parser = self.parser.lock().unwrap();
141 let parse_result = parser.parse(sql);
142
143 let context = if let Some(tree) = &parse_result.tree {
145 if let Some(node) = parser.get_node_at_position(tree, position) {
146 parser.analyze_completion_context(node, sql, position)
147 } else {
148 crate::parser::CompletionContext::Default
149 }
150 } else {
151 crate::parser::CompletionContext::Default
152 };
153
154 let mut items = Vec::new();
155
156 match context {
158 crate::parser::CompletionContext::FromClause
159 | crate::parser::CompletionContext::JoinClause => {
160 if let Some(schema) = schema {
163 for table in &schema.tables {
164 items.push(self.create_table_item(table));
165 }
166 }
167 }
168
169 crate::parser::CompletionContext::SelectClause => {
170 let prefix = {
173 let lines: Vec<&str> = sql.lines().collect();
174 let line_text = lines.get(position.line as usize).unwrap_or(&"");
175 let text_before =
176 &line_text[..position.character.min(line_text.len() as u32) as usize];
177
178 text_before
180 .split(|c: char| c.is_whitespace() || c == ',' || c == '(')
181 .next_back()
182 .unwrap_or("")
183 .to_lowercase()
184 };
185
186 if let Some(schema) = schema {
188 let use_table_prefix = if let Some(tree) = &parse_result.tree {
190 let referenced_tables = parser.extract_referenced_tables(tree, sql);
191 referenced_tables.len() > 1
192 } else {
193 false
194 };
195
196 for table in &schema.tables {
197 for column in &table.columns {
198 let table_name = if use_table_prefix {
200 Some(table.name.as_str())
201 } else {
202 None
203 };
204 let mut item = self.create_column_item(column, table_name);
205
206 if !prefix.is_empty() && column.name.to_lowercase().starts_with(&prefix)
208 {
209 item.sort_text = Some(format!("00{}", column.name));
211 } else {
212 item.sort_text = Some(format!("01{}", column.name));
214 }
215
216 items.push(item);
217 }
218 }
219 }
220
221 let select_keywords = vec!["SELECT", "DISTINCT", "AS", "FROM"];
223 for keyword in select_keywords {
224 let mut item = self.create_keyword_item(keyword);
225 item.sort_text = Some(format!("1{}", keyword));
226 items.push(item);
227 }
228 }
229
230 crate::parser::CompletionContext::WhereClause => {
231 if let Some(schema) = schema {
234 if let Some(tree) = &parse_result.tree {
235 let referenced_tables = parser.extract_referenced_tables(tree, sql);
236 let aliases = parser.extract_aliases(tree, sql);
237
238 let mut real_table_names: Vec<String> = referenced_tables
240 .iter()
241 .map(|t| aliases.get(t).unwrap_or(t).clone())
242 .collect();
243 real_table_names.dedup();
244
245 let use_table_prefix = real_table_names.len() > 1;
247
248 for table in &schema.tables {
249 if real_table_names.contains(&table.name) {
250 for column in &table.columns {
251 let table_name = if use_table_prefix {
252 Some(table.name.as_str())
253 } else {
254 None
255 };
256 let mut item = self.create_column_item(column, table_name);
257 item.sort_text = Some(format!("0{}", column.name)); items.push(item);
259 }
260 }
261 }
262 }
263 }
264
265 let operators = vec!["LIKE", "IN", "BETWEEN", "IS NULL", "IS NOT NULL"];
268 for op in operators {
269 items.push(CompletionItem {
270 label: op.to_string(),
271 kind: Some(CompletionItemKind::OPERATOR),
272 detail: Some(format!("Operator: {}", op)),
273 documentation: None,
274 deprecated: None,
275 preselect: None,
276 sort_text: Some(format!("1{}", op)), filter_text: None,
278 insert_text: Some(op.to_string()),
279 insert_text_format: None,
280 insert_text_mode: None,
281 text_edit: None,
282 additional_text_edits: None,
283 commit_characters: None,
284 command: None,
285 data: None,
286 tags: None,
287 label_details: None,
288 });
289 }
290 }
291
292 crate::parser::CompletionContext::OrderByClause => {
293 if let Some(schema) = schema {
296 if let Some(tree) = &parse_result.tree {
298 let referenced_tables = parser.extract_referenced_tables(tree, sql);
299 let aliases = parser.extract_aliases(tree, sql);
300
301 let mut real_table_names: Vec<String> = referenced_tables
303 .iter()
304 .map(|t| aliases.get(t).unwrap_or(t).clone())
305 .collect();
306 real_table_names.dedup();
307
308 let use_table_prefix = real_table_names.len() > 1;
310
311 for table in &schema.tables {
312 if real_table_names.is_empty() || real_table_names.contains(&table.name)
314 {
315 for column in &table.columns {
316 let table_name = if use_table_prefix {
317 Some(table.name.as_str())
318 } else {
319 None
320 };
321 let mut item = self.create_column_item(column, table_name);
322 item.sort_text = Some(format!("0{}", column.name)); items.push(item);
324 }
325 }
326 }
327 } else {
328 for table in &schema.tables {
330 for column in &table.columns {
331 let mut item = self.create_column_item(column, None);
332 item.sort_text = Some(format!("0{}", column.name)); items.push(item);
334 }
335 }
336 }
337 }
338
339 let keywords = vec!["ASC", "DESC"];
341 for keyword in keywords {
342 let mut item = self.create_keyword_item(keyword);
343 item.sort_text = Some(format!("1{}", keyword)); items.push(item);
345 }
346 }
347
348 crate::parser::CompletionContext::GroupByClause => {
349 if let Some(schema) = schema {
352 if let Some(tree) = &parse_result.tree {
354 let referenced_tables = parser.extract_referenced_tables(tree, sql);
355 let aliases = parser.extract_aliases(tree, sql);
356
357 let mut real_table_names: Vec<String> = referenced_tables
359 .iter()
360 .map(|t| aliases.get(t).unwrap_or(t).clone())
361 .collect();
362 real_table_names.dedup();
363
364 let use_table_prefix = real_table_names.len() > 1;
366
367 for table in &schema.tables {
368 if real_table_names.is_empty() || real_table_names.contains(&table.name)
370 {
371 for column in &table.columns {
372 let table_name = if use_table_prefix {
373 Some(table.name.as_str())
374 } else {
375 None
376 };
377 let mut item = self.create_column_item(column, table_name);
378 item.sort_text = Some(format!("0{}", column.name)); items.push(item);
380 }
381 }
382 }
383 } else {
384 for table in &schema.tables {
386 for column in &table.columns {
387 let mut item = self.create_column_item(column, None);
388 item.sort_text = Some(format!("0{}", column.name)); items.push(item);
390 }
391 }
392 }
393 }
394 }
396
397 crate::parser::CompletionContext::HavingClause => {
398 if let Some(schema) = schema {
402 if let Some(tree) = &parse_result.tree {
405 let referenced_tables = parser.extract_referenced_tables(tree, sql);
406 let aliases = parser.extract_aliases(tree, sql);
407 let mut real_table_names: Vec<String> = referenced_tables
408 .iter()
409 .map(|t| aliases.get(t).unwrap_or(t).clone())
410 .collect();
411 real_table_names.dedup();
412 let use_table_prefix = real_table_names.len() > 1;
413
414 for table in &schema.tables {
415 if real_table_names.is_empty() || real_table_names.contains(&table.name)
416 {
417 for column in &table.columns {
418 let table_name = if use_table_prefix {
419 Some(table.name.as_str())
420 } else {
421 None
422 };
423 let mut item = self.create_column_item(column, table_name);
424 item.sort_text = Some(format!("0{}", column.name));
425 items.push(item);
426 }
427 }
428 }
429 }
430 }
431
432 let aggregate_functions = vec!["COUNT", "SUM", "AVG", "MIN", "MAX"];
434 for func in aggregate_functions {
435 let mut item = self.create_keyword_item(func);
436 item.kind = Some(CompletionItemKind::FUNCTION);
437 item.sort_text = Some(format!("1{}", func));
438 items.push(item);
439 }
440
441 let having_keywords =
444 vec!["AND", "OR", "NOT", "IN", "LIKE", "BETWEEN", "IS", "NULL"];
445 for keyword in having_keywords {
446 let mut item = self.create_keyword_item(keyword);
447 item.sort_text = Some(format!("2{}", keyword)); items.push(item);
449 }
450 }
451
452 crate::parser::CompletionContext::TableColumn => {
453 if let Some(tree) = &parse_result.tree {
455 if let Some(node) = parser.get_node_at_position(tree, position) {
456 if let Some(table_name) = parser.get_table_name_for_column(node, sql) {
457 if let Some(schema) = schema {
458 let aliases = parser.extract_aliases(tree, sql);
459
460 let real_table_name =
462 aliases.get(&table_name).unwrap_or(&table_name);
463
464 if let Some(table) =
465 schema.tables.iter().find(|t| t.name == *real_table_name)
466 {
467 for column in &table.columns {
468 items.push(self.create_column_item(column, None));
469 }
470 }
471 }
472 }
473 }
474 }
475 }
476
477 crate::parser::CompletionContext::Default => {
478 let keywords = vec![
480 "SELECT", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP",
481 "ALTER", "TABLE", "INDEX", "DATABASE", "SHOW", "DESCRIBE", "EXPLAIN", "JOIN",
482 "INNER", "LEFT", "RIGHT", "OUTER", "ON", "GROUP", "BY", "ORDER", "HAVING",
483 "LIMIT", "OFFSET", "UNION", "ALL", "DISTINCT", "AS", "AND", "OR", "NOT", "IN",
484 "LIKE", "BETWEEN", "IS", "NULL", "TRUE", "FALSE",
485 ];
486
487 for keyword in keywords {
488 items.push(self.create_keyword_item(keyword));
489 }
490
491 if let Some(schema) = schema {
493 for table in &schema.tables {
494 items.push(self.create_table_item(table));
495 }
496 }
497 }
498 }
499
500 items
501 }
502
503 async fn hover(&self, sql: &str, position: Position, schema: Option<&Schema>) -> Option<Hover> {
504 let mut parser = self.parser.lock().unwrap();
505 let parse_result = parser.parse(sql);
506
507 if let Some(tree) = &parse_result.tree {
509 if let Some(node) = parser.get_node_at_position(tree, position) {
510 let node_text = parser.node_text(node, sql);
511 let node_kind = node.kind();
512 let node_range = parser.node_range(node);
513
514 if crate::token::Keywords::is_keyword(&node_text)
516 || crate::token::Operators::is_operator(&node_text)
517 || crate::token::Delimiters::is_delimiter(&node_text)
518 {
519 return None;
520 }
521
522 if let Some(schema) = schema {
523 let is_table = node_kind == "table_name"
525 || node_kind == "table_reference"
526 || node_kind == "table_identifier"
527 || (node_kind == "identifier" && parser.is_in_from_context(node, sql));
528
529 if is_table {
530 if let Some(table) = schema.tables.iter().find(|t| t.name == node_text) {
531 let mut info = format!("**Table**: `{}`\n\n", table.name);
532 if let Some(comment) = &table.comment {
533 info.push_str(&format!("{}\n\n", comment));
534 }
535 info.push_str(&format!("**Columns** ({})\n", table.columns.len()));
536 for (idx, col) in table.columns.iter().take(10).enumerate() {
537 info.push_str(&format!(
538 "- `{}`: {} {}\n",
539 col.name,
540 col.data_type,
541 if col.nullable { "" } else { "NOT NULL" }
542 ));
543 if idx == 9 && table.columns.len() > 10 {
544 info.push_str(&format!(
545 "- ... and {} more\n",
546 table.columns.len() - 10
547 ));
548 break;
549 }
550 }
551
552 return Some(Hover {
553 contents: tower_lsp::lsp_types::HoverContents::Markup(
554 tower_lsp::lsp_types::MarkupContent {
555 kind: tower_lsp::lsp_types::MarkupKind::Markdown,
556 value: info,
557 },
558 ),
559 range: Some(node_range),
560 });
561 }
562 }
563
564 let is_column = node_kind == "column_name"
566 || node_kind == "column_reference"
567 || node_kind == "column_identifier"
568 || (node_kind == "identifier" && parser.is_in_column_context(node, sql));
569
570 if is_column {
571 let table_name = parser.get_table_name_for_column(node, sql);
573
574 for table in &schema.tables {
575 if let Some(ref tname) = table_name {
577 if table.name != *tname {
578 continue;
579 }
580 }
581
582 if let Some(column) = table.columns.iter().find(|c| c.name == node_text)
583 {
584 let mut info =
585 format!("**Column**: `{}.{}`\n\n", table.name, column.name);
586 info.push_str(&format!("**Type**: `{}`\n", column.data_type));
587 info.push_str(&format!(
588 "**Nullable**: {}\n",
589 if column.nullable { "Yes" } else { "No" }
590 ));
591 if let Some(comment) = &column.comment {
592 info.push_str(&format!("\n{}\n", comment));
593 }
594
595 return Some(Hover {
596 contents: tower_lsp::lsp_types::HoverContents::Markup(
597 tower_lsp::lsp_types::MarkupContent {
598 kind: tower_lsp::lsp_types::MarkupKind::Markdown,
599 value: info,
600 },
601 ),
602 range: Some(node_range),
603 });
604 }
605 }
606 }
607
608 if node_kind == "function_name" || node_kind.contains("function") {
610 if let Some(func) = schema.functions.iter().find(|f| f.name == node_text) {
611 let mut info = format!("**Function**: `{}`\n\n", func.name);
612 if let Some(desc) = &func.description {
613 info.push_str(&format!("{}\n\n", desc));
614 }
615 info.push_str(&format!("**Returns**: `{}`\n", func.return_type));
616 if !func.parameters.is_empty() {
617 info.push_str("\n**Parameters**:\n");
618 for param in &func.parameters {
619 info.push_str(&format!(
620 "- `{}`: `{}`{}\n",
621 param.name,
622 param.data_type,
623 if param.optional { " (optional)" } else { "" }
624 ));
625 }
626 }
627
628 return Some(Hover {
629 contents: tower_lsp::lsp_types::HoverContents::Markup(
630 tower_lsp::lsp_types::MarkupContent {
631 kind: tower_lsp::lsp_types::MarkupKind::Markdown,
632 value: info,
633 },
634 ),
635 range: Some(node_range),
636 });
637 }
638 }
639 }
640 }
641 }
642
643 None
644 }
645
646 async fn goto_definition(
647 &self,
648 sql: &str,
649 position: Position,
650 schema: Option<&Schema>,
651 ) -> Option<Location> {
652 let mut parser = self.parser.lock().unwrap();
653 let parse_result = parser.parse(sql);
654
655 if let Some(tree) = &parse_result.tree {
657 if let Some(node) = parser.get_node_at_position(tree, position) {
658 let node_text = parser.node_text(node, sql);
659 let node_kind = node.kind();
660
661 if crate::token::Keywords::is_keyword(&node_text)
663 || crate::token::Operators::is_operator(&node_text)
664 || crate::token::Delimiters::is_delimiter(&node_text)
665 {
666 return None;
667 }
668
669 let is_table = node_kind == "table_name"
671 || node_kind == "table_reference"
672 || node_kind == "table_identifier"
673 || (node_kind == "identifier" && parser.is_in_from_context(node, sql));
674
675 let is_column = node_kind == "column_name"
676 || node_kind == "column_reference"
677 || node_kind == "column_identifier"
678 || (node_kind == "identifier" && parser.is_in_column_context(node, sql));
679
680 if is_table {
682 if let Some(schema) = schema {
683 if let Some(table) = schema.tables.iter().find(|t| t.name == node_text) {
684 let (uri, line) = if let Some((ref source_uri, source_line)) =
686 table.source_location
687 {
688 (
689 tower_lsp::lsp_types::Url::parse(source_uri).unwrap_or_else(
690 |_| {
691 tower_lsp::lsp_types::Url::parse("file:///schema.sql")
692 .unwrap()
693 },
694 ),
695 source_line.saturating_sub(1), )
697 } else if let Some(ref schema_uri) = schema.source_uri {
698 (
700 tower_lsp::lsp_types::Url::parse(schema_uri).unwrap_or_else(
701 |_| {
702 tower_lsp::lsp_types::Url::parse("file:///schema.sql")
703 .unwrap()
704 },
705 ),
706 0,
707 )
708 } else {
709 (
711 tower_lsp::lsp_types::Url::parse("file:///schema.sql").unwrap(),
712 0,
713 )
714 };
715
716 return Some(Location {
717 uri,
718 range: tower_lsp::lsp_types::Range {
719 start: tower_lsp::lsp_types::Position { line, character: 0 },
720 end: tower_lsp::lsp_types::Position {
721 line,
722 character: 100,
723 },
724 },
725 });
726 }
727 }
728 }
729
730 if is_column {
732 if let Some(schema) = schema {
733 let (table_name, column_name) =
735 if let Some(table_name) = parser.get_table_name_for_column(node, sql) {
736 (Some(table_name), node_text.clone())
737 } else {
738 let tables = parser.extract_tables(tree, sql);
740 let table_name = tables.first().cloned();
741 (table_name, node_text.clone())
742 };
743
744 for table in &schema.tables {
746 if let Some(ref tname) = table_name {
747 if table.name == *tname
748 && table.columns.iter().any(|c| c.name == column_name)
749 {
750 return Some(Location {
752 uri: tower_lsp::lsp_types::Url::parse("file:///schema.sql")
753 .unwrap_or_else(|_| {
754 tower_lsp::lsp_types::Url::parse("file:///")
755 .unwrap()
756 }),
757 range: parser.node_range(node),
758 });
759 }
760 } else if table.columns.iter().any(|c| c.name == column_name) {
761 return Some(Location {
763 uri: tower_lsp::lsp_types::Url::parse("file:///schema.sql")
764 .unwrap_or_else(|_| {
765 tower_lsp::lsp_types::Url::parse("file:///").unwrap()
766 }),
767 range: parser.node_range(node),
768 });
769 }
770 }
771 }
772 }
773 }
774 }
775
776 None
777 }
778
779 async fn references(
780 &self,
781 sql: &str,
782 position: Position,
783 _schema: Option<&Schema>,
784 ) -> Vec<Location> {
785 let mut parser = self.parser.lock().unwrap();
786 let parse_result = parser.parse(sql);
787
788 let mut locations = Vec::new();
789
790 if let Some(tree) = &parse_result.tree {
792 if let Some(node) = parser.get_node_at_position(tree, position) {
793 let identifier = parser.node_text(node, sql);
794 let node_kind = node.kind();
795
796 if crate::token::Keywords::is_keyword(&identifier)
798 || crate::token::Operators::is_operator(&identifier)
799 || crate::token::Delimiters::is_delimiter(&identifier)
800 {
801 return locations;
802 }
803
804 let is_table = node_kind == "table_name"
806 || node_kind == "table_reference"
807 || node_kind == "table_identifier"
808 || (node_kind == "identifier" && parser.is_in_from_context(node, sql));
809
810 let is_column = node_kind == "column_name"
811 || node_kind == "column_reference"
812 || node_kind == "column_identifier"
813 || (node_kind == "identifier" && parser.is_in_column_context(node, sql));
814
815 if is_table || is_column {
816 let tokens = parser.tokenize(tree, sql);
818 let current_uri = tower_lsp::lsp_types::Url::parse("file:///current.sql")
819 .unwrap_or_else(|_| tower_lsp::lsp_types::Url::parse("file:///").unwrap());
820
821 for token in tokens {
822 if token.text.eq_ignore_ascii_case(&identifier)
824 && !crate::token::Keywords::is_keyword(&token.text)
825 && !crate::token::Operators::is_operator(&token.text)
826 && !crate::token::Delimiters::is_delimiter(&token.text)
827 {
828 locations.push(Location {
830 uri: current_uri.clone(),
831 range: tower_lsp::lsp_types::Range {
832 start: token.position,
833 end: tower_lsp::lsp_types::Position {
834 line: token.position.line,
835 character: token.position.character
836 + token.text.len() as u32,
837 },
838 },
839 });
840 }
841 }
842 }
843 }
844 }
845
846 locations
847 }
848
849 async fn format(&self, sql: &str) -> String {
850 use sqlformat::{FormatOptions, Indent, QueryParams};
851 let options = FormatOptions {
852 indent: Indent::Spaces(2),
853 uppercase: true,
854 lines_between_queries: 1,
855 };
856 sqlformat::format(sql, &QueryParams::None, options)
857 }
858
859 async fn validate(&self, sql: &str, schema: Option<&Schema>) -> Vec<Diagnostic> {
860 self.parse(sql, schema).await
861 }
862}