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