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