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    #[must_use]
881    fn symbols(sql: &str) -> String {
882        let db = Database::default();
883        let file = File::new(&db, sql.to_string().into());
884        let symbols = document_symbols(&db, file);
885        if symbols.is_empty() {
886            panic!("No symbols found. If this is expected, use `symbols_not_found` instead.")
887        }
888
889        let mut output = vec![];
890        for symbol in symbols {
891            let group = symbol_to_group(&symbol, sql);
892            output.push(group);
893        }
894        Renderer::plain()
895            .decor_style(DecorStyle::Unicode)
896            .render(&output)
897            .to_string()
898    }
899
900    fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> {
901        let kind = match symbol.kind {
902            DocumentSymbolKind::Schema => "schema",
903            DocumentSymbolKind::Table => "table",
904            DocumentSymbolKind::View => "view",
905            DocumentSymbolKind::MaterializedView => "materialized view",
906            DocumentSymbolKind::Function => "function",
907            DocumentSymbolKind::Aggregate => "aggregate",
908            DocumentSymbolKind::Procedure => "procedure",
909            DocumentSymbolKind::EventTrigger => "event trigger",
910            DocumentSymbolKind::Role => "role",
911            DocumentSymbolKind::Policy => "policy",
912            DocumentSymbolKind::PropertyGraph => "property graph",
913            DocumentSymbolKind::Type => "type",
914            DocumentSymbolKind::Enum => "enum",
915            DocumentSymbolKind::Index => "index",
916            DocumentSymbolKind::Domain => "domain",
917            DocumentSymbolKind::Sequence => "sequence",
918            DocumentSymbolKind::Trigger => "trigger",
919            DocumentSymbolKind::Tablespace => "tablespace",
920            DocumentSymbolKind::Database => "database",
921            DocumentSymbolKind::Server => "server",
922            DocumentSymbolKind::Extension => "extension",
923            DocumentSymbolKind::Column => "column",
924            DocumentSymbolKind::Variant => "variant",
925            DocumentSymbolKind::Cursor => "cursor",
926            DocumentSymbolKind::PreparedStatement => "prepared statement",
927            DocumentSymbolKind::Channel => "channel",
928        };
929
930        let title = if let Some(detail) = &symbol.detail {
931            format!("{}: {} {}", kind, symbol.name, detail)
932        } else {
933            format!("{}: {}", kind, symbol.name)
934        };
935
936        let snippet = Snippet::source(sql)
937            .fold(true)
938            .annotation(
939                AnnotationKind::Primary
940                    .span(symbol.focus_range.into())
941                    .label("focus range"),
942            )
943            .annotation(
944                AnnotationKind::Context
945                    .span(symbol.full_range.into())
946                    .label("full range"),
947            );
948
949        let mut group = Level::INFO.primary_title(title.clone()).element(snippet);
950
951        if !symbol.children.is_empty() {
952            let child_labels: Vec<String> = symbol
953                .children
954                .iter()
955                .map(|child| {
956                    let kind = match child.kind {
957                        DocumentSymbolKind::Column => "column",
958                        DocumentSymbolKind::Variant => "variant",
959                        _ => unreachable!("only columns and variants can be children"),
960                    };
961                    if let Some(detail) = &child.detail {
962                        format!("{}: {} {}", kind, child.name, detail)
963                    } else {
964                        format!("{}: {}", kind, child.name)
965                    }
966                })
967                .collect();
968
969            let mut children_snippet = Snippet::source(sql).fold(true);
970
971            for (i, child) in symbol.children.iter().enumerate() {
972                children_snippet = children_snippet
973                    .annotation(
974                        AnnotationKind::Context
975                            .span(child.full_range.into())
976                            .label(format!("full range for `{}`", child_labels[i].clone())),
977                    )
978                    .annotation(
979                        AnnotationKind::Primary
980                            .span(child.focus_range.into())
981                            .label("focus range"),
982                    );
983            }
984
985            group = group.element(children_snippet);
986        }
987
988        group
989    }
990
991    #[test]
992    fn create_table() {
993        assert_snapshot!(symbols("
994create table users (
995  id int,
996  email citext
997);"), @r"
998        info: table: public.users
999          ╭▸ 
1000        2 │   create table users (
1001          │   │            ━━━━━ focus range
1002          │ ┌─┘
1003          │ │
1004        3 │ │   id int,
1005        4 │ │   email citext
1006        5 │ │ );
1007          │ └─┘ full range
100810091010        3 │     id int,
1011          │     ┯━────
1012          │     │
1013          │     full range for `column: id int`
1014          │     focus range
1015        4 │     email citext
1016          │     ┯━━━━───────
1017          │     │
1018          │     full range for `column: email citext`
1019          ╰╴    focus range
1020        ");
1021    }
1022
1023    #[test]
1024    fn create_table_as() {
1025        assert_snapshot!(symbols("
1026create table t as select 1 a;
1027"), @r"
1028        info: table: public.t
1029          ╭▸ 
1030        2 │ create table t as select 1 a;
1031          │ ┬────────────┯──────────────
1032          │ │            │
1033          │ │            focus range
1034          ╰╴full range
1035        ");
1036    }
1037
1038    #[test]
1039    fn create_schema() {
1040        assert_snapshot!(symbols("
1041create schema foo;
1042"), @r"
1043        info: schema: foo
1044          ╭▸ 
1045        2 │ create schema foo;
1046          │ ┬─────────────┯━━
1047          │ │             │
1048          │ │             focus range
1049          ╰╴full range
1050        ");
1051    }
1052
1053    #[test]
1054    fn create_schema_authorization() {
1055        assert_snapshot!(symbols("
1056create schema authorization foo;
1057"), @r"
1058        info: schema: foo
1059          ╭▸ 
1060        2 │ create schema authorization foo;
1061          │ ┬───────────────────────────┯━━
1062          │ │                           │
1063          │ │                           focus range
1064          ╰╴full range
1065        ");
1066    }
1067
1068    #[test]
1069    fn listen_notify_unlisten() {
1070        assert_snapshot!(symbols("
1071listen updates;
1072notify updates;
1073unlisten updates;
1074unlisten *;
1075"), @r"
1076        info: channel: updates listen
1077          ╭▸ 
1078        2 │ listen updates;
1079          │ ┬──────┯━━━━━━
1080          │ │      │
1081          │ │      focus range
1082          │ full range
1083          ╰╴
1084        info: channel: updates notify
1085          ╭▸ 
1086        3 │ notify updates;
1087          │ ┬──────┯━━━━━━
1088          │ │      │
1089          │ │      focus range
1090          ╰╴full range
1091        info: channel: updates unlisten
1092          ╭▸ 
1093        4 │ unlisten updates;
1094          │ ┬────────┯━━━━━━
1095          │ │        │
1096          │ │        focus range
1097          ╰╴full range
1098        ");
1099    }
1100
1101    #[test]
1102    fn create_function() {
1103        assert_snapshot!(
1104            symbols("create function hello() returns void as $$ select 1; $$ language sql;"),
1105            @r"
1106        info: function: public.hello
1107          ╭▸ 
1108        1 │ create function hello() returns void as $$ select 1; $$ language sql;
1109          │ ┬───────────────┯━━━━───────────────────────────────────────────────
1110          │ │               │
1111          │ │               focus range
1112          ╰╴full range
1113        "
1114        );
1115    }
1116
1117    #[test]
1118    fn create_materialized_view() {
1119        assert_snapshot!(
1120            symbols("create materialized view reports as select 1;"),
1121            @r"
1122        info: materialized view: public.reports
1123          ╭▸ 
1124        1 │ create materialized view reports as select 1;
1125          │ ┬────────────────────────┯━━━━━━────────────
1126          │ │                        │
1127          │ │                        focus range
1128          ╰╴full range
1129        "
1130        );
1131    }
1132
1133    #[test]
1134    fn create_aggregate() {
1135        assert_snapshot!(
1136            symbols("create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);"),
1137            @r"
1138        info: aggregate: public.myavg
1139          ╭▸ 
1140        1 │ create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
1141          │ ┬────────────────┯━━━━─────────────────────────────────────────────
1142          │ │                │
1143          │ │                focus range
1144          ╰╴full range
1145        "
1146        );
1147    }
1148
1149    #[test]
1150    fn create_procedure() {
1151        assert_snapshot!(
1152            symbols("create procedure hello() language sql as $$ select 1; $$;"),
1153            @r"
1154        info: procedure: public.hello
1155          ╭▸ 
1156        1 │ create procedure hello() language sql as $$ select 1; $$;
1157          │ ┬────────────────┯━━━━──────────────────────────────────
1158          │ │                │
1159          │ │                focus range
1160          ╰╴full range
1161        "
1162        );
1163    }
1164
1165    #[test]
1166    fn create_index() {
1167        assert_snapshot!(symbols("
1168create index idx_users_email on users (email);
1169"), @r"
1170        info: index: idx_users_email
1171          ╭▸ 
1172        2 │ create index idx_users_email on users (email);
1173          │ ┬────────────┯━━━━━━━━━━━━━━─────────────────
1174          │ │            │
1175          │ │            focus range
1176          ╰╴full range
1177        ");
1178    }
1179
1180    #[test]
1181    fn create_domain() {
1182        assert_snapshot!(
1183            symbols("create domain email_addr as text;"),
1184            @r"
1185        info: domain: public.email_addr
1186          ╭▸ 
1187        1 │ create domain email_addr as text;
1188          │ ┬─────────────┯━━━━━━━━━────────
1189          │ │             │
1190          │ │             focus range
1191          ╰╴full range
1192        "
1193        );
1194    }
1195
1196    #[test]
1197    fn create_sequence() {
1198        assert_snapshot!(
1199            symbols("create sequence user_id_seq;"),
1200            @r"
1201        info: sequence: public.user_id_seq
1202          ╭▸ 
1203        1 │ create sequence user_id_seq;
1204          │ ┬───────────────┯━━━━━━━━━━
1205          │ │               │
1206          │ │               focus range
1207          ╰╴full range
1208        "
1209        );
1210    }
1211
1212    #[test]
1213    fn create_trigger() {
1214        assert_snapshot!(symbols("
1215create trigger update_timestamp
1216  before update on users
1217  execute function update_modified_column();
1218"), @r"
1219        info: trigger: update_timestamp
1220          ╭▸ 
1221        2 │   create trigger update_timestamp
1222          │   │              ━━━━━━━━━━━━━━━━ focus range
1223          │ ┌─┘
1224          │ │
1225        3 │ │   before update on users
1226        4 │ │   execute function update_modified_column();
1227          ╰╴└───────────────────────────────────────────┘ full range
1228        ");
1229    }
1230
1231    #[test]
1232    fn create_event_trigger() {
1233        assert_snapshot!(
1234            symbols("create event trigger et on ddl_command_start execute function f();"),
1235            @r"
1236        info: event trigger: et
1237          ╭▸ 
1238        1 │ create event trigger et on ddl_command_start execute function f();
1239          │ ┬────────────────────┯━──────────────────────────────────────────
1240          │ │                    │
1241          │ │                    focus range
1242          ╰╴full range
1243        "
1244        );
1245    }
1246
1247    #[test]
1248    fn create_tablespace() {
1249        assert_snapshot!(symbols("
1250create tablespace dbspace location '/data/dbs';
1251"), @r"
1252        info: tablespace: dbspace
1253          ╭▸ 
1254        2 │ create tablespace dbspace location '/data/dbs';
1255          │ ┬─────────────────┯━━━━━━─────────────────────
1256          │ │                 │
1257          │ │                 focus range
1258          ╰╴full range
1259        ");
1260    }
1261
1262    #[test]
1263    fn create_database() {
1264        assert_snapshot!(
1265            symbols("create database mydb;"),
1266            @r"
1267        info: database: mydb
1268          ╭▸ 
1269        1 │ create database mydb;
1270          │ ┬───────────────┯━━━
1271          │ │               │
1272          │ │               focus range
1273          ╰╴full range
1274        "
1275        );
1276    }
1277
1278    #[test]
1279    fn create_server() {
1280        assert_snapshot!(symbols("
1281create server myserver foreign data wrapper postgres_fdw;
1282"), @r"
1283        info: server: myserver
1284          ╭▸ 
1285        2 │ create server myserver foreign data wrapper postgres_fdw;
1286          │ ┬─────────────┯━━━━━━━──────────────────────────────────
1287          │ │             │
1288          │ │             focus range
1289          ╰╴full range
1290        ");
1291    }
1292
1293    #[test]
1294    fn create_extension() {
1295        assert_snapshot!(
1296            symbols("create extension pgcrypto;"),
1297            @r"
1298        info: extension: pgcrypto
1299          ╭▸ 
1300        1 │ create extension pgcrypto;
1301          │ ┬────────────────┯━━━━━━━
1302          │ │                │
1303          │ │                focus range
1304          ╰╴full range
1305        "
1306        );
1307    }
1308
1309    #[test]
1310    fn create_role() {
1311        assert_snapshot!(symbols("
1312create role reader;
1313"), @r"
1314        info: role: reader
1315          ╭▸ 
1316        2 │ create role reader;
1317          │ ┬───────────┯━━━━━
1318          │ │           │
1319          │ │           focus range
1320          ╰╴full range
1321        ");
1322    }
1323
1324    #[test]
1325    fn create_policy() {
1326        assert_snapshot!(symbols("
1327create policy allow_read on t;
1328"), @r"
1329        info: policy: allow_read
1330          ╭▸ 
1331        2 │ create policy allow_read on t;
1332          │ ┬─────────────┯━━━━━━━━━─────
1333          │ │             │
1334          │ │             focus range
1335          ╰╴full range
1336        ");
1337    }
1338
1339    #[test]
1340    fn multiple_symbols() {
1341        assert_snapshot!(symbols("
1342create table users (id int);
1343create table posts (id int);
1344create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
1345"), @r"
1346        info: table: public.users
1347          ╭▸ 
1348        2 │ create table users (id int);
1349          │ ┬────────────┯━━━━─────────
1350          │ │            │
1351          │ │            focus range
1352          │ full range
135313541355        2 │ create table users (id int);
1356          │                     ┯━────
1357          │                     │
1358          │                     full range for `column: id int`
1359          │                     focus range
1360          ╰╴
1361        info: table: public.posts
1362          ╭▸ 
1363        3 │ create table posts (id int);
1364          │ ┬────────────┯━━━━─────────
1365          │ │            │
1366          │ │            focus range
1367          │ full range
136813691370        3 │ create table posts (id int);
1371          │                     ┯━────
1372          │                     │
1373          │                     full range for `column: id int`
1374          ╰╴                    focus range
1375        info: function: public.get_user
1376          ╭▸ 
1377        4 │ create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
1378          │ ┬───────────────┯━━━━━━━──────────────────────────────────────────────────────────
1379          │ │               │
1380          │ │               focus range
1381          ╰╴full range
1382        ");
1383    }
1384
1385    #[test]
1386    fn qualified_names() {
1387        assert_snapshot!(symbols("
1388create table public.users (id int);
1389create function my_schema.hello() returns void as $$ select 1; $$ language sql;
1390"), @r"
1391        info: table: public.users
1392          ╭▸ 
1393        2 │ create table public.users (id int);
1394          │ ┬───────────────────┯━━━━─────────
1395          │ │                   │
1396          │ │                   focus range
1397          │ full range
139813991400        2 │ create table public.users (id int);
1401          │                            ┯━────
1402          │                            │
1403          │                            full range for `column: id int`
1404          │                            focus range
1405          ╰╴
1406        info: function: my_schema.hello
1407          ╭▸ 
1408        3 │ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
1409          │ ┬─────────────────────────┯━━━━───────────────────────────────────────────────
1410          │ │                         │
1411          │ │                         focus range
1412          ╰╴full range
1413        ");
1414    }
1415
1416    #[test]
1417    fn create_property_graph() {
1418        assert_snapshot!(symbols("
1419create property graph foo.bar
1420  vertex tables (t key (a) no properties);
1421"), @"
1422        info: property graph: foo.bar
1423          ╭▸ 
1424        2 │   create property graph foo.bar
1425          │   │                         ━━━ focus range
1426          │ ┌─┘
1427          │ │
1428        3 │ │   vertex tables (t key (a) no properties);
1429          ╰╴└─────────────────────────────────────────┘ full range
1430        ");
1431    }
1432
1433    #[test]
1434    fn create_type() {
1435        assert_snapshot!(
1436            symbols("create type status as enum ('active', 'inactive');"),
1437            @r"
1438        info: enum: public.status
1439          ╭▸ 
1440        1 │ create type status as enum ('active', 'inactive');
1441          │ ┬───────────┯━━━━━───────────────────────────────
1442          │ │           │
1443          │ │           focus range
1444          │ full range
144514461447        1 │ create type status as enum ('active', 'inactive');
1448          │                             ┯━━━━━━━  ┯━━━━━━━━━
1449          │                             │         │
1450          │                             │         full range for `variant: inactive`
1451          │                             │         focus range
1452          │                             full range for `variant: active`
1453          ╰╴                            focus range
1454        "
1455        );
1456    }
1457
1458    #[test]
1459    fn create_type_composite() {
1460        assert_snapshot!(
1461            symbols("create type person as (name text, age int);"),
1462            @r"
1463        info: type: public.person
1464          ╭▸ 
1465        1 │ create type person as (name text, age int);
1466          │ ┬───────────┯━━━━━────────────────────────
1467          │ │           │
1468          │ │           focus range
1469          │ full range
147014711472        1 │ create type person as (name text, age int);
1473          │                        ┯━━━─────  ┯━━────
1474          │                        │          │
1475          │                        │          full range for `column: age int`
1476          │                        │          focus range
1477          │                        full range for `column: name text`
1478          ╰╴                       focus range
1479        "
1480        );
1481    }
1482
1483    #[test]
1484    fn create_type_composite_multiple_columns() {
1485        assert_snapshot!(
1486            symbols("create type address as (street text, city text, zip varchar(10));"),
1487            @r"
1488        info: type: public.address
1489          ╭▸ 
1490        1 │ create type address as (street text, city text, zip varchar(10));
1491          │ ┬───────────┯━━━━━━─────────────────────────────────────────────
1492          │ │           │
1493          │ │           focus range
1494          │ full range
149514961497        1 │ create type address as (street text, city text, zip varchar(10));
1498          │                         ┯━━━━━─────  ┯━━━─────  ┯━━────────────
1499          │                         │            │          │
1500          │                         │            │          full range for `column: zip varchar(10)`
1501          │                         │            │          focus range
1502          │                         │            full range for `column: city text`
1503          │                         │            focus range
1504          │                         full range for `column: street text`
1505          ╰╴                        focus range
1506        "
1507        );
1508    }
1509
1510    #[test]
1511    fn create_type_with_schema() {
1512        assert_snapshot!(
1513            symbols("create type myschema.status as enum ('active', 'inactive');"),
1514            @r"
1515        info: enum: myschema.status
1516          ╭▸ 
1517        1 │ create type myschema.status as enum ('active', 'inactive');
1518          │ ┬────────────────────┯━━━━━───────────────────────────────
1519          │ │                    │
1520          │ │                    focus range
1521          │ full range
152215231524        1 │ create type myschema.status as enum ('active', 'inactive');
1525          │                                      ┯━━━━━━━  ┯━━━━━━━━━
1526          │                                      │         │
1527          │                                      │         full range for `variant: inactive`
1528          │                                      │         focus range
1529          │                                      full range for `variant: active`
1530          ╰╴                                     focus range
1531        "
1532        );
1533    }
1534
1535    #[test]
1536    fn create_type_enum_multiple_variants() {
1537        assert_snapshot!(
1538            symbols("create type priority as enum ('low', 'medium', 'high', 'urgent');"),
1539            @r"
1540        info: enum: public.priority
1541          ╭▸ 
1542        1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1543          │ ┬───────────┯━━━━━━━────────────────────────────────────────────
1544          │ │           │
1545          │ │           focus range
1546          │ full range
154715481549        1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1550          │                               ┯━━━━  ┯━━━━━━━  ┯━━━━━  ┯━━━━━━━
1551          │                               │      │         │       │
1552          │                               │      │         │       full range for `variant: urgent`
1553          │                               │      │         │       focus range
1554          │                               │      │         full range for `variant: high`
1555          │                               │      │         focus range
1556          │                               │      full range for `variant: medium`
1557          │                               │      focus range
1558          │                               full range for `variant: low`
1559          ╰╴                              focus range
1560        "
1561        );
1562    }
1563
1564    #[test]
1565    fn declare_cursor() {
1566        assert_snapshot!(symbols("
1567declare c scroll cursor for select * from t;
1568"), @r"
1569        info: cursor: c
1570          ╭▸ 
1571        2 │ declare c scroll cursor for select * from t;
1572          │ ┬───────┯──────────────────────────────────
1573          │ │       │
1574          │ │       focus range
1575          ╰╴full range
1576        ");
1577    }
1578
1579    #[test]
1580    fn prepare_statement() {
1581        assert_snapshot!(symbols("
1582prepare stmt as select 1;
1583"), @r"
1584        info: prepared statement: stmt
1585          ╭▸ 
1586        2 │ prepare stmt as select 1;
1587          │ ┬───────┯━━━────────────
1588          │ │       │
1589          │ │       focus range
1590          ╰╴full range
1591        ");
1592    }
1593
1594    #[test]
1595    fn empty_file() {
1596        symbols_not_found("")
1597    }
1598
1599    #[test]
1600    fn non_create_statements() {
1601        symbols_not_found("select * from users;")
1602    }
1603
1604    #[test]
1605    fn cte_table() {
1606        assert_snapshot!(
1607            symbols("
1608with recent_users as (
1609  select id, email as user_email
1610  from users
1611)
1612select * from recent_users;
1613"),
1614            @r"
1615        info: table: recent_users
1616          ╭▸ 
1617        2 │   with recent_users as (
1618          │        │━━━━━━━━━━━
1619          │        │
1620          │ ┌──────focus range
1621          │ │
1622        3 │ │   select id, email as user_email
1623        4 │ │   from users
1624        5 │ │ )
1625          ╰╴└─┘ full range
1626        "
1627        );
1628    }
1629
1630    #[test]
1631    fn cte_table_with_column_list() {
1632        assert_snapshot!(
1633            symbols("
1634with t(a, b, c) as (
1635  select 1, 2, 3
1636)
1637select * from t;
1638"),
1639            @r"
1640        info: table: t
1641          ╭▸ 
1642        2 │   with t(a, b, c) as (
1643          │        ━ focus range
1644          │ ┌──────┘
1645          │ │
1646        3 │ │   select 1, 2, 3
1647        4 │ │ )
1648          │ └─┘ full range
164916501651        2 │   with t(a, b, c) as (
1652          │          ┯  ┯  ┯
1653          │          │  │  │
1654          │          │  │  full range for `column: c`
1655          │          │  │  focus range
1656          │          │  full range for `column: b`
1657          │          │  focus range
1658          │          full range for `column: a`
1659          ╰╴         focus range
1660        "
1661        );
1662    }
1663
1664    #[test]
1665    fn create_foreign_table() {
1666        assert_snapshot!(symbols("
1667create foreign table films (
1668  code char(5),
1669  title varchar(40)
1670) server film_server;
1671"), @r"
1672        info: table: public.films
1673          ╭▸ 
1674        2 │   create foreign table films (
1675          │   │                    ━━━━━ focus range
1676          │ ┌─┘
1677          │ │
1678        3 │ │   code char(5),
1679        4 │ │   title varchar(40)
1680        5 │ │ ) server film_server;
1681          │ └────────────────────┘ full range
168216831684        3 │     code char(5),
1685          │     ┯━━━────────
1686          │     │
1687          │     full range for `column: code char(5)`
1688          │     focus range
1689        4 │     title varchar(40)
1690          │     ┯━━━━────────────
1691          │     │
1692          │     full range for `column: title varchar(40)`
1693          ╰╴    focus range
1694        ");
1695    }
1696}