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