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_materialized_view_info,
7 resolve_procedure_info, resolve_table_info, 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 Type,
20 Enum,
21 Column,
22 Variant,
23}
24
25#[derive(Debug)]
26pub struct DocumentSymbol {
27 pub name: String,
28 pub detail: Option<String>,
29 pub kind: DocumentSymbolKind,
30 pub full_range: TextRange,
33 pub focus_range: TextRange,
35 pub children: Vec<DocumentSymbol>,
36}
37
38pub fn document_symbols(file: &ast::SourceFile) -> Vec<DocumentSymbol> {
39 let binder = binder::bind(file);
40 let mut symbols = vec![];
41
42 for stmt in file.stmts() {
43 match stmt {
44 ast::Stmt::CreateSchema(create_schema) => {
45 if let Some(symbol) = create_schema_symbol(create_schema) {
46 symbols.push(symbol);
47 }
48 }
49 ast::Stmt::CreateTable(create_table) => {
50 if let Some(symbol) = create_table_symbol(&binder, create_table) {
51 symbols.push(symbol);
52 }
53 }
54 ast::Stmt::CreateFunction(create_function) => {
55 if let Some(symbol) = create_function_symbol(&binder, create_function) {
56 symbols.push(symbol);
57 }
58 }
59 ast::Stmt::CreateAggregate(create_aggregate) => {
60 if let Some(symbol) = create_aggregate_symbol(&binder, create_aggregate) {
61 symbols.push(symbol);
62 }
63 }
64 ast::Stmt::CreateProcedure(create_procedure) => {
65 if let Some(symbol) = create_procedure_symbol(&binder, create_procedure) {
66 symbols.push(symbol);
67 }
68 }
69 ast::Stmt::CreateType(create_type) => {
70 if let Some(symbol) = create_type_symbol(&binder, create_type) {
71 symbols.push(symbol);
72 }
73 }
74 ast::Stmt::CreateView(create_view) => {
75 if let Some(symbol) = create_view_symbol(&binder, create_view) {
76 symbols.push(symbol);
77 }
78 }
79 ast::Stmt::CreateMaterializedView(create_view) => {
80 if let Some(symbol) = create_materialized_view_symbol(&binder, create_view) {
81 symbols.push(symbol);
82 }
83 }
84 ast::Stmt::Select(select) => {
85 symbols.extend(cte_table_symbols(select));
86 }
87 ast::Stmt::SelectInto(select_into) => {
88 symbols.extend(cte_table_symbols(select_into));
89 }
90 ast::Stmt::Insert(insert) => {
91 symbols.extend(cte_table_symbols(insert));
92 }
93 ast::Stmt::Update(update) => {
94 symbols.extend(cte_table_symbols(update));
95 }
96 ast::Stmt::Delete(delete) => {
97 symbols.extend(cte_table_symbols(delete));
98 }
99
100 _ => {}
101 }
102 }
103
104 symbols
105}
106
107fn cte_table_symbols(stmt: impl ast::HasWithClause) -> Vec<DocumentSymbol> {
108 let Some(with_clause) = stmt.with_clause() else {
109 return vec![];
110 };
111
112 with_clause
113 .with_tables()
114 .filter_map(create_cte_table_symbol)
115 .collect()
116}
117
118fn create_cte_table_symbol(with_table: ast::WithTable) -> Option<DocumentSymbol> {
119 let name_node = with_table.name()?;
120 let name = name_node.syntax().text().to_string();
121
122 let full_range = with_table.syntax().text_range();
123 let focus_range = name_node.syntax().text_range();
124
125 let mut children = vec![];
126 if let Some(column_list) = with_table.column_list() {
127 for column in column_list.columns() {
128 if let Some(column_symbol) = create_column_symbol(column) {
129 children.push(column_symbol);
130 }
131 }
132 }
133
134 Some(DocumentSymbol {
135 name,
136 detail: None,
137 kind: DocumentSymbolKind::Table,
138 full_range,
139 focus_range,
140 children,
141 })
142}
143
144fn create_schema_symbol(create_schema: ast::CreateSchema) -> Option<DocumentSymbol> {
145 let (name, focus_range) = if let Some(name_node) = create_schema.name() {
146 (
147 name_node.syntax().text().to_string(),
148 name_node.syntax().text_range(),
149 )
150 } else if let Some(schema_name_ref) = create_schema
151 .schema_authorization()
152 .and_then(|authorization| authorization.role())
153 .and_then(|role| role.name_ref())
154 {
155 (
156 schema_name_ref.syntax().text().to_string(),
157 schema_name_ref.syntax().text_range(),
158 )
159 } else {
160 return None;
161 };
162
163 let full_range = create_schema.syntax().text_range();
164
165 Some(DocumentSymbol {
166 name,
167 detail: None,
168 kind: DocumentSymbolKind::Schema,
169 full_range,
170 focus_range,
171 children: vec![],
172 })
173}
174
175fn create_table_symbol(
176 binder: &binder::Binder,
177 create_table: ast::CreateTable,
178) -> Option<DocumentSymbol> {
179 let path = create_table.path()?;
180 let segment = path.segment()?;
181 let name_node = segment.name()?;
182
183 let (schema, table_name) = resolve_table_info(binder, &path)?;
184 let name = format!("{}.{}", schema.0, table_name);
185
186 let full_range = create_table.syntax().text_range();
187 let focus_range = name_node.syntax().text_range();
188
189 let mut children = vec![];
190 if let Some(table_arg_list) = create_table.table_arg_list() {
191 for arg in table_arg_list.args() {
192 if let ast::TableArg::Column(column) = arg
193 && let Some(column_symbol) = create_column_symbol(column)
194 {
195 children.push(column_symbol);
196 }
197 }
198 }
199
200 Some(DocumentSymbol {
201 name,
202 detail: None,
203 kind: DocumentSymbolKind::Table,
204 full_range,
205 focus_range,
206 children,
207 })
208}
209
210fn create_view_symbol(
211 binder: &binder::Binder,
212 create_view: ast::CreateView,
213) -> Option<DocumentSymbol> {
214 let path = create_view.path()?;
215 let segment = path.segment()?;
216 let name_node = segment.name()?;
217
218 let (schema, view_name) = resolve_view_info(binder, &path)?;
219 let name = format!("{}.{}", schema.0, view_name);
220
221 let full_range = create_view.syntax().text_range();
222 let focus_range = name_node.syntax().text_range();
223
224 let mut children = vec![];
225 if let Some(column_list) = create_view.column_list() {
226 for column in column_list.columns() {
227 if let Some(column_symbol) = create_column_symbol(column) {
228 children.push(column_symbol);
229 }
230 }
231 }
232
233 Some(DocumentSymbol {
234 name,
235 detail: None,
236 kind: DocumentSymbolKind::View,
237 full_range,
238 focus_range,
239 children,
240 })
241}
242
243fn create_materialized_view_symbol(
244 binder: &binder::Binder,
245 create_view: ast::CreateMaterializedView,
246) -> Option<DocumentSymbol> {
247 let path = create_view.path()?;
248 let segment = path.segment()?;
249 let name_node = segment.name()?;
250
251 let (schema, view_name) = resolve_materialized_view_info(binder, &path)?;
252 let name = format!("{}.{}", schema.0, view_name);
253
254 let full_range = create_view.syntax().text_range();
255 let focus_range = name_node.syntax().text_range();
256
257 let mut children = vec![];
258 if let Some(column_list) = create_view.column_list() {
259 for column in column_list.columns() {
260 if let Some(column_symbol) = create_column_symbol(column) {
261 children.push(column_symbol);
262 }
263 }
264 }
265
266 Some(DocumentSymbol {
267 name,
268 detail: None,
269 kind: DocumentSymbolKind::MaterializedView,
270 full_range,
271 focus_range,
272 children,
273 })
274}
275
276fn create_function_symbol(
277 binder: &binder::Binder,
278 create_function: ast::CreateFunction,
279) -> Option<DocumentSymbol> {
280 let path = create_function.path()?;
281 let segment = path.segment()?;
282 let name_node = segment.name()?;
283
284 let (schema, function_name) = resolve_function_info(binder, &path)?;
285 let name = format!("{}.{}", schema.0, function_name);
286
287 let full_range = create_function.syntax().text_range();
288 let focus_range = name_node.syntax().text_range();
289
290 Some(DocumentSymbol {
291 name,
292 detail: None,
293 kind: DocumentSymbolKind::Function,
294 full_range,
295 focus_range,
296 children: vec![],
297 })
298}
299
300fn create_aggregate_symbol(
301 binder: &binder::Binder,
302 create_aggregate: ast::CreateAggregate,
303) -> Option<DocumentSymbol> {
304 let path = create_aggregate.path()?;
305 let segment = path.segment()?;
306 let name_node = segment.name()?;
307
308 let (schema, aggregate_name) = resolve_aggregate_info(binder, &path)?;
309 let name = format!("{}.{}", schema.0, aggregate_name);
310
311 let full_range = create_aggregate.syntax().text_range();
312 let focus_range = name_node.syntax().text_range();
313
314 Some(DocumentSymbol {
315 name,
316 detail: None,
317 kind: DocumentSymbolKind::Aggregate,
318 full_range,
319 focus_range,
320 children: vec![],
321 })
322}
323
324fn create_procedure_symbol(
325 binder: &binder::Binder,
326 create_procedure: ast::CreateProcedure,
327) -> Option<DocumentSymbol> {
328 let path = create_procedure.path()?;
329 let segment = path.segment()?;
330 let name_node = segment.name()?;
331
332 let (schema, procedure_name) = resolve_procedure_info(binder, &path)?;
333 let name = format!("{}.{}", schema.0, procedure_name);
334
335 let full_range = create_procedure.syntax().text_range();
336 let focus_range = name_node.syntax().text_range();
337
338 Some(DocumentSymbol {
339 name,
340 detail: None,
341 kind: DocumentSymbolKind::Procedure,
342 full_range,
343 focus_range,
344 children: vec![],
345 })
346}
347
348fn create_type_symbol(
349 binder: &binder::Binder,
350 create_type: ast::CreateType,
351) -> Option<DocumentSymbol> {
352 let path = create_type.path()?;
353 let segment = path.segment()?;
354 let name_node = segment.name()?;
355
356 let (schema, type_name) = resolve_type_info(binder, &path)?;
357 let name = format!("{}.{}", schema.0, type_name);
358
359 let full_range = create_type.syntax().text_range();
360 let focus_range = name_node.syntax().text_range();
361
362 let mut children = vec![];
363 if let Some(variant_list) = create_type.variant_list() {
364 for variant in variant_list.variants() {
365 if let Some(variant_symbol) = create_variant_symbol(variant) {
366 children.push(variant_symbol);
367 }
368 }
369 } else if let Some(column_list) = create_type.column_list() {
370 for column in column_list.columns() {
371 if let Some(column_symbol) = create_column_symbol(column) {
372 children.push(column_symbol);
373 }
374 }
375 }
376
377 Some(DocumentSymbol {
378 name,
379 detail: None,
380 kind: if create_type.variant_list().is_some() {
381 DocumentSymbolKind::Enum
382 } else {
383 DocumentSymbolKind::Type
384 },
385 full_range,
386 focus_range,
387 children,
388 })
389}
390
391fn create_column_symbol(column: ast::Column) -> Option<DocumentSymbol> {
392 let name_node = column.name()?;
393 let name = name_node.syntax().text().to_string();
394
395 let detail = column.ty().map(|t| t.syntax().text().to_string());
396
397 let full_range = column.syntax().text_range();
398 let focus_range = name_node.syntax().text_range();
399
400 Some(DocumentSymbol {
401 name,
402 detail,
403 kind: DocumentSymbolKind::Column,
404 full_range,
405 focus_range,
406 children: vec![],
407 })
408}
409
410fn create_variant_symbol(variant: ast::Variant) -> Option<DocumentSymbol> {
411 let literal = variant.literal()?;
412 let name = extract_string_literal(&literal)?;
413
414 let full_range = variant.syntax().text_range();
415 let focus_range = literal.syntax().text_range();
416
417 Some(DocumentSymbol {
418 name,
419 detail: None,
420 kind: DocumentSymbolKind::Variant,
421 full_range,
422 focus_range,
423 children: vec![],
424 })
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use annotate_snippets::{
431 AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle,
432 };
433 use insta::assert_snapshot;
434
435 fn symbols_not_found(sql: &str) {
436 let parse = ast::SourceFile::parse(sql);
437 let file = parse.tree();
438 let symbols = document_symbols(&file);
439 if !symbols.is_empty() {
440 panic!("Symbols found. If this is expected, use `symbols` instead.")
441 }
442 }
443
444 fn symbols(sql: &str) -> String {
445 let parse = ast::SourceFile::parse(sql);
446 let file = parse.tree();
447 let symbols = document_symbols(&file);
448 if symbols.is_empty() {
449 panic!("No symbols found. If this is expected, use `symbols_not_found` instead.")
450 }
451
452 let mut output = vec![];
453 for symbol in symbols {
454 let group = symbol_to_group(&symbol, sql);
455 output.push(group);
456 }
457 Renderer::plain()
458 .decor_style(DecorStyle::Unicode)
459 .render(&output)
460 .to_string()
461 }
462
463 fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> {
464 let kind = match symbol.kind {
465 DocumentSymbolKind::Schema => "schema",
466 DocumentSymbolKind::Table => "table",
467 DocumentSymbolKind::View => "view",
468 DocumentSymbolKind::MaterializedView => "materialized view",
469 DocumentSymbolKind::Function => "function",
470 DocumentSymbolKind::Aggregate => "aggregate",
471 DocumentSymbolKind::Procedure => "procedure",
472 DocumentSymbolKind::Type => "type",
473 DocumentSymbolKind::Enum => "enum",
474 DocumentSymbolKind::Column => "column",
475 DocumentSymbolKind::Variant => "variant",
476 };
477
478 let title = if let Some(detail) = &symbol.detail {
479 format!("{}: {} {}", kind, symbol.name, detail)
480 } else {
481 format!("{}: {}", kind, symbol.name)
482 };
483
484 let snippet = Snippet::source(sql)
485 .fold(true)
486 .annotation(
487 AnnotationKind::Primary
488 .span(symbol.focus_range.into())
489 .label("focus range"),
490 )
491 .annotation(
492 AnnotationKind::Context
493 .span(symbol.full_range.into())
494 .label("full range"),
495 );
496
497 let mut group = Level::INFO.primary_title(title.clone()).element(snippet);
498
499 if !symbol.children.is_empty() {
500 let child_labels: Vec<String> = symbol
501 .children
502 .iter()
503 .map(|child| {
504 let kind = match child.kind {
505 DocumentSymbolKind::Column => "column",
506 DocumentSymbolKind::Variant => "variant",
507 _ => unreachable!("only columns and variants can be children"),
508 };
509 if let Some(detail) = &child.detail {
510 format!("{}: {} {}", kind, child.name, detail)
511 } else {
512 format!("{}: {}", kind, child.name)
513 }
514 })
515 .collect();
516
517 let mut children_snippet = Snippet::source(sql).fold(true);
518
519 for (i, child) in symbol.children.iter().enumerate() {
520 children_snippet = children_snippet
521 .annotation(
522 AnnotationKind::Context
523 .span(child.full_range.into())
524 .label(format!("full range for `{}`", child_labels[i].clone())),
525 )
526 .annotation(
527 AnnotationKind::Primary
528 .span(child.focus_range.into())
529 .label("focus range"),
530 );
531 }
532
533 group = group.element(children_snippet);
534 }
535
536 group
537 }
538
539 #[test]
540 fn create_table() {
541 assert_snapshot!(symbols("
542create table users (
543 id int,
544 email citext
545);"), @r"
546 info: table: public.users
547 ╭▸
548 2 │ create table users (
549 │ │ ━━━━━ focus range
550 │ ┌─┘
551 │ │
552 3 │ │ id int,
553 4 │ │ email citext
554 5 │ │ );
555 │ └─┘ full range
556 │
557 ⸬
558 3 │ id int,
559 │ ┯━────
560 │ │
561 │ full range for `column: id int`
562 │ focus range
563 4 │ email citext
564 │ ┯━━━━───────
565 │ │
566 │ full range for `column: email citext`
567 ╰╴ focus range
568 ");
569 }
570
571 #[test]
572 fn create_schema() {
573 assert_snapshot!(symbols("
574create schema foo;
575"), @r"
576 info: schema: foo
577 ╭▸
578 2 │ create schema foo;
579 │ ┬─────────────┯━━
580 │ │ │
581 │ │ focus range
582 ╰╴full range
583 ");
584 }
585
586 #[test]
587 fn create_schema_authorization() {
588 assert_snapshot!(symbols("
589create schema authorization foo;
590"), @r"
591 info: schema: foo
592 ╭▸
593 2 │ create schema authorization foo;
594 │ ┬───────────────────────────┯━━
595 │ │ │
596 │ │ focus range
597 ╰╴full range
598 ");
599 }
600
601 #[test]
602 fn create_function() {
603 assert_snapshot!(
604 symbols("create function hello() returns void as $$ select 1; $$ language sql;"),
605 @r"
606 info: function: public.hello
607 ╭▸
608 1 │ create function hello() returns void as $$ select 1; $$ language sql;
609 │ ┬───────────────┯━━━━───────────────────────────────────────────────
610 │ │ │
611 │ │ focus range
612 ╰╴full range
613 "
614 );
615 }
616
617 #[test]
618 fn create_materialized_view() {
619 assert_snapshot!(
620 symbols("create materialized view reports as select 1;"),
621 @r"
622 info: materialized view: public.reports
623 ╭▸
624 1 │ create materialized view reports as select 1;
625 │ ┬────────────────────────┯━━━━━━────────────
626 │ │ │
627 │ │ focus range
628 ╰╴full range
629 "
630 );
631 }
632
633 #[test]
634 fn create_aggregate() {
635 assert_snapshot!(
636 symbols("create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);"),
637 @r"
638 info: aggregate: public.myavg
639 ╭▸
640 1 │ create aggregate myavg(int) (sfunc = int4_avg_accum, stype = _int8);
641 │ ┬────────────────┯━━━━─────────────────────────────────────────────
642 │ │ │
643 │ │ focus range
644 ╰╴full range
645 "
646 );
647 }
648
649 #[test]
650 fn create_procedure() {
651 assert_snapshot!(
652 symbols("create procedure hello() language sql as $$ select 1; $$;"),
653 @r"
654 info: procedure: public.hello
655 ╭▸
656 1 │ create procedure hello() language sql as $$ select 1; $$;
657 │ ┬────────────────┯━━━━──────────────────────────────────
658 │ │ │
659 │ │ focus range
660 ╰╴full range
661 "
662 );
663 }
664
665 #[test]
666 fn multiple_symbols() {
667 assert_snapshot!(symbols("
668create table users (id int);
669create table posts (id int);
670create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
671"), @r"
672 info: table: public.users
673 ╭▸
674 2 │ create table users (id int);
675 │ ┬────────────┯━━━━─────────
676 │ │ │
677 │ │ focus range
678 │ full range
679 │
680 ⸬
681 2 │ create table users (id int);
682 │ ┯━────
683 │ │
684 │ full range for `column: id int`
685 │ focus range
686 ╰╴
687 info: table: public.posts
688 ╭▸
689 3 │ create table posts (id int);
690 │ ┬────────────┯━━━━─────────
691 │ │ │
692 │ │ focus range
693 │ full range
694 │
695 ⸬
696 3 │ create table posts (id int);
697 │ ┯━────
698 │ │
699 │ full range for `column: id int`
700 ╰╴ focus range
701 info: function: public.get_user
702 ╭▸
703 4 │ create function get_user(user_id int) returns void as $$ select 1; $$ language sql;
704 │ ┬───────────────┯━━━━━━━──────────────────────────────────────────────────────────
705 │ │ │
706 │ │ focus range
707 ╰╴full range
708 ");
709 }
710
711 #[test]
712 fn qualified_names() {
713 assert_snapshot!(symbols("
714create table public.users (id int);
715create function my_schema.hello() returns void as $$ select 1; $$ language sql;
716"), @r"
717 info: table: public.users
718 ╭▸
719 2 │ create table public.users (id int);
720 │ ┬───────────────────┯━━━━─────────
721 │ │ │
722 │ │ focus range
723 │ full range
724 │
725 ⸬
726 2 │ create table public.users (id int);
727 │ ┯━────
728 │ │
729 │ full range for `column: id int`
730 │ focus range
731 ╰╴
732 info: function: my_schema.hello
733 ╭▸
734 3 │ create function my_schema.hello() returns void as $$ select 1; $$ language sql;
735 │ ┬─────────────────────────┯━━━━───────────────────────────────────────────────
736 │ │ │
737 │ │ focus range
738 ╰╴full range
739 ");
740 }
741
742 #[test]
743 fn create_type() {
744 assert_snapshot!(
745 symbols("create type status as enum ('active', 'inactive');"),
746 @r"
747 info: enum: public.status
748 ╭▸
749 1 │ create type status as enum ('active', 'inactive');
750 │ ┬───────────┯━━━━━───────────────────────────────
751 │ │ │
752 │ │ focus range
753 │ full range
754 │
755 ⸬
756 1 │ create type status as enum ('active', 'inactive');
757 │ ┯━━━━━━━ ┯━━━━━━━━━
758 │ │ │
759 │ │ full range for `variant: inactive`
760 │ │ focus range
761 │ full range for `variant: active`
762 ╰╴ focus range
763 "
764 );
765 }
766
767 #[test]
768 fn create_type_composite() {
769 assert_snapshot!(
770 symbols("create type person as (name text, age int);"),
771 @r"
772 info: type: public.person
773 ╭▸
774 1 │ create type person as (name text, age int);
775 │ ┬───────────┯━━━━━────────────────────────
776 │ │ │
777 │ │ focus range
778 │ full range
779 │
780 ⸬
781 1 │ create type person as (name text, age int);
782 │ ┯━━━───── ┯━━────
783 │ │ │
784 │ │ full range for `column: age int`
785 │ │ focus range
786 │ full range for `column: name text`
787 ╰╴ focus range
788 "
789 );
790 }
791
792 #[test]
793 fn create_type_composite_multiple_columns() {
794 assert_snapshot!(
795 symbols("create type address as (street text, city text, zip varchar(10));"),
796 @r"
797 info: type: public.address
798 ╭▸
799 1 │ create type address as (street text, city text, zip varchar(10));
800 │ ┬───────────┯━━━━━━─────────────────────────────────────────────
801 │ │ │
802 │ │ focus range
803 │ full range
804 │
805 ⸬
806 1 │ create type address as (street text, city text, zip varchar(10));
807 │ ┯━━━━━───── ┯━━━───── ┯━━────────────
808 │ │ │ │
809 │ │ │ full range for `column: zip varchar(10)`
810 │ │ │ focus range
811 │ │ full range for `column: city text`
812 │ │ focus range
813 │ full range for `column: street text`
814 ╰╴ focus range
815 "
816 );
817 }
818
819 #[test]
820 fn create_type_with_schema() {
821 assert_snapshot!(
822 symbols("create type myschema.status as enum ('active', 'inactive');"),
823 @r"
824 info: enum: myschema.status
825 ╭▸
826 1 │ create type myschema.status as enum ('active', 'inactive');
827 │ ┬────────────────────┯━━━━━───────────────────────────────
828 │ │ │
829 │ │ focus range
830 │ full range
831 │
832 ⸬
833 1 │ create type myschema.status as enum ('active', 'inactive');
834 │ ┯━━━━━━━ ┯━━━━━━━━━
835 │ │ │
836 │ │ full range for `variant: inactive`
837 │ │ focus range
838 │ full range for `variant: active`
839 ╰╴ focus range
840 "
841 );
842 }
843
844 #[test]
845 fn create_type_enum_multiple_variants() {
846 assert_snapshot!(
847 symbols("create type priority as enum ('low', 'medium', 'high', 'urgent');"),
848 @r"
849 info: enum: public.priority
850 ╭▸
851 1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
852 │ ┬───────────┯━━━━━━━────────────────────────────────────────────
853 │ │ │
854 │ │ focus range
855 │ full range
856 │
857 ⸬
858 1 │ create type priority as enum ('low', 'medium', 'high', 'urgent');
859 │ ┯━━━━ ┯━━━━━━━ ┯━━━━━ ┯━━━━━━━
860 │ │ │ │ │
861 │ │ │ │ full range for `variant: urgent`
862 │ │ │ │ focus range
863 │ │ │ full range for `variant: high`
864 │ │ │ focus range
865 │ │ full range for `variant: medium`
866 │ │ focus range
867 │ full range for `variant: low`
868 ╰╴ focus range
869 "
870 );
871 }
872
873 #[test]
874 fn empty_file() {
875 symbols_not_found("")
876 }
877
878 #[test]
879 fn non_create_statements() {
880 symbols_not_found("select * from users;")
881 }
882
883 #[test]
884 fn cte_table() {
885 assert_snapshot!(
886 symbols("
887with recent_users as (
888 select id, email as user_email
889 from users
890)
891select * from recent_users;
892"),
893 @r"
894 info: table: recent_users
895 ╭▸
896 2 │ with recent_users as (
897 │ │━━━━━━━━━━━
898 │ │
899 │ ┌──────focus range
900 │ │
901 3 │ │ select id, email as user_email
902 4 │ │ from users
903 5 │ │ )
904 ╰╴└─┘ full range
905 "
906 );
907 }
908
909 #[test]
910 fn cte_table_with_column_list() {
911 assert_snapshot!(
912 symbols("
913with t(a, b, c) as (
914 select 1, 2, 3
915)
916select * from t;
917"),
918 @r"
919 info: table: t
920 ╭▸
921 2 │ with t(a, b, c) as (
922 │ ━ focus range
923 │ ┌──────┘
924 │ │
925 3 │ │ select 1, 2, 3
926 4 │ │ )
927 │ └─┘ full range
928 │
929 ⸬
930 2 │ with t(a, b, c) as (
931 │ ┯ ┯ ┯
932 │ │ │ │
933 │ │ │ full range for `column: c`
934 │ │ │ focus range
935 │ │ full range for `column: b`
936 │ │ focus range
937 │ full range for `column: a`
938 ╰╴ focus range
939 "
940 );
941 }
942}