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