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