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