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