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