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