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