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