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