Skip to main content

squawk_ide/
document_symbols.rs

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