1use rowan::TextRange;
2use salsa::Database as Db;
3use squawk_syntax::ast::{self, AstNode};
4
5use crate::binder::{self, extract_string_literal};
6use crate::db::{File, parse};
7use crate::resolve::{
8 resolve_aggregate_info, resolve_function_info, resolve_procedure_info, resolve_sequence_info,
9 resolve_table_info, resolve_type_info, resolve_view_info,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum DocumentSymbolKind {
14 Schema,
15 Table,
16 View,
17 MaterializedView,
18 Function,
19 Aggregate,
20 Procedure,
21 EventTrigger,
22 Role,
23 Policy,
24 Type,
25 Enum,
26 Index,
27 Domain,
28 Sequence,
29 Trigger,
30 Tablespace,
31 Database,
32 Server,
33 Extension,
34 Column,
35 Variant,
36 Cursor,
37 PreparedStatement,
38 Channel,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct DocumentSymbol {
43 pub name: String,
44 pub detail: Option<String>,
45 pub kind: DocumentSymbolKind,
46 pub full_range: TextRange,
49 pub focus_range: TextRange,
51 pub children: Vec<DocumentSymbol>,
52}
53
54#[salsa::tracked]
55pub fn document_symbols(db: &dyn Db, file: File) -> Vec<DocumentSymbol> {
56 let parse = parse(db, file);
57 let source_file = parse.tree();
58
59 let binder = binder::bind(&source_file);
61 let mut symbols = vec![];
62
63 for stmt in source_file.stmts() {
64 match stmt {
65 ast::Stmt::CreateSchema(create_schema) => {
66 if let Some(symbol) = create_schema_symbol(create_schema) {
67 symbols.push(symbol);
68 }
69 }
70 ast::Stmt::CreateTable(create_table) => {
71 if let Some(symbol) = create_table_symbol(&binder, create_table) {
72 symbols.push(symbol);
73 }
74 }
75 ast::Stmt::CreateTableAs(create_table_as) => {
76 if let Some(symbol) = create_table_as_symbol(&binder, create_table_as) {
77 symbols.push(symbol);
78 }
79 }
80 ast::Stmt::CreateForeignTable(create_foreign_table) => {
81 if let Some(symbol) = create_table_symbol(&binder, create_foreign_table) {
82 symbols.push(symbol);
83 }
84 }
85 ast::Stmt::CreateFunction(create_function) => {
86 if let Some(symbol) = create_function_symbol(&binder, create_function) {
87 symbols.push(symbol);
88 }
89 }
90 ast::Stmt::CreateAggregate(create_aggregate) => {
91 if let Some(symbol) = create_aggregate_symbol(&binder, create_aggregate) {
92 symbols.push(symbol);
93 }
94 }
95 ast::Stmt::CreateProcedure(create_procedure) => {
96 if let Some(symbol) = create_procedure_symbol(&binder, create_procedure) {
97 symbols.push(symbol);
98 }
99 }
100 ast::Stmt::CreateIndex(create_index) => {
101 if let Some(symbol) = create_index_symbol(create_index) {
102 symbols.push(symbol);
103 }
104 }
105 ast::Stmt::CreateDomain(create_domain) => {
106 if let Some(symbol) = create_domain_symbol(&binder, create_domain) {
107 symbols.push(symbol);
108 }
109 }
110 ast::Stmt::CreateSequence(create_sequence) => {
111 if let Some(symbol) = create_sequence_symbol(&binder, create_sequence) {
112 symbols.push(symbol);
113 }
114 }
115 ast::Stmt::CreateTrigger(create_trigger) => {
116 if let Some(symbol) = create_trigger_symbol(create_trigger) {
117 symbols.push(symbol);
118 }
119 }
120 ast::Stmt::CreateEventTrigger(create_event_trigger) => {
121 if let Some(symbol) = create_event_trigger_symbol(create_event_trigger) {
122 symbols.push(symbol);
123 }
124 }
125 ast::Stmt::CreateTablespace(create_tablespace) => {
126 if let Some(symbol) = create_tablespace_symbol(create_tablespace) {
127 symbols.push(symbol);
128 }
129 }
130 ast::Stmt::CreateDatabase(create_database) => {
131 if let Some(symbol) = create_database_symbol(create_database) {
132 symbols.push(symbol);
133 }
134 }
135 ast::Stmt::CreateServer(create_server) => {
136 if let Some(symbol) = create_server_symbol(create_server) {
137 symbols.push(symbol);
138 }
139 }
140 ast::Stmt::CreateExtension(create_extension) => {
141 if let Some(symbol) = create_extension_symbol(create_extension) {
142 symbols.push(symbol);
143 }
144 }
145 ast::Stmt::CreateRole(create_role) => {
146 if let Some(symbol) = create_role_symbol(create_role) {
147 symbols.push(symbol);
148 }
149 }
150 ast::Stmt::CreatePolicy(create_policy) => {
151 if let Some(symbol) = create_policy_symbol(create_policy) {
152 symbols.push(symbol);
153 }
154 }
155 ast::Stmt::CreateType(create_type) => {
156 if let Some(symbol) = create_type_symbol(&binder, create_type) {
157 symbols.push(symbol);
158 }
159 }
160 ast::Stmt::CreateView(create_view) => {
161 if let Some(symbol) = create_view_symbol(&binder, create_view) {
162 symbols.push(symbol);
163 }
164 }
165 ast::Stmt::CreateMaterializedView(create_view) => {
166 if let Some(symbol) = create_materialized_view_symbol(&binder, create_view) {
167 symbols.push(symbol);
168 }
169 }
170 ast::Stmt::Declare(declare) => {
171 if let Some(symbol) = create_declare_cursor_symbol(declare) {
172 symbols.push(symbol);
173 }
174 }
175 ast::Stmt::Prepare(prepare) => {
176 if let Some(symbol) = create_prepare_symbol(prepare) {
177 symbols.push(symbol);
178 }
179 }
180 ast::Stmt::Select(select) => {
181 symbols.extend(cte_table_symbols(select));
182 }
183 ast::Stmt::SelectInto(select_into) => {
184 symbols.extend(cte_table_symbols(select_into));
185 }
186 ast::Stmt::Insert(insert) => {
187 symbols.extend(cte_table_symbols(insert));
188 }
189 ast::Stmt::Update(update) => {
190 symbols.extend(cte_table_symbols(update));
191 }
192 ast::Stmt::Delete(delete) => {
193 symbols.extend(cte_table_symbols(delete));
194 }
195 ast::Stmt::Listen(listen) => {
196 if let Some(symbol) = create_listen_symbol(listen) {
197 symbols.push(symbol);
198 }
199 }
200 ast::Stmt::Notify(notify) => {
201 if let Some(symbol) = create_notify_symbol(notify) {
202 symbols.push(symbol);
203 }
204 }
205 ast::Stmt::Unlisten(unlisten) => {
206 if let Some(symbol) = create_unlisten_symbol(unlisten) {
207 symbols.push(symbol);
208 }
209 }
210
211 _ => (),
212 }
213 }
214
215 symbols
216}
217
218fn cte_table_symbols(stmt: impl ast::HasWithClause) -> Vec<DocumentSymbol> {
219 let Some(with_clause) = stmt.with_clause() else {
220 return vec![];
221 };
222
223 with_clause
224 .with_tables()
225 .filter_map(create_cte_table_symbol)
226 .collect()
227}
228
229fn create_cte_table_symbol(with_table: ast::WithTable) -> Option<DocumentSymbol> {
230 let name_node = with_table.name()?;
231 let name = name_node.syntax().text().to_string();
232
233 let full_range = with_table.syntax().text_range();
234 let focus_range = name_node.syntax().text_range();
235
236 symbols_from_column_list(
237 with_table.column_list(),
238 name,
239 full_range,
240 focus_range,
241 DocumentSymbolKind::Table,
242 )
243}
244
245fn create_schema_symbol(create_schema: ast::CreateSchema) -> Option<DocumentSymbol> {
246 let (name, focus_range) = if let Some(name_node) = create_schema.name() {
247 (
248 name_node.syntax().text().to_string(),
249 name_node.syntax().text_range(),
250 )
251 } else if let Some(name) = create_schema.role().and_then(|r| r.name()) {
252 (name.syntax().text().to_string(), name.syntax().text_range())
253 } else {
254 return None;
255 };
256
257 let full_range = create_schema.syntax().text_range();
258
259 Some(DocumentSymbol {
260 name,
261 detail: None,
262 kind: DocumentSymbolKind::Schema,
263 full_range,
264 focus_range,
265 children: vec![],
266 })
267}
268
269fn create_table_symbol(
270 binder: &binder::Binder,
271 create_table: impl ast::HasCreateTable,
272) -> Option<DocumentSymbol> {
273 let path = create_table.path()?;
274 let segment = path.segment()?;
275 let name_node = segment.name()?;
276
277 let (schema, table_name) = resolve_table_info(binder, &path)?;
278 let name = format!("{}.{}", schema.0, table_name);
279
280 let full_range = create_table.syntax().text_range();
281 let focus_range = name_node.syntax().text_range();
282
283 let mut children = vec![];
284 if let Some(table_arg_list) = create_table.table_arg_list() {
285 for arg in table_arg_list.args() {
286 if let ast::TableArg::Column(column) = arg
287 && let Some(column_symbol) = create_column_symbol(column)
288 {
289 children.push(column_symbol);
290 }
291 }
292 }
293
294 Some(DocumentSymbol {
295 name,
296 detail: None,
297 kind: DocumentSymbolKind::Table,
298 full_range,
299 focus_range,
300 children,
301 })
302}
303
304fn create_table_as_symbol(
305 binder: &binder::Binder,
306 create_table_as: ast::CreateTableAs,
307) -> Option<DocumentSymbol> {
308 let path = create_table_as.path()?;
309 let segment = path.segment()?;
310 let name_node = if let Some(name) = segment.name() {
311 name.syntax().clone()
312 } else {
313 return None;
314 };
315
316 let (schema, table_name) = resolve_table_info(binder, &path)?;
317 let name = format!("{}.{}", schema.0, table_name);
318
319 let full_range = create_table_as.syntax().text_range();
320 let focus_range = name_node.text_range();
321
322 Some(DocumentSymbol {
323 name,
324 detail: None,
325 kind: DocumentSymbolKind::Table,
326 full_range,
327 focus_range,
328 children: vec![],
331 })
332}
333
334fn create_view_symbol(
335 binder: &binder::Binder,
336 create_view: ast::CreateView,
337) -> Option<DocumentSymbol> {
338 let path = create_view.path()?;
339 let segment = path.segment()?;
340 let name_node = segment.name()?;
341
342 let (schema, view_name) = resolve_view_info(binder, &path)?;
343 let name = format!("{}.{}", schema.0, view_name);
344
345 let full_range = create_view.syntax().text_range();
346 let focus_range = name_node.syntax().text_range();
347
348 symbols_from_column_list(
349 create_view.column_list(),
350 name,
351 full_range,
352 focus_range,
353 DocumentSymbolKind::View,
354 )
355}
356
357fn symbols_from_column_list(
358 column_list: Option<ast::ColumnList>,
359 name: String,
360 full_range: TextRange,
361 focus_range: TextRange,
362 kind: DocumentSymbolKind,
363) -> Option<DocumentSymbol> {
364 let mut children = vec![];
365 if let Some(column_list) = column_list {
366 for column in column_list.columns() {
367 if let Some(column_symbol) = create_column_symbol(column) {
368 children.push(column_symbol);
369 }
370 }
371 }
372
373 Some(DocumentSymbol {
374 name,
375 detail: None,
376 kind,
377 full_range,
378 focus_range,
379 children,
380 })
381}
382
383fn create_materialized_view_symbol(
385 binder: &binder::Binder,
386 create_view: ast::CreateMaterializedView,
387) -> Option<DocumentSymbol> {
388 let path = create_view.path()?;
389 let segment = path.segment()?;
390 let name_node = segment.name()?;
391
392 let (schema, view_name) = resolve_view_info(binder, &path)?;
393 let name = format!("{}.{}", schema.0, view_name);
394
395 let full_range = create_view.syntax().text_range();
396 let focus_range = name_node.syntax().text_range();
397
398 symbols_from_column_list(
399 create_view.column_list(),
400 name,
401 full_range,
402 focus_range,
403 DocumentSymbolKind::MaterializedView,
404 )
405}
406
407fn create_function_symbol(
408 binder: &binder::Binder,
409 create_function: ast::CreateFunction,
410) -> Option<DocumentSymbol> {
411 let path = create_function.path()?;
412 let segment = path.segment()?;
413 let name_node = segment.name()?;
414
415 let (schema, function_name) = resolve_function_info(binder, &path)?;
416 let name = format!("{}.{}", schema.0, function_name);
417
418 let full_range = create_function.syntax().text_range();
419 let focus_range = name_node.syntax().text_range();
420
421 Some(DocumentSymbol {
422 name,
423 detail: None,
424 kind: DocumentSymbolKind::Function,
425 full_range,
426 focus_range,
427 children: vec![],
428 })
429}
430
431fn create_aggregate_symbol(
432 binder: &binder::Binder,
433 create_aggregate: ast::CreateAggregate,
434) -> Option<DocumentSymbol> {
435 let path = create_aggregate.path()?;
436 let segment = path.segment()?;
437 let name_node = segment.name()?;
438
439 let (schema, aggregate_name) = resolve_aggregate_info(binder, &path)?;
440 let name = format!("{}.{}", schema.0, aggregate_name);
441
442 let full_range = create_aggregate.syntax().text_range();
443 let focus_range = name_node.syntax().text_range();
444
445 Some(DocumentSymbol {
446 name,
447 detail: None,
448 kind: DocumentSymbolKind::Aggregate,
449 full_range,
450 focus_range,
451 children: vec![],
452 })
453}
454
455fn create_procedure_symbol(
456 binder: &binder::Binder,
457 create_procedure: ast::CreateProcedure,
458) -> Option<DocumentSymbol> {
459 let path = create_procedure.path()?;
460 let segment = path.segment()?;
461 let name_node = segment.name()?;
462
463 let (schema, procedure_name) = resolve_procedure_info(binder, &path)?;
464 let name = format!("{}.{}", schema.0, procedure_name);
465
466 let full_range = create_procedure.syntax().text_range();
467 let focus_range = name_node.syntax().text_range();
468
469 Some(DocumentSymbol {
470 name,
471 detail: None,
472 kind: DocumentSymbolKind::Procedure,
473 full_range,
474 focus_range,
475 children: vec![],
476 })
477}
478
479fn create_index_symbol(create_index: ast::CreateIndex) -> Option<DocumentSymbol> {
480 let name_node = create_index.name()?;
481 let name = name_node.syntax().text().to_string();
482
483 let full_range = create_index.syntax().text_range();
484 let focus_range = name_node.syntax().text_range();
485
486 Some(DocumentSymbol {
487 name,
488 detail: None,
489 kind: DocumentSymbolKind::Index,
490 full_range,
491 focus_range,
492 children: vec![],
493 })
494}
495
496fn create_domain_symbol(
497 binder: &binder::Binder,
498 create_domain: ast::CreateDomain,
499) -> Option<DocumentSymbol> {
500 let path = create_domain.path()?;
501 let segment = path.segment()?;
502 let name_node = segment.name()?;
503
504 let (schema, domain_name) = resolve_type_info(binder, &path)?;
505 let name = format!("{}.{}", schema.0, domain_name);
506
507 let full_range = create_domain.syntax().text_range();
508 let focus_range = name_node.syntax().text_range();
509
510 Some(DocumentSymbol {
511 name,
512 detail: None,
513 kind: DocumentSymbolKind::Domain,
514 full_range,
515 focus_range,
516 children: vec![],
517 })
518}
519
520fn create_sequence_symbol(
521 binder: &binder::Binder,
522 create_sequence: ast::CreateSequence,
523) -> Option<DocumentSymbol> {
524 let path = create_sequence.path()?;
525 let segment = path.segment()?;
526 let name_node = segment.name()?;
527
528 let (schema, sequence_name) = resolve_sequence_info(binder, &path)?;
529 let name = format!("{}.{}", schema.0, sequence_name);
530
531 let full_range = create_sequence.syntax().text_range();
532 let focus_range = name_node.syntax().text_range();
533
534 Some(DocumentSymbol {
535 name,
536 detail: None,
537 kind: DocumentSymbolKind::Sequence,
538 full_range,
539 focus_range,
540 children: vec![],
541 })
542}
543
544fn create_trigger_symbol(create_trigger: ast::CreateTrigger) -> Option<DocumentSymbol> {
545 let name_node = create_trigger.name()?;
546 let name = name_node.syntax().text().to_string();
547
548 let full_range = create_trigger.syntax().text_range();
549 let focus_range = name_node.syntax().text_range();
550
551 Some(DocumentSymbol {
552 name,
553 detail: None,
554 kind: DocumentSymbolKind::Trigger,
555 full_range,
556 focus_range,
557 children: vec![],
558 })
559}
560
561fn create_event_trigger_symbol(
562 create_event_trigger: ast::CreateEventTrigger,
563) -> Option<DocumentSymbol> {
564 let name_node = create_event_trigger.name()?;
565 let name = name_node.syntax().text().to_string();
566
567 let full_range = create_event_trigger.syntax().text_range();
568 let focus_range = name_node.syntax().text_range();
569
570 Some(DocumentSymbol {
571 name,
572 detail: None,
573 kind: DocumentSymbolKind::EventTrigger,
574 full_range,
575 focus_range,
576 children: vec![],
577 })
578}
579
580fn create_tablespace_symbol(create_tablespace: ast::CreateTablespace) -> Option<DocumentSymbol> {
581 let name_node = create_tablespace.name()?;
582 let name = name_node.syntax().text().to_string();
583
584 let full_range = create_tablespace.syntax().text_range();
585 let focus_range = name_node.syntax().text_range();
586
587 Some(DocumentSymbol {
588 name,
589 detail: None,
590 kind: DocumentSymbolKind::Tablespace,
591 full_range,
592 focus_range,
593 children: vec![],
594 })
595}
596
597fn create_database_symbol(create_database: ast::CreateDatabase) -> Option<DocumentSymbol> {
598 let name_node = create_database.name()?;
599 let name = name_node.syntax().text().to_string();
600
601 let full_range = create_database.syntax().text_range();
602 let focus_range = name_node.syntax().text_range();
603
604 Some(DocumentSymbol {
605 name,
606 detail: None,
607 kind: DocumentSymbolKind::Database,
608 full_range,
609 focus_range,
610 children: vec![],
611 })
612}
613
614fn create_server_symbol(create_server: ast::CreateServer) -> Option<DocumentSymbol> {
615 let name_node = create_server.name()?;
616 let name = name_node.syntax().text().to_string();
617
618 let full_range = create_server.syntax().text_range();
619 let focus_range = name_node.syntax().text_range();
620
621 Some(DocumentSymbol {
622 name,
623 detail: None,
624 kind: DocumentSymbolKind::Server,
625 full_range,
626 focus_range,
627 children: vec![],
628 })
629}
630
631fn create_extension_symbol(create_extension: ast::CreateExtension) -> Option<DocumentSymbol> {
632 let name_node = create_extension.name()?;
633 let name = name_node.syntax().text().to_string();
634
635 let full_range = create_extension.syntax().text_range();
636 let focus_range = name_node.syntax().text_range();
637
638 Some(DocumentSymbol {
639 name,
640 detail: None,
641 kind: DocumentSymbolKind::Extension,
642 full_range,
643 focus_range,
644 children: vec![],
645 })
646}
647
648fn create_role_symbol(create_role: ast::CreateRole) -> Option<DocumentSymbol> {
649 let name_node = create_role.name()?;
650 let name = name_node.syntax().text().to_string();
651
652 let full_range = create_role.syntax().text_range();
653 let focus_range = name_node.syntax().text_range();
654
655 Some(DocumentSymbol {
656 name,
657 detail: None,
658 kind: DocumentSymbolKind::Role,
659 full_range,
660 focus_range,
661 children: vec![],
662 })
663}
664
665fn create_policy_symbol(create_policy: ast::CreatePolicy) -> Option<DocumentSymbol> {
666 let name_node = create_policy.name()?;
667 let name = name_node.syntax().text().to_string();
668
669 let full_range = create_policy.syntax().text_range();
670 let focus_range = name_node.syntax().text_range();
671
672 Some(DocumentSymbol {
673 name,
674 detail: None,
675 kind: DocumentSymbolKind::Policy,
676 full_range,
677 focus_range,
678 children: vec![],
679 })
680}
681
682fn create_type_symbol(
683 binder: &binder::Binder,
684 create_type: ast::CreateType,
685) -> Option<DocumentSymbol> {
686 let path = create_type.path()?;
687 let segment = path.segment()?;
688 let name_node = segment.name()?;
689
690 let (schema, type_name) = resolve_type_info(binder, &path)?;
691 let name = format!("{}.{}", schema.0, type_name);
692
693 let full_range = create_type.syntax().text_range();
694 let focus_range = name_node.syntax().text_range();
695
696 let mut children = vec![];
697 if let Some(variant_list) = create_type.variant_list() {
698 for variant in variant_list.variants() {
699 if let Some(variant_symbol) = create_variant_symbol(variant) {
700 children.push(variant_symbol);
701 }
702 }
703 } else if let Some(column_list) = create_type.column_list() {
704 for column in column_list.columns() {
705 if let Some(column_symbol) = create_column_symbol(column) {
706 children.push(column_symbol);
707 }
708 }
709 }
710
711 Some(DocumentSymbol {
712 name,
713 detail: None,
714 kind: if create_type.variant_list().is_some() {
715 DocumentSymbolKind::Enum
716 } else {
717 DocumentSymbolKind::Type
718 },
719 full_range,
720 focus_range,
721 children,
722 })
723}
724
725fn create_column_symbol(column: ast::Column) -> Option<DocumentSymbol> {
726 let name_node = column.name()?;
727 let name = name_node.syntax().text().to_string();
728
729 let detail = column.ty().map(|t| t.syntax().text().to_string());
730
731 let full_range = column.syntax().text_range();
732 let focus_range = name_node.syntax().text_range();
733
734 Some(DocumentSymbol {
735 name,
736 detail,
737 kind: DocumentSymbolKind::Column,
738 full_range,
739 focus_range,
740 children: vec![],
741 })
742}
743
744fn create_variant_symbol(variant: ast::Variant) -> Option<DocumentSymbol> {
745 let literal = variant.literal()?;
746 let name = extract_string_literal(&literal)?;
747
748 let full_range = variant.syntax().text_range();
749 let focus_range = literal.syntax().text_range();
750
751 Some(DocumentSymbol {
752 name,
753 detail: None,
754 kind: DocumentSymbolKind::Variant,
755 full_range,
756 focus_range,
757 children: vec![],
758 })
759}
760
761fn create_declare_cursor_symbol(declare: ast::Declare) -> Option<DocumentSymbol> {
762 let name_node = declare.name()?;
763 let name = name_node.syntax().text().to_string();
764
765 let full_range = declare.syntax().text_range();
766 let focus_range = name_node.syntax().text_range();
767
768 Some(DocumentSymbol {
769 name,
770 detail: None,
771 kind: DocumentSymbolKind::Cursor,
772 full_range,
773 focus_range,
774 children: vec![],
775 })
776}
777
778fn create_prepare_symbol(prepare: ast::Prepare) -> Option<DocumentSymbol> {
779 let name_node = prepare.name()?;
780 let name = name_node.syntax().text().to_string();
781
782 let full_range = prepare.syntax().text_range();
783 let focus_range = name_node.syntax().text_range();
784
785 Some(DocumentSymbol {
786 name,
787 detail: None,
788 kind: DocumentSymbolKind::PreparedStatement,
789 full_range,
790 focus_range,
791 children: vec![],
792 })
793}
794
795fn create_listen_symbol(listen: ast::Listen) -> Option<DocumentSymbol> {
796 let name_node = listen.name()?;
797 let name = name_node.syntax().text().to_string();
798
799 let full_range = listen.syntax().text_range();
800 let focus_range = name_node.syntax().text_range();
801
802 Some(DocumentSymbol {
803 name,
804 detail: Some("listen".to_string()),
805 kind: DocumentSymbolKind::Channel,
806 full_range,
807 focus_range,
808 children: vec![],
809 })
810}
811
812fn create_notify_symbol(notify: ast::Notify) -> Option<DocumentSymbol> {
813 let name_node = notify.name_ref()?;
814 let name = name_node.syntax().text().to_string();
815
816 let full_range = notify.syntax().text_range();
817 let focus_range = name_node.syntax().text_range();
818
819 Some(DocumentSymbol {
820 name,
821 detail: Some("notify".to_string()),
822 kind: DocumentSymbolKind::Channel,
823 full_range,
824 focus_range,
825 children: vec![],
826 })
827}
828
829fn create_unlisten_symbol(unlisten: ast::Unlisten) -> Option<DocumentSymbol> {
830 let name_node = unlisten.name_ref()?;
831 let name = name_node.syntax().text().to_string();
832
833 let full_range = unlisten.syntax().text_range();
834 let focus_range = name_node.syntax().text_range();
835
836 Some(DocumentSymbol {
837 name,
838 detail: Some("unlisten".to_string()),
839 kind: DocumentSymbolKind::Channel,
840 full_range,
841 focus_range,
842 children: vec![],
843 })
844}
845
846#[cfg(test)]
847mod tests {
848 use super::*;
849 use crate::db::{Database, File};
850 use annotate_snippets::{
851 AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle,
852 };
853 use insta::assert_snapshot;
854
855 fn symbols_not_found(sql: &str) {
856 let db = Database::default();
857 let file = File::new(&db, sql.to_string().into());
858 let symbols = document_symbols(&db, file);
859 if !symbols.is_empty() {
860 panic!("Symbols found. If this is expected, use `symbols` instead.")
861 }
862 }
863
864 fn symbols(sql: &str) -> String {
865 let db = Database::default();
866 let file = File::new(&db, sql.to_string().into());
867 let symbols = document_symbols(&db, file);
868 if symbols.is_empty() {
869 panic!("No symbols found. If this is expected, use `symbols_not_found` instead.")
870 }
871
872 let mut output = vec![];
873 for symbol in symbols {
874 let group = symbol_to_group(&symbol, sql);
875 output.push(group);
876 }
877 Renderer::plain()
878 .decor_style(DecorStyle::Unicode)
879 .render(&output)
880 .to_string()
881 }
882
883 fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> {
884 let kind = match symbol.kind {
885 DocumentSymbolKind::Schema => "schema",
886 DocumentSymbolKind::Table => "table",
887 DocumentSymbolKind::View => "view",
888 DocumentSymbolKind::MaterializedView => "materialized view",
889 DocumentSymbolKind::Function => "function",
890 DocumentSymbolKind::Aggregate => "aggregate",
891 DocumentSymbolKind::Procedure => "procedure",
892 DocumentSymbolKind::EventTrigger => "event trigger",
893 DocumentSymbolKind::Role => "role",
894 DocumentSymbolKind::Policy => "policy",
895 DocumentSymbolKind::Type => "type",
896 DocumentSymbolKind::Enum => "enum",
897 DocumentSymbolKind::Index => "index",
898 DocumentSymbolKind::Domain => "domain",
899 DocumentSymbolKind::Sequence => "sequence",
900 DocumentSymbolKind::Trigger => "trigger",
901 DocumentSymbolKind::Tablespace => "tablespace",
902 DocumentSymbolKind::Database => "database",
903 DocumentSymbolKind::Server => "server",
904 DocumentSymbolKind::Extension => "extension",
905 DocumentSymbolKind::Column => "column",
906 DocumentSymbolKind::Variant => "variant",
907 DocumentSymbolKind::Cursor => "cursor",
908 DocumentSymbolKind::PreparedStatement => "prepared statement",
909 DocumentSymbolKind::Channel => "channel",
910 };
911
912 let title = if let Some(detail) = &symbol.detail {
913 format!("{}: {} {}", kind, symbol.name, detail)
914 } else {
915 format!("{}: {}", kind, symbol.name)
916 };
917
918 let snippet = Snippet::source(sql)
919 .fold(true)
920 .annotation(
921 AnnotationKind::Primary
922 .span(symbol.focus_range.into())
923 .label("focus range"),
924 )
925 .annotation(
926 AnnotationKind::Context
927 .span(symbol.full_range.into())
928 .label("full range"),
929 );
930
931 let mut group = Level::INFO.primary_title(title.clone()).element(snippet);
932
933 if !symbol.children.is_empty() {
934 let child_labels: Vec<String> = symbol
935 .children
936 .iter()
937 .map(|child| {
938 let kind = match child.kind {
939 DocumentSymbolKind::Column => "column",
940 DocumentSymbolKind::Variant => "variant",
941 _ => unreachable!("only columns and variants can be children"),
942 };
943 if let Some(detail) = &child.detail {
944 format!("{}: {} {}", kind, child.name, detail)
945 } else {
946 format!("{}: {}", kind, child.name)
947 }
948 })
949 .collect();
950
951 let mut children_snippet = Snippet::source(sql).fold(true);
952
953 for (i, child) in symbol.children.iter().enumerate() {
954 children_snippet = children_snippet
955 .annotation(
956 AnnotationKind::Context
957 .span(child.full_range.into())
958 .label(format!("full range for `{}`", child_labels[i].clone())),
959 )
960 .annotation(
961 AnnotationKind::Primary
962 .span(child.focus_range.into())
963 .label("focus range"),
964 );
965 }
966
967 group = group.element(children_snippet);
968 }
969
970 group
971 }
972
973 #[test]
974 fn create_table() {
975 assert_snapshot!(symbols("
976create table users (
977 id int,
978 email citext
979);"), @r"
980 info: table: public.users
981 ╭▸
982 2 │ create table users (
983 │ │ ━━━━━ focus range
984 │ ┌─┘
985 │ │
986 3 │ │ id int,
987 4 │ │ email citext
988 5 │ │ );
989 │ └─┘ full range
990 │
991 ⸬
992 3 │ id int,
993 │ ┯━────
994 │ │
995 │ full range for `column: id int`
996 │ focus range
997 4 │ email citext
998 │ ┯━━━━───────
999 │ │
1000 │ full range for `column: email citext`
1001 ╰╴ focus range
1002 ");
1003 }
1004
1005 #[test]
1006 fn create_table_as() {
1007 assert_snapshot!(symbols("
1008create table t as select 1 a;
1009"), @r"
1010 info: table: public.t
1011 ╭▸
1012 2 │ create table t as select 1 a;
1013 │ ┬────────────┯──────────────
1014 │ │ │
1015 │ │ focus range
1016 ╰╴full range
1017 ");
1018 }
1019
1020 #[test]
1021 fn create_schema() {
1022 assert_snapshot!(symbols("
1023create schema foo;
1024"), @r"
1025 info: schema: foo
1026 ╭▸
1027 2 │ create schema foo;
1028 │ ┬─────────────┯━━
1029 │ │ │
1030 │ │ focus range
1031 ╰╴full range
1032 ");
1033 }
1034
1035 #[test]
1036 fn create_schema_authorization() {
1037 assert_snapshot!(symbols("
1038create schema authorization foo;
1039"), @r"
1040 info: schema: foo
1041 ╭▸
1042 2 │ create schema authorization foo;
1043 │ ┬───────────────────────────┯━━
1044 │ │ │
1045 │ │ focus range
1046 ╰╴full range
1047 ");
1048 }
1049
1050 #[test]
1051 fn listen_notify_unlisten() {
1052 assert_snapshot!(symbols("
1053listen updates;
1054notify updates;
1055unlisten updates;
1056unlisten *;
1057"), @r"
1058 info: channel: updates listen
1059 ╭▸
1060 2 │ listen updates;
1061 │ ┬──────┯━━━━━━
1062 │ │ │
1063 │ │ focus range
1064 │ full range
1065 ╰╴
1066 info: channel: updates notify
1067 ╭▸
1068 3 │ notify updates;
1069 │ ┬──────┯━━━━━━
1070 │ │ │
1071 │ │ focus range
1072 ╰╴full range
1073 info: channel: updates unlisten
1074 ╭▸
1075 4 │ unlisten updates;
1076 │ ┬────────┯━━━━━━
1077 │ │ │
1078 │ │ focus range
1079 ╰╴full range
1080 ");
1081 }
1082
1083 #[test]
1084 fn create_function() {
1085 assert_snapshot!(
1086 symbols("create function hello() returns void as $$ select 1; $$ language sql;"),
1087 @r"
1088 info: function: public.hello
1089 ╭▸
1090 1 │ create function hello() returns void as $$ select 1; $$ language sql;
1091 │ ┬───────────────┯━━━━───────────────────────────────────────────────
1092 │ │ │
1093 │ │ focus range
1094 ╰╴full range
1095 "
1096 );
1097 }
1098
1099 #[test]
1100 fn create_materialized_view() {
1101 assert_snapshot!(
1102 symbols("create materialized view reports as select 1;"),
1103 @r"
1104 info: materialized view: public.reports
1105 ╭▸
1106 1 │ create materialized view reports as select 1;
1107 │ ┬────────────────────────┯━━━━━━────────────
1108 │ │ │
1109 │ │ focus range
1110 ╰╴full range
1111 "
1112 );
1113 }
1114
1115 #[test]
1116 fn create_aggregate() {
1117 assert_snapshot!(
1118 symbols("create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);"),
1119 @r"
1120 info: aggregate: public.myavg
1121 ╭▸
1122 1 │ create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1123 │ ┬────────────────┯━━━━─────────────────────────────────────────────
1124 │ │ │
1125 │ │ focus range
1126 ╰╴full range
1127 "
1128 );
1129 }
1130
1131 #[test]
1132 fn create_procedure() {
1133 assert_snapshot!(
1134 symbols("create procedure hello() language sql as $$ select 1; $$;"),
1135 @r"
1136 info: procedure: public.hello
1137 ╭▸
1138 1 │ create procedure hello() language sql as $$ select 1; $$;
1139 │ ┬────────────────┯━━━━──────────────────────────────────
1140 │ │ │
1141 │ │ focus range
1142 ╰╴full range
1143 "
1144 );
1145 }
1146
1147 #[test]
1148 fn create_index() {
1149 assert_snapshot!(symbols("
1150create index idx_users_email on users (email);
1151"), @r"
1152 info: index: idx_users_email
1153 ╭▸
1154 2 │ create index idx_users_email on users (email);
1155 │ ┬────────────┯━━━━━━━━━━━━━━─────────────────
1156 │ │ │
1157 │ │ focus range
1158 ╰╴full range
1159 ");
1160 }
1161
1162 #[test]
1163 fn create_domain() {
1164 assert_snapshot!(
1165 symbols("create domain email_addr as text;"),
1166 @r"
1167 info: domain: public.email_addr
1168 ╭▸
1169 1 │ create domain email_addr as text;
1170 │ ┬─────────────┯━━━━━━━━━────────
1171 │ │ │
1172 │ │ focus range
1173 ╰╴full range
1174 "
1175 );
1176 }
1177
1178 #[test]
1179 fn create_sequence() {
1180 assert_snapshot!(
1181 symbols("create sequence user_id_seq;"),
1182 @r"
1183 info: sequence: public.user_id_seq
1184 ╭▸
1185 1 │ create sequence user_id_seq;
1186 │ ┬───────────────┯━━━━━━━━━━
1187 │ │ │
1188 │ │ focus range
1189 ╰╴full range
1190 "
1191 );
1192 }
1193
1194 #[test]
1195 fn create_trigger() {
1196 assert_snapshot!(symbols("
1197create trigger update_timestamp
1198 before update on users
1199 execute function update_modified_column();
1200"), @r"
1201 info: trigger: update_timestamp
1202 ╭▸
1203 2 │ create trigger update_timestamp
1204 │ │ ━━━━━━━━━━━━━━━━ focus range
1205 │ ┌─┘
1206 │ │
1207 3 │ │ before update on users
1208 4 │ │ execute function update_modified_column();
1209 ╰╴└───────────────────────────────────────────┘ full range
1210 ");
1211 }
1212
1213 #[test]
1214 fn create_event_trigger() {
1215 assert_snapshot!(
1216 symbols("create event trigger et on ddl_command_start execute function f();"),
1217 @r"
1218 info: event trigger: et
1219 ╭▸
1220 1 │ create event trigger et on ddl_command_start execute function f();
1221 │ ┬────────────────────┯━──────────────────────────────────────────
1222 │ │ │
1223 │ │ focus range
1224 ╰╴full range
1225 "
1226 );
1227 }
1228
1229 #[test]
1230 fn create_tablespace() {
1231 assert_snapshot!(symbols("
1232create tablespace dbspace location '/data/dbs';
1233"), @r"
1234 info: tablespace: dbspace
1235 ╭▸
1236 2 │ create tablespace dbspace location '/data/dbs';
1237 │ ┬─────────────────┯━━━━━━─────────────────────
1238 │ │ │
1239 │ │ focus range
1240 ╰╴full range
1241 ");
1242 }
1243
1244 #[test]
1245 fn create_database() {
1246 assert_snapshot!(
1247 symbols("create database mydb;"),
1248 @r"
1249 info: database: mydb
1250 ╭▸
1251 1 │ create database mydb;
1252 │ ┬───────────────┯━━━
1253 │ │ │
1254 │ │ focus range
1255 ╰╴full range
1256 "
1257 );
1258 }
1259
1260 #[test]
1261 fn create_server() {
1262 assert_snapshot!(symbols("
1263create server myserver foreign data wrapper postgres_fdw;
1264"), @r"
1265 info: server: myserver
1266 ╭▸
1267 2 │ create server myserver foreign data wrapper postgres_fdw;
1268 │ ┬─────────────┯━━━━━━━──────────────────────────────────
1269 │ │ │
1270 │ │ focus range
1271 ╰╴full range
1272 ");
1273 }
1274
1275 #[test]
1276 fn create_extension() {
1277 assert_snapshot!(
1278 symbols("create extension pgcrypto;"),
1279 @r"
1280 info: extension: pgcrypto
1281 ╭▸
1282 1 │ create extension pgcrypto;
1283 │ ┬────────────────┯━━━━━━━
1284 │ │ │
1285 │ │ focus range
1286 ╰╴full range
1287 "
1288 );
1289 }
1290
1291 #[test]
1292 fn create_role() {
1293 assert_snapshot!(symbols("
1294create role reader;
1295"), @r"
1296 info: role: reader
1297 ╭▸
1298 2 │ create role reader;
1299 │ ┬───────────┯━━━━━
1300 │ │ │
1301 │ │ focus range
1302 ╰╴full range
1303 ");
1304 }
1305
1306 #[test]
1307 fn create_policy() {
1308 assert_snapshot!(symbols("
1309create policy allow_read on t;
1310"), @r"
1311 info: policy: allow_read
1312 ╭▸
1313 2 │ create policy allow_read on t;
1314 │ ┬─────────────┯━━━━━━━━━─────
1315 │ │ │
1316 │ │ focus range
1317 ╰╴full range
1318 ");
1319 }
1320
1321 #[test]
1322 fn multiple_symbols() {
1323 assert_snapshot!(symbols("
1324create table users (id int);
1325create table posts (id int);
1326create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
1327"), @r"
1328 info: table: public.users
1329 ╭▸
1330 2 │ create table users (id int);
1331 │ ┬────────────┯━━━━─────────
1332 │ │ │
1333 │ │ focus range
1334 │ full range
1335 │
1336 ⸬
1337 2 │ create table users (id int);
1338 │ ┯━────
1339 │ │
1340 │ full range for `column: id int`
1341 │ focus range
1342 ╰╴
1343 info: table: public.posts
1344 ╭▸
1345 3 │ create table posts (id int);
1346 │ ┬────────────┯━━━━─────────
1347 │ │ │
1348 │ │ focus range
1349 │ full range
1350 │
1351 ⸬
1352 3 │ create table posts (id int);
1353 │ ┯━────
1354 │ │
1355 │ full range for `column: id int`
1356 ╰╴ focus range
1357 info: function: public.get_user
1358 ╭▸
1359 4 │ create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
1360 │ ┬───────────────┯━━━━━━━──────────────────────────────────────────────────────────
1361 │ │ │
1362 │ │ focus range
1363 ╰╴full range
1364 ");
1365 }
1366
1367 #[test]
1368 fn qualified_names() {
1369 assert_snapshot!(symbols("
1370create table public.users (id int);
1371create function my_schema.hello() returns void as $$ select 1; $$ language sql;
1372"), @r"
1373 info: table: public.users
1374 ╭▸
1375 2 │ create table public.users (id int);
1376 │ ┬───────────────────┯━━━━─────────
1377 │ │ │
1378 │ │ focus range
1379 │ full range
1380 │
1381 ⸬
1382 2 │ create table public.users (id int);
1383 │ ┯━────
1384 │ │
1385 │ full range for `column: id int`
1386 │ focus range
1387 ╰╴
1388 info: function: my_schema.hello
1389 ╭▸
1390 3 │ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
1391 │ ┬─────────────────────────┯━━━━───────────────────────────────────────────────
1392 │ │ │
1393 │ │ focus range
1394 ╰╴full range
1395 ");
1396 }
1397
1398 #[test]
1399 fn create_type() {
1400 assert_snapshot!(
1401 symbols("create type status as enum ('active', 'inactive');"),
1402 @r"
1403 info: enum: public.status
1404 ╭▸
1405 1 │ create type status as enum ('active', 'inactive');
1406 │ ┬───────────┯━━━━━───────────────────────────────
1407 │ │ │
1408 │ │ focus range
1409 │ full range
1410 │
1411 ⸬
1412 1 │ create type status as enum ('active', 'inactive');
1413 │ ┯━━━━━━━ ┯━━━━━━━━━
1414 │ │ │
1415 │ │ full range for `variant: inactive`
1416 │ │ focus range
1417 │ full range for `variant: active`
1418 ╰╴ focus range
1419 "
1420 );
1421 }
1422
1423 #[test]
1424 fn create_type_composite() {
1425 assert_snapshot!(
1426 symbols("create type person as (name text, age int);"),
1427 @r"
1428 info: type: public.person
1429 ╭▸
1430 1 │ create type person as (name text, age int);
1431 │ ┬───────────┯━━━━━────────────────────────
1432 │ │ │
1433 │ │ focus range
1434 │ full range
1435 │
1436 ⸬
1437 1 │ create type person as (name text, age int);
1438 │ ┯━━━───── ┯━━────
1439 │ │ │
1440 │ │ full range for `column: age int`
1441 │ │ focus range
1442 │ full range for `column: name text`
1443 ╰╴ focus range
1444 "
1445 );
1446 }
1447
1448 #[test]
1449 fn create_type_composite_multiple_columns() {
1450 assert_snapshot!(
1451 symbols("create type address as (street text, city text, zip varchar(10));"),
1452 @r"
1453 info: type: public.address
1454 ╭▸
1455 1 │ create type address as (street text, city text, zip varchar(10));
1456 │ ┬───────────┯━━━━━━─────────────────────────────────────────────
1457 │ │ │
1458 │ │ focus range
1459 │ full range
1460 │
1461 ⸬
1462 1 │ create type address as (street text, city text, zip varchar(10));
1463 │ ┯━━━━━───── ┯━━━───── ┯━━────────────
1464 │ │ │ │
1465 │ │ │ full range for `column: zip varchar(10)`
1466 │ │ │ focus range
1467 │ │ full range for `column: city text`
1468 │ │ focus range
1469 │ full range for `column: street text`
1470 ╰╴ focus range
1471 "
1472 );
1473 }
1474
1475 #[test]
1476 fn create_type_with_schema() {
1477 assert_snapshot!(
1478 symbols("create type myschema.status as enum ('active', 'inactive');"),
1479 @r"
1480 info: enum: myschema.status
1481 ╭▸
1482 1 │ create type myschema.status as enum ('active', 'inactive');
1483 │ ┬────────────────────┯━━━━━───────────────────────────────
1484 │ │ │
1485 │ │ focus range
1486 │ full range
1487 │
1488 ⸬
1489 1 │ create type myschema.status as enum ('active', 'inactive');
1490 │ ┯━━━━━━━ ┯━━━━━━━━━
1491 │ │ │
1492 │ │ full range for `variant: inactive`
1493 │ │ focus range
1494 │ full range for `variant: active`
1495 ╰╴ focus range
1496 "
1497 );
1498 }
1499
1500 #[test]
1501 fn create_type_enum_multiple_variants() {
1502 assert_snapshot!(
1503 symbols("create type priority as enum ('low', 'medium', 'high', 'urgent');"),
1504 @r"
1505 info: enum: public.priority
1506 ╭▸
1507 1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1508 │ ┬───────────┯━━━━━━━────────────────────────────────────────────
1509 │ │ │
1510 │ │ focus range
1511 │ full range
1512 │
1513 ⸬
1514 1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1515 │ ┯━━━━ ┯━━━━━━━ ┯━━━━━ ┯━━━━━━━
1516 │ │ │ │ │
1517 │ │ │ │ full range for `variant: urgent`
1518 │ │ │ │ focus range
1519 │ │ │ full range for `variant: high`
1520 │ │ │ focus range
1521 │ │ full range for `variant: medium`
1522 │ │ focus range
1523 │ full range for `variant: low`
1524 ╰╴ focus range
1525 "
1526 );
1527 }
1528
1529 #[test]
1530 fn declare_cursor() {
1531 assert_snapshot!(symbols("
1532declare c scroll cursor for select * from t;
1533"), @r"
1534 info: cursor: c
1535 ╭▸
1536 2 │ declare c scroll cursor for select * from t;
1537 │ ┬───────┯──────────────────────────────────
1538 │ │ │
1539 │ │ focus range
1540 ╰╴full range
1541 ");
1542 }
1543
1544 #[test]
1545 fn prepare_statement() {
1546 assert_snapshot!(symbols("
1547prepare stmt as select 1;
1548"), @r"
1549 info: prepared statement: stmt
1550 ╭▸
1551 2 │ prepare stmt as select 1;
1552 │ ┬───────┯━━━────────────
1553 │ │ │
1554 │ │ focus range
1555 ╰╴full range
1556 ");
1557 }
1558
1559 #[test]
1560 fn empty_file() {
1561 symbols_not_found("")
1562 }
1563
1564 #[test]
1565 fn non_create_statements() {
1566 symbols_not_found("select * from users;")
1567 }
1568
1569 #[test]
1570 fn cte_table() {
1571 assert_snapshot!(
1572 symbols("
1573with recent_users as (
1574 select id, email as user_email
1575 from users
1576)
1577select * from recent_users;
1578"),
1579 @r"
1580 info: table: recent_users
1581 ╭▸
1582 2 │ with recent_users as (
1583 │ │━━━━━━━━━━━
1584 │ │
1585 │ ┌──────focus range
1586 │ │
1587 3 │ │ select id, email as user_email
1588 4 │ │ from users
1589 5 │ │ )
1590 ╰╴└─┘ full range
1591 "
1592 );
1593 }
1594
1595 #[test]
1596 fn cte_table_with_column_list() {
1597 assert_snapshot!(
1598 symbols("
1599with t(a, b, c) as (
1600 select 1, 2, 3
1601)
1602select * from t;
1603"),
1604 @r"
1605 info: table: t
1606 ╭▸
1607 2 │ with t(a, b, c) as (
1608 │ ━ focus range
1609 │ ┌──────┘
1610 │ │
1611 3 │ │ select 1, 2, 3
1612 4 │ │ )
1613 │ └─┘ full range
1614 │
1615 ⸬
1616 2 │ with t(a, b, c) as (
1617 │ ┯ ┯ ┯
1618 │ │ │ │
1619 │ │ │ full range for `column: c`
1620 │ │ │ focus range
1621 │ │ full range for `column: b`
1622 │ │ focus range
1623 │ full range for `column: a`
1624 ╰╴ focus range
1625 "
1626 );
1627 }
1628
1629 #[test]
1630 fn create_foreign_table() {
1631 assert_snapshot!(symbols("
1632create foreign table films (
1633 code char(5),
1634 title varchar(40)
1635) server film_server;
1636"), @r"
1637 info: table: public.films
1638 ╭▸
1639 2 │ create foreign table films (
1640 │ │ ━━━━━ focus range
1641 │ ┌─┘
1642 │ │
1643 3 │ │ code char(5),
1644 4 │ │ title varchar(40)
1645 5 │ │ ) server film_server;
1646 │ └────────────────────┘ full range
1647 │
1648 ⸬
1649 3 │ code char(5),
1650 │ ┯━━━────────
1651 │ │
1652 │ full range for `column: code char(5)`
1653 │ focus range
1654 4 │ title varchar(40)
1655 │ ┯━━━━────────────
1656 │ │
1657 │ full range for `column: title varchar(40)`
1658 ╰╴ focus range
1659 ");
1660 }
1661}