1use rowan::TextRange;
2use squawk_syntax::ast::{self, AstNode};
3
4use crate::binder::{self, extract_string_literal};
5use crate::resolve::{
6 resolve_aggregate_info, resolve_function_info, resolve_procedure_info, resolve_table_info,
7 resolve_type_info, resolve_view_info,
8};
9
10#[derive(Debug)]
11pub enum DocumentSymbolKind {
12 Schema,
13 Table,
14 View,
15 MaterializedView,
16 Function,
17 Aggregate,
18 Procedure,
19 EventTrigger,
20 Role,
21 Type,
22 Enum,
23 Column,
24 Variant,
25 Cursor,
26 PreparedStatement,
27 Channel,
28}
29
30#[derive(Debug)]
31pub struct DocumentSymbol {
32 pub name: String,
33 pub detail: Option<String>,
34 pub kind: DocumentSymbolKind,
35 pub full_range: TextRange,
38 pub focus_range: TextRange,
40 pub children: Vec<DocumentSymbol>,
41}
42
43pub fn document_symbols(file: &ast::SourceFile) -> Vec<DocumentSymbol> {
44 let binder = binder::bind(file);
45 let mut symbols = vec![];
46
47 for stmt in file.stmts() {
48 match stmt {
49 ast::Stmt::CreateSchema(create_schema) => {
50 if let Some(symbol) = create_schema_symbol(create_schema) {
51 symbols.push(symbol);
52 }
53 }
54 ast::Stmt::CreateTable(create_table) => {
55 if let Some(symbol) = create_table_symbol(&binder, create_table) {
56 symbols.push(symbol);
57 }
58 }
59 ast::Stmt::CreateFunction(create_function) => {
60 if let Some(symbol) = create_function_symbol(&binder, create_function) {
61 symbols.push(symbol);
62 }
63 }
64 ast::Stmt::CreateAggregate(create_aggregate) => {
65 if let Some(symbol) = create_aggregate_symbol(&binder, create_aggregate) {
66 symbols.push(symbol);
67 }
68 }
69 ast::Stmt::CreateProcedure(create_procedure) => {
70 if let Some(symbol) = create_procedure_symbol(&binder, create_procedure) {
71 symbols.push(symbol);
72 }
73 }
74 ast::Stmt::CreateEventTrigger(create_event_trigger) => {
75 if let Some(symbol) = create_event_trigger_symbol(create_event_trigger) {
76 symbols.push(symbol);
77 }
78 }
79 ast::Stmt::CreateRole(create_role) => {
80 if let Some(symbol) = create_role_symbol(create_role) {
81 symbols.push(symbol);
82 }
83 }
84 ast::Stmt::CreateType(create_type) => {
85 if let Some(symbol) = create_type_symbol(&binder, create_type) {
86 symbols.push(symbol);
87 }
88 }
89 ast::Stmt::CreateView(create_view) => {
90 if let Some(symbol) = create_view_symbol(&binder, create_view) {
91 symbols.push(symbol);
92 }
93 }
94 ast::Stmt::CreateMaterializedView(create_view) => {
95 if let Some(symbol) = create_materialized_view_symbol(&binder, create_view) {
96 symbols.push(symbol);
97 }
98 }
99 ast::Stmt::Declare(declare) => {
100 if let Some(symbol) = create_declare_cursor_symbol(declare) {
101 symbols.push(symbol);
102 }
103 }
104 ast::Stmt::Prepare(prepare) => {
105 if let Some(symbol) = create_prepare_symbol(prepare) {
106 symbols.push(symbol);
107 }
108 }
109 ast::Stmt::Select(select) => {
110 symbols.extend(cte_table_symbols(select));
111 }
112 ast::Stmt::SelectInto(select_into) => {
113 symbols.extend(cte_table_symbols(select_into));
114 }
115 ast::Stmt::Insert(insert) => {
116 symbols.extend(cte_table_symbols(insert));
117 }
118 ast::Stmt::Update(update) => {
119 symbols.extend(cte_table_symbols(update));
120 }
121 ast::Stmt::Delete(delete) => {
122 symbols.extend(cte_table_symbols(delete));
123 }
124 ast::Stmt::Listen(listen) => {
125 if let Some(symbol) = create_listen_symbol(listen) {
126 symbols.push(symbol);
127 }
128 }
129 ast::Stmt::Notify(notify) => {
130 if let Some(symbol) = create_notify_symbol(notify) {
131 symbols.push(symbol);
132 }
133 }
134 ast::Stmt::Unlisten(unlisten) => {
135 if let Some(symbol) = create_unlisten_symbol(unlisten) {
136 symbols.push(symbol);
137 }
138 }
139
140 _ => {}
141 }
142 }
143
144 symbols
145}
146
147fn cte_table_symbols(stmt: impl ast::HasWithClause) -> Vec<DocumentSymbol> {
148 let Some(with_clause) = stmt.with_clause() else {
149 return vec![];
150 };
151
152 with_clause
153 .with_tables()
154 .filter_map(create_cte_table_symbol)
155 .collect()
156}
157
158fn create_cte_table_symbol(with_table: ast::WithTable) -> Option<DocumentSymbol> {
159 let name_node = with_table.name()?;
160 let name = name_node.syntax().text().to_string();
161
162 let full_range = with_table.syntax().text_range();
163 let focus_range = name_node.syntax().text_range();
164
165 symbols_from_column_list(
166 with_table.column_list(),
167 name,
168 full_range,
169 focus_range,
170 DocumentSymbolKind::Table,
171 )
172}
173
174fn create_schema_symbol(create_schema: ast::CreateSchema) -> Option<DocumentSymbol> {
175 let (name, focus_range) = if let Some(name_node) = create_schema.name() {
176 (
177 name_node.syntax().text().to_string(),
178 name_node.syntax().text_range(),
179 )
180 } else if let Some(name) = create_schema.role().and_then(|r| r.name()) {
181 (name.syntax().text().to_string(), name.syntax().text_range())
182 } else {
183 return None;
184 };
185
186 let full_range = create_schema.syntax().text_range();
187
188 Some(DocumentSymbol {
189 name,
190 detail: None,
191 kind: DocumentSymbolKind::Schema,
192 full_range,
193 focus_range,
194 children: vec![],
195 })
196}
197
198fn create_table_symbol(
199 binder: &binder::Binder,
200 create_table: ast::CreateTable,
201) -> Option<DocumentSymbol> {
202 let path = create_table.path()?;
203 let segment = path.segment()?;
204 let name_node = segment.name()?;
205
206 let (schema, table_name) = resolve_table_info(binder, &path)?;
207 let name = format!("{}.{}", schema.0, table_name);
208
209 let full_range = create_table.syntax().text_range();
210 let focus_range = name_node.syntax().text_range();
211
212 let mut children = vec![];
213 if let Some(table_arg_list) = create_table.table_arg_list() {
214 for arg in table_arg_list.args() {
215 if let ast::TableArg::Column(column) = arg
216 && let Some(column_symbol) = create_column_symbol(column)
217 {
218 children.push(column_symbol);
219 }
220 }
221 }
222
223 Some(DocumentSymbol {
224 name,
225 detail: None,
226 kind: DocumentSymbolKind::Table,
227 full_range,
228 focus_range,
229 children,
230 })
231}
232
233fn create_view_symbol(
234 binder: &binder::Binder,
235 create_view: ast::CreateView,
236) -> Option<DocumentSymbol> {
237 let path = create_view.path()?;
238 let segment = path.segment()?;
239 let name_node = segment.name()?;
240
241 let (schema, view_name) = resolve_view_info(binder, &path)?;
242 let name = format!("{}.{}", schema.0, view_name);
243
244 let full_range = create_view.syntax().text_range();
245 let focus_range = name_node.syntax().text_range();
246
247 symbols_from_column_list(
248 create_view.column_list(),
249 name,
250 full_range,
251 focus_range,
252 DocumentSymbolKind::View,
253 )
254}
255
256fn symbols_from_column_list(
257 column_list: Option<ast::ColumnList>,
258 name: String,
259 full_range: TextRange,
260 focus_range: TextRange,
261 kind: DocumentSymbolKind,
262) -> Option<DocumentSymbol> {
263 let mut children = vec![];
264 if let Some(column_list) = column_list {
265 for column in column_list.columns() {
266 if let Some(column_symbol) = create_column_symbol(column) {
267 children.push(column_symbol);
268 }
269 }
270 }
271
272 Some(DocumentSymbol {
273 name,
274 detail: None,
275 kind,
276 full_range,
277 focus_range,
278 children,
279 })
280}
281
282fn create_materialized_view_symbol(
284 binder: &binder::Binder,
285 create_view: ast::CreateMaterializedView,
286) -> Option<DocumentSymbol> {
287 let path = create_view.path()?;
288 let segment = path.segment()?;
289 let name_node = segment.name()?;
290
291 let (schema, view_name) = resolve_view_info(binder, &path)?;
292 let name = format!("{}.{}", schema.0, view_name);
293
294 let full_range = create_view.syntax().text_range();
295 let focus_range = name_node.syntax().text_range();
296
297 symbols_from_column_list(
298 create_view.column_list(),
299 name,
300 full_range,
301 focus_range,
302 DocumentSymbolKind::MaterializedView,
303 )
304}
305
306fn create_function_symbol(
307 binder: &binder::Binder,
308 create_function: ast::CreateFunction,
309) -> Option<DocumentSymbol> {
310 let path = create_function.path()?;
311 let segment = path.segment()?;
312 let name_node = segment.name()?;
313
314 let (schema, function_name) = resolve_function_info(binder, &path)?;
315 let name = format!("{}.{}", schema.0, function_name);
316
317 let full_range = create_function.syntax().text_range();
318 let focus_range = name_node.syntax().text_range();
319
320 Some(DocumentSymbol {
321 name,
322 detail: None,
323 kind: DocumentSymbolKind::Function,
324 full_range,
325 focus_range,
326 children: vec![],
327 })
328}
329
330fn create_aggregate_symbol(
331 binder: &binder::Binder,
332 create_aggregate: ast::CreateAggregate,
333) -> Option<DocumentSymbol> {
334 let path = create_aggregate.path()?;
335 let segment = path.segment()?;
336 let name_node = segment.name()?;
337
338 let (schema, aggregate_name) = resolve_aggregate_info(binder, &path)?;
339 let name = format!("{}.{}", schema.0, aggregate_name);
340
341 let full_range = create_aggregate.syntax().text_range();
342 let focus_range = name_node.syntax().text_range();
343
344 Some(DocumentSymbol {
345 name,
346 detail: None,
347 kind: DocumentSymbolKind::Aggregate,
348 full_range,
349 focus_range,
350 children: vec![],
351 })
352}
353
354fn create_procedure_symbol(
355 binder: &binder::Binder,
356 create_procedure: ast::CreateProcedure,
357) -> Option<DocumentSymbol> {
358 let path = create_procedure.path()?;
359 let segment = path.segment()?;
360 let name_node = segment.name()?;
361
362 let (schema, procedure_name) = resolve_procedure_info(binder, &path)?;
363 let name = format!("{}.{}", schema.0, procedure_name);
364
365 let full_range = create_procedure.syntax().text_range();
366 let focus_range = name_node.syntax().text_range();
367
368 Some(DocumentSymbol {
369 name,
370 detail: None,
371 kind: DocumentSymbolKind::Procedure,
372 full_range,
373 focus_range,
374 children: vec![],
375 })
376}
377
378fn create_event_trigger_symbol(
379 create_event_trigger: ast::CreateEventTrigger,
380) -> Option<DocumentSymbol> {
381 let name_node = create_event_trigger.name()?;
382 let name = name_node.syntax().text().to_string();
383
384 let full_range = create_event_trigger.syntax().text_range();
385 let focus_range = name_node.syntax().text_range();
386
387 Some(DocumentSymbol {
388 name,
389 detail: None,
390 kind: DocumentSymbolKind::EventTrigger,
391 full_range,
392 focus_range,
393 children: vec![],
394 })
395}
396
397fn create_role_symbol(create_role: ast::CreateRole) -> Option<DocumentSymbol> {
398 let name_node = create_role.name()?;
399 let name = name_node.syntax().text().to_string();
400
401 let full_range = create_role.syntax().text_range();
402 let focus_range = name_node.syntax().text_range();
403
404 Some(DocumentSymbol {
405 name,
406 detail: None,
407 kind: DocumentSymbolKind::Role,
408 full_range,
409 focus_range,
410 children: vec![],
411 })
412}
413
414fn create_type_symbol(
415 binder: &binder::Binder,
416 create_type: ast::CreateType,
417) -> Option<DocumentSymbol> {
418 let path = create_type.path()?;
419 let segment = path.segment()?;
420 let name_node = segment.name()?;
421
422 let (schema, type_name) = resolve_type_info(binder, &path)?;
423 let name = format!("{}.{}", schema.0, type_name);
424
425 let full_range = create_type.syntax().text_range();
426 let focus_range = name_node.syntax().text_range();
427
428 let mut children = vec![];
429 if let Some(variant_list) = create_type.variant_list() {
430 for variant in variant_list.variants() {
431 if let Some(variant_symbol) = create_variant_symbol(variant) {
432 children.push(variant_symbol);
433 }
434 }
435 } else if let Some(column_list) = create_type.column_list() {
436 for column in column_list.columns() {
437 if let Some(column_symbol) = create_column_symbol(column) {
438 children.push(column_symbol);
439 }
440 }
441 }
442
443 Some(DocumentSymbol {
444 name,
445 detail: None,
446 kind: if create_type.variant_list().is_some() {
447 DocumentSymbolKind::Enum
448 } else {
449 DocumentSymbolKind::Type
450 },
451 full_range,
452 focus_range,
453 children,
454 })
455}
456
457fn create_column_symbol(column: ast::Column) -> Option<DocumentSymbol> {
458 let name_node = column.name()?;
459 let name = name_node.syntax().text().to_string();
460
461 let detail = column.ty().map(|t| t.syntax().text().to_string());
462
463 let full_range = column.syntax().text_range();
464 let focus_range = name_node.syntax().text_range();
465
466 Some(DocumentSymbol {
467 name,
468 detail,
469 kind: DocumentSymbolKind::Column,
470 full_range,
471 focus_range,
472 children: vec![],
473 })
474}
475
476fn create_variant_symbol(variant: ast::Variant) -> Option<DocumentSymbol> {
477 let literal = variant.literal()?;
478 let name = extract_string_literal(&literal)?;
479
480 let full_range = variant.syntax().text_range();
481 let focus_range = literal.syntax().text_range();
482
483 Some(DocumentSymbol {
484 name,
485 detail: None,
486 kind: DocumentSymbolKind::Variant,
487 full_range,
488 focus_range,
489 children: vec![],
490 })
491}
492
493fn create_declare_cursor_symbol(declare: ast::Declare) -> Option<DocumentSymbol> {
494 let name_node = declare.name()?;
495 let name = name_node.syntax().text().to_string();
496
497 let full_range = declare.syntax().text_range();
498 let focus_range = name_node.syntax().text_range();
499
500 Some(DocumentSymbol {
501 name,
502 detail: None,
503 kind: DocumentSymbolKind::Cursor,
504 full_range,
505 focus_range,
506 children: vec![],
507 })
508}
509
510fn create_prepare_symbol(prepare: ast::Prepare) -> Option<DocumentSymbol> {
511 let name_node = prepare.name()?;
512 let name = name_node.syntax().text().to_string();
513
514 let full_range = prepare.syntax().text_range();
515 let focus_range = name_node.syntax().text_range();
516
517 Some(DocumentSymbol {
518 name,
519 detail: None,
520 kind: DocumentSymbolKind::PreparedStatement,
521 full_range,
522 focus_range,
523 children: vec![],
524 })
525}
526
527fn create_listen_symbol(listen: ast::Listen) -> Option<DocumentSymbol> {
528 let name_node = listen.name()?;
529 let name = name_node.syntax().text().to_string();
530
531 let full_range = listen.syntax().text_range();
532 let focus_range = name_node.syntax().text_range();
533
534 Some(DocumentSymbol {
535 name,
536 detail: Some("listen".to_string()),
537 kind: DocumentSymbolKind::Channel,
538 full_range,
539 focus_range,
540 children: vec![],
541 })
542}
543
544fn create_notify_symbol(notify: ast::Notify) -> Option<DocumentSymbol> {
545 let name_node = notify.name_ref()?;
546 let name = name_node.syntax().text().to_string();
547
548 let full_range = notify.syntax().text_range();
549 let focus_range = name_node.syntax().text_range();
550
551 Some(DocumentSymbol {
552 name,
553 detail: Some("notify".to_string()),
554 kind: DocumentSymbolKind::Channel,
555 full_range,
556 focus_range,
557 children: vec![],
558 })
559}
560
561fn create_unlisten_symbol(unlisten: ast::Unlisten) -> Option<DocumentSymbol> {
562 let name_node = unlisten.name_ref()?;
563 let name = name_node.syntax().text().to_string();
564
565 let full_range = unlisten.syntax().text_range();
566 let focus_range = name_node.syntax().text_range();
567
568 Some(DocumentSymbol {
569 name,
570 detail: Some("unlisten".to_string()),
571 kind: DocumentSymbolKind::Channel,
572 full_range,
573 focus_range,
574 children: vec![],
575 })
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581 use annotate_snippets::{
582 AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle,
583 };
584 use insta::assert_snapshot;
585
586 fn symbols_not_found(sql: &str) {
587 let parse = ast::SourceFile::parse(sql);
588 let file = parse.tree();
589 let symbols = document_symbols(&file);
590 if !symbols.is_empty() {
591 panic!("Symbols found. If this is expected, use `symbols` instead.")
592 }
593 }
594
595 fn symbols(sql: &str) -> String {
596 let parse = ast::SourceFile::parse(sql);
597 let file = parse.tree();
598 let symbols = document_symbols(&file);
599 if symbols.is_empty() {
600 panic!("No symbols found. If this is expected, use `symbols_not_found` instead.")
601 }
602
603 let mut output = vec![];
604 for symbol in symbols {
605 let group = symbol_to_group(&symbol, sql);
606 output.push(group);
607 }
608 Renderer::plain()
609 .decor_style(DecorStyle::Unicode)
610 .render(&output)
611 .to_string()
612 }
613
614 fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> {
615 let kind = match symbol.kind {
616 DocumentSymbolKind::Schema => "schema",
617 DocumentSymbolKind::Table => "table",
618 DocumentSymbolKind::View => "view",
619 DocumentSymbolKind::MaterializedView => "materialized view",
620 DocumentSymbolKind::Function => "function",
621 DocumentSymbolKind::Aggregate => "aggregate",
622 DocumentSymbolKind::Procedure => "procedure",
623 DocumentSymbolKind::EventTrigger => "event trigger",
624 DocumentSymbolKind::Role => "role",
625 DocumentSymbolKind::Type => "type",
626 DocumentSymbolKind::Enum => "enum",
627 DocumentSymbolKind::Column => "column",
628 DocumentSymbolKind::Variant => "variant",
629 DocumentSymbolKind::Cursor => "cursor",
630 DocumentSymbolKind::PreparedStatement => "prepared statement",
631 DocumentSymbolKind::Channel => "channel",
632 };
633
634 let title = if let Some(detail) = &symbol.detail {
635 format!("{}: {} {}", kind, symbol.name, detail)
636 } else {
637 format!("{}: {}", kind, symbol.name)
638 };
639
640 let snippet = Snippet::source(sql)
641 .fold(true)
642 .annotation(
643 AnnotationKind::Primary
644 .span(symbol.focus_range.into())
645 .label("focus range"),
646 )
647 .annotation(
648 AnnotationKind::Context
649 .span(symbol.full_range.into())
650 .label("full range"),
651 );
652
653 let mut group = Level::INFO.primary_title(title.clone()).element(snippet);
654
655 if !symbol.children.is_empty() {
656 let child_labels: Vec<String> = symbol
657 .children
658 .iter()
659 .map(|child| {
660 let kind = match child.kind {
661 DocumentSymbolKind::Column => "column",
662 DocumentSymbolKind::Variant => "variant",
663 _ => unreachable!("only columns and variants can be children"),
664 };
665 if let Some(detail) = &child.detail {
666 format!("{}: {} {}", kind, child.name, detail)
667 } else {
668 format!("{}: {}", kind, child.name)
669 }
670 })
671 .collect();
672
673 let mut children_snippet = Snippet::source(sql).fold(true);
674
675 for (i, child) in symbol.children.iter().enumerate() {
676 children_snippet = children_snippet
677 .annotation(
678 AnnotationKind::Context
679 .span(child.full_range.into())
680 .label(format!("full range for `{}`", child_labels[i].clone())),
681 )
682 .annotation(
683 AnnotationKind::Primary
684 .span(child.focus_range.into())
685 .label("focus range"),
686 );
687 }
688
689 group = group.element(children_snippet);
690 }
691
692 group
693 }
694
695 #[test]
696 fn create_table() {
697 assert_snapshot!(symbols("
698create table users (
699 id int,
700 email citext
701);"), @r"
702 info: table: public.users
703 ╭▸
704 2 │ create table users (
705 │ │ ━━━━━ focus range
706 │ ┌─┘
707 │ │
708 3 │ │ id int,
709 4 │ │ email citext
710 5 │ │ );
711 │ └─┘ full range
712 │
713 ⸬
714 3 │ id int,
715 │ ┯━────
716 │ │
717 │ full range for `column: id int`
718 │ focus range
719 4 │ email citext
720 │ ┯━━━━───────
721 │ │
722 │ full range for `column: email citext`
723 ╰╴ focus range
724 ");
725 }
726
727 #[test]
728 fn create_schema() {
729 assert_snapshot!(symbols("
730create schema foo;
731"), @r"
732 info: schema: foo
733 ╭▸
734 2 │ create schema foo;
735 │ ┬─────────────┯━━
736 │ │ │
737 │ │ focus range
738 ╰╴full range
739 ");
740 }
741
742 #[test]
743 fn create_schema_authorization() {
744 assert_snapshot!(symbols("
745create schema authorization foo;
746"), @r"
747 info: schema: foo
748 ╭▸
749 2 │ create schema authorization foo;
750 │ ┬───────────────────────────┯━━
751 │ │ │
752 │ │ focus range
753 ╰╴full range
754 ");
755 }
756
757 #[test]
758 fn listen_notify_unlisten() {
759 assert_snapshot!(symbols("
760listen updates;
761notify updates;
762unlisten updates;
763unlisten *;
764"), @r"
765 info: channel: updates listen
766 ╭▸
767 2 │ listen updates;
768 │ ┬──────┯━━━━━━
769 │ │ │
770 │ │ focus range
771 │ full range
772 ╰╴
773 info: channel: updates notify
774 ╭▸
775 3 │ notify updates;
776 │ ┬──────┯━━━━━━
777 │ │ │
778 │ │ focus range
779 ╰╴full range
780 info: channel: updates unlisten
781 ╭▸
782 4 │ unlisten updates;
783 │ ┬────────┯━━━━━━
784 │ │ │
785 │ │ focus range
786 ╰╴full range
787 ");
788 }
789
790 #[test]
791 fn create_function() {
792 assert_snapshot!(
793 symbols("create function hello() returns void as $$ select 1; $$ language sql;"),
794 @r"
795 info: function: public.hello
796 ╭▸
797 1 │ create function hello() returns void as $$ select 1; $$ language sql;
798 │ ┬───────────────┯━━━━───────────────────────────────────────────────
799 │ │ │
800 │ │ focus range
801 ╰╴full range
802 "
803 );
804 }
805
806 #[test]
807 fn create_materialized_view() {
808 assert_snapshot!(
809 symbols("create materialized view reports as select 1;"),
810 @r"
811 info: materialized view: public.reports
812 ╭▸
813 1 │ create materialized view reports as select 1;
814 │ ┬────────────────────────┯━━━━━━────────────
815 │ │ │
816 │ │ focus range
817 ╰╴full range
818 "
819 );
820 }
821
822 #[test]
823 fn create_aggregate() {
824 assert_snapshot!(
825 symbols("create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);"),
826 @r"
827 info: aggregate: public.myavg
828 ╭▸
829 1 │ create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
830 │ ┬────────────────┯━━━━─────────────────────────────────────────────
831 │ │ │
832 │ │ focus range
833 ╰╴full range
834 "
835 );
836 }
837
838 #[test]
839 fn create_procedure() {
840 assert_snapshot!(
841 symbols("create procedure hello() language sql as $$ select 1; $$;"),
842 @r"
843 info: procedure: public.hello
844 ╭▸
845 1 │ create procedure hello() language sql as $$ select 1; $$;
846 │ ┬────────────────┯━━━━──────────────────────────────────
847 │ │ │
848 │ │ focus range
849 ╰╴full range
850 "
851 );
852 }
853
854 #[test]
855 fn create_event_trigger() {
856 assert_snapshot!(
857 symbols("create event trigger et on ddl_command_start execute function f();"),
858 @r"
859 info: event trigger: et
860 ╭▸
861 1 │ create event trigger et on ddl_command_start execute function f();
862 │ ┬────────────────────┯━──────────────────────────────────────────
863 │ │ │
864 │ │ focus range
865 ╰╴full range
866 "
867 );
868 }
869
870 #[test]
871 fn create_role() {
872 assert_snapshot!(symbols("
873create role reader;
874"), @r"
875 info: role: reader
876 ╭▸
877 2 │ create role reader;
878 │ ┬───────────┯━━━━━
879 │ │ │
880 │ │ focus range
881 ╰╴full range
882 ");
883 }
884
885 #[test]
886 fn multiple_symbols() {
887 assert_snapshot!(symbols("
888create table users (id int);
889create table posts (id int);
890create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
891"), @r"
892 info: table: public.users
893 ╭▸
894 2 │ create table users (id int);
895 │ ┬────────────┯━━━━─────────
896 │ │ │
897 │ │ focus range
898 │ full range
899 │
900 ⸬
901 2 │ create table users (id int);
902 │ ┯━────
903 │ │
904 │ full range for `column: id int`
905 │ focus range
906 ╰╴
907 info: table: public.posts
908 ╭▸
909 3 │ create table posts (id int);
910 │ ┬────────────┯━━━━─────────
911 │ │ │
912 │ │ focus range
913 │ full range
914 │
915 ⸬
916 3 │ create table posts (id int);
917 │ ┯━────
918 │ │
919 │ full range for `column: id int`
920 ╰╴ focus range
921 info: function: public.get_user
922 ╭▸
923 4 │ create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
924 │ ┬───────────────┯━━━━━━━──────────────────────────────────────────────────────────
925 │ │ │
926 │ │ focus range
927 ╰╴full range
928 ");
929 }
930
931 #[test]
932 fn qualified_names() {
933 assert_snapshot!(symbols("
934create table public.users (id int);
935create function my_schema.hello() returns void as $$ select 1; $$ language sql;
936"), @r"
937 info: table: public.users
938 ╭▸
939 2 │ create table public.users (id int);
940 │ ┬───────────────────┯━━━━─────────
941 │ │ │
942 │ │ focus range
943 │ full range
944 │
945 ⸬
946 2 │ create table public.users (id int);
947 │ ┯━────
948 │ │
949 │ full range for `column: id int`
950 │ focus range
951 ╰╴
952 info: function: my_schema.hello
953 ╭▸
954 3 │ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
955 │ ┬─────────────────────────┯━━━━───────────────────────────────────────────────
956 │ │ │
957 │ │ focus range
958 ╰╴full range
959 ");
960 }
961
962 #[test]
963 fn create_type() {
964 assert_snapshot!(
965 symbols("create type status as enum ('active', 'inactive');"),
966 @r"
967 info: enum: public.status
968 ╭▸
969 1 │ create type status as enum ('active', 'inactive');
970 │ ┬───────────┯━━━━━───────────────────────────────
971 │ │ │
972 │ │ focus range
973 │ full range
974 │
975 ⸬
976 1 │ create type status as enum ('active', 'inactive');
977 │ ┯━━━━━━━ ┯━━━━━━━━━
978 │ │ │
979 │ │ full range for `variant: inactive`
980 │ │ focus range
981 │ full range for `variant: active`
982 ╰╴ focus range
983 "
984 );
985 }
986
987 #[test]
988 fn create_type_composite() {
989 assert_snapshot!(
990 symbols("create type person as (name text, age int);"),
991 @r"
992 info: type: public.person
993 ╭▸
994 1 │ create type person as (name text, age int);
995 │ ┬───────────┯━━━━━────────────────────────
996 │ │ │
997 │ │ focus range
998 │ full range
999 │
1000 ⸬
1001 1 │ create type person as (name text, age int);
1002 │ ┯━━━───── ┯━━────
1003 │ │ │
1004 │ │ full range for `column: age int`
1005 │ │ focus range
1006 │ full range for `column: name text`
1007 ╰╴ focus range
1008 "
1009 );
1010 }
1011
1012 #[test]
1013 fn create_type_composite_multiple_columns() {
1014 assert_snapshot!(
1015 symbols("create type address as (street text, city text, zip varchar(10));"),
1016 @r"
1017 info: type: public.address
1018 ╭▸
1019 1 │ create type address as (street text, city text, zip varchar(10));
1020 │ ┬───────────┯━━━━━━─────────────────────────────────────────────
1021 │ │ │
1022 │ │ focus range
1023 │ full range
1024 │
1025 ⸬
1026 1 │ create type address as (street text, city text, zip varchar(10));
1027 │ ┯━━━━━───── ┯━━━───── ┯━━────────────
1028 │ │ │ │
1029 │ │ │ full range for `column: zip varchar(10)`
1030 │ │ │ focus range
1031 │ │ full range for `column: city text`
1032 │ │ focus range
1033 │ full range for `column: street text`
1034 ╰╴ focus range
1035 "
1036 );
1037 }
1038
1039 #[test]
1040 fn create_type_with_schema() {
1041 assert_snapshot!(
1042 symbols("create type myschema.status as enum ('active', 'inactive');"),
1043 @r"
1044 info: enum: myschema.status
1045 ╭▸
1046 1 │ create type myschema.status as enum ('active', 'inactive');
1047 │ ┬────────────────────┯━━━━━───────────────────────────────
1048 │ │ │
1049 │ │ focus range
1050 │ full range
1051 │
1052 ⸬
1053 1 │ create type myschema.status as enum ('active', 'inactive');
1054 │ ┯━━━━━━━ ┯━━━━━━━━━
1055 │ │ │
1056 │ │ full range for `variant: inactive`
1057 │ │ focus range
1058 │ full range for `variant: active`
1059 ╰╴ focus range
1060 "
1061 );
1062 }
1063
1064 #[test]
1065 fn create_type_enum_multiple_variants() {
1066 assert_snapshot!(
1067 symbols("create type priority as enum ('low', 'medium', 'high', 'urgent');"),
1068 @r"
1069 info: enum: public.priority
1070 ╭▸
1071 1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1072 │ ┬───────────┯━━━━━━━────────────────────────────────────────────
1073 │ │ │
1074 │ │ focus range
1075 │ full range
1076 │
1077 ⸬
1078 1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
1079 │ ┯━━━━ ┯━━━━━━━ ┯━━━━━ ┯━━━━━━━
1080 │ │ │ │ │
1081 │ │ │ │ full range for `variant: urgent`
1082 │ │ │ │ focus range
1083 │ │ │ full range for `variant: high`
1084 │ │ │ focus range
1085 │ │ full range for `variant: medium`
1086 │ │ focus range
1087 │ full range for `variant: low`
1088 ╰╴ focus range
1089 "
1090 );
1091 }
1092
1093 #[test]
1094 fn declare_cursor() {
1095 assert_snapshot!(symbols("
1096declare c scroll cursor for select * from t;
1097"), @r"
1098 info: cursor: c
1099 ╭▸
1100 2 │ declare c scroll cursor for select * from t;
1101 │ ┬───────┯──────────────────────────────────
1102 │ │ │
1103 │ │ focus range
1104 ╰╴full range
1105 ");
1106 }
1107
1108 #[test]
1109 fn prepare_statement() {
1110 assert_snapshot!(symbols("
1111prepare stmt as select 1;
1112"), @r"
1113 info: prepared statement: stmt
1114 ╭▸
1115 2 │ prepare stmt as select 1;
1116 │ ┬───────┯━━━────────────
1117 │ │ │
1118 │ │ focus range
1119 ╰╴full range
1120 ");
1121 }
1122
1123 #[test]
1124 fn empty_file() {
1125 symbols_not_found("")
1126 }
1127
1128 #[test]
1129 fn non_create_statements() {
1130 symbols_not_found("select * from users;")
1131 }
1132
1133 #[test]
1134 fn cte_table() {
1135 assert_snapshot!(
1136 symbols("
1137with recent_users as (
1138 select id, email as user_email
1139 from users
1140)
1141select * from recent_users;
1142"),
1143 @r"
1144 info: table: recent_users
1145 ╭▸
1146 2 │ with recent_users as (
1147 │ │━━━━━━━━━━━
1148 │ │
1149 │ ┌──────focus range
1150 │ │
1151 3 │ │ select id, email as user_email
1152 4 │ │ from users
1153 5 │ │ )
1154 ╰╴└─┘ full range
1155 "
1156 );
1157 }
1158
1159 #[test]
1160 fn cte_table_with_column_list() {
1161 assert_snapshot!(
1162 symbols("
1163with t(a, b, c) as (
1164 select 1, 2, 3
1165)
1166select * from t;
1167"),
1168 @r"
1169 info: table: t
1170 ╭▸
1171 2 │ with t(a, b, c) as (
1172 │ ━ focus range
1173 │ ┌──────┘
1174 │ │
1175 3 │ │ select 1, 2, 3
1176 4 │ │ )
1177 │ └─┘ full range
1178 │
1179 ⸬
1180 2 │ with t(a, b, c) as (
1181 │ ┯ ┯ ┯
1182 │ │ │ │
1183 │ │ │ full range for `column: c`
1184 │ │ │ focus range
1185 │ │ full range for `column: b`
1186 │ │ focus range
1187 │ full range for `column: a`
1188 ╰╴ focus range
1189 "
1190 );
1191 }
1192}