diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index 0053e1a1..4d601ac7 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -88,6 +88,7 @@ fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) { ast::Stmt::CreateMaterializedView(create_view) => { bind_create_materialized_view(b, create_view) } + ast::Stmt::CreateSequence(create_sequence) => bind_create_sequence(b, create_sequence), ast::Stmt::Set(set) => bind_set(b, set), _ => {} } @@ -331,6 +332,34 @@ fn bind_create_materialized_view(b: &mut Binder, create_view: ast::CreateMateria b.scopes[root].insert(view_name, view_id); } +fn bind_create_sequence(b: &mut Binder, create_sequence: ast::CreateSequence) { + let Some(path) = create_sequence.path() else { + return; + }; + + let Some(sequence_name) = item_name(&path) else { + return; + }; + + let name_ptr = path_to_ptr(&path); + let is_temp = + create_sequence.temp_token().is_some() || create_sequence.temporary_token().is_some(); + + let Some(schema) = schema_name(b, &path, is_temp) else { + return; + }; + + let sequence_id = b.symbols.alloc(Symbol { + kind: SymbolKind::Sequence, + ptr: name_ptr, + schema, + params: None, + }); + + let root = b.root_scope(); + b.scopes[root].insert(sequence_name, sequence_id); +} + fn item_name(path: &ast::Path) -> Option { let segment = path.segment()?; diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index f0b78ca3..41aea480 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -8,6 +8,20 @@ pub(crate) enum NameRefClass { DropType, DropView, DropMaterializedView, + DropSequence, + ForeignKeyTable, + ForeignKeyColumn, + ForeignKeyLocalColumn, + CheckConstraintColumn, + GeneratedColumn, + UniqueConstraintColumn, + PrimaryKeyConstraintColumn, + NotNullConstraintColumn, + ExcludeConstraintColumn, + PartitionByColumn, + PartitionOfTable, + LikeTable, + InheritsTable, DropFunction, DropAggregate, DropProcedure, @@ -36,13 +50,16 @@ pub(crate) enum NameRefClass { } pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option { - let mut in_partition_item = false; let mut in_call_expr = false; let mut in_arg_list = false; let mut in_column_list = false; let mut in_where_clause = false; let mut in_from_clause = false; let mut in_set_clause = false; + let mut in_constraint_exclusion_list = false; + let mut in_constraint_include_clause = false; + let mut in_constraint_where_clause = false; + let mut in_partition_item = false; // TODO: can we combine this if and the one that follows? if let Some(parent) = name_ref.syntax().parent() @@ -170,6 +187,62 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option if ast::DropMaterializedView::can_cast(ancestor.kind()) { return Some(NameRefClass::DropMaterializedView); } + if ast::DropSequence::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropSequence); + } + if let Some(foreign_key) = ast::ForeignKeyConstraint::cast(ancestor.clone()) { + if in_column_list { + // TODO: ast is too "flat" here, we need a unique node for to + // and from columns to differentiate which would help us avoid + // this + if let Some(to_columns) = foreign_key.to_columns() + && to_columns + .syntax() + .text_range() + .contains_range(name_ref.syntax().text_range()) + { + return Some(NameRefClass::ForeignKeyColumn); + } + if let Some(from_columns) = foreign_key.from_columns() + && from_columns + .syntax() + .text_range() + .contains_range(name_ref.syntax().text_range()) + { + return Some(NameRefClass::ForeignKeyLocalColumn); + } + } else { + return Some(NameRefClass::ForeignKeyTable); + } + } + if ast::CheckConstraint::can_cast(ancestor.kind()) { + return Some(NameRefClass::CheckConstraintColumn); + } + if ast::GeneratedConstraint::can_cast(ancestor.kind()) { + return Some(NameRefClass::GeneratedColumn); + } + if in_column_list && ast::UniqueConstraint::can_cast(ancestor.kind()) { + return Some(NameRefClass::UniqueConstraintColumn); + } + if in_column_list && ast::PrimaryKeyConstraint::can_cast(ancestor.kind()) { + return Some(NameRefClass::PrimaryKeyConstraintColumn); + } + if ast::NotNullConstraint::can_cast(ancestor.kind()) { + return Some(NameRefClass::NotNullConstraintColumn); + } + if (in_constraint_exclusion_list + || in_constraint_include_clause + || in_constraint_where_clause) + && ast::ExcludeConstraint::can_cast(ancestor.kind()) + { + return Some(NameRefClass::ExcludeConstraintColumn); + } + if ast::LikeClause::can_cast(ancestor.kind()) { + return Some(NameRefClass::LikeTable); + } + if ast::Inherits::can_cast(ancestor.kind()) { + return Some(NameRefClass::InheritsTable); + } if ast::CastExpr::can_cast(ancestor.kind()) && in_type { return Some(NameRefClass::TypeReference); } @@ -197,15 +270,18 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option { return Some(NameRefClass::CreateSchema); } - if ast::PartitionItem::can_cast(ancestor.kind()) { - in_partition_item = true; - } if ast::CreateIndex::can_cast(ancestor.kind()) { if in_partition_item { return Some(NameRefClass::CreateIndexColumn); } return Some(NameRefClass::CreateIndex); } + if in_partition_item && ast::CreateTable::can_cast(ancestor.kind()) { + return Some(NameRefClass::PartitionByColumn); + } + if ast::PartitionOf::can_cast(ancestor.kind()) { + return Some(NameRefClass::PartitionOfTable); + } if ast::ArgList::can_cast(ancestor.kind()) { in_arg_list = true; } @@ -229,6 +305,18 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option if ast::ColumnList::can_cast(ancestor.kind()) { in_column_list = true; } + if ast::ConstraintExclusionList::can_cast(ancestor.kind()) { + in_constraint_exclusion_list = true; + } + if ast::ConstraintIncludeClause::can_cast(ancestor.kind()) { + in_constraint_include_clause = true; + } + if ast::WhereConditionClause::can_cast(ancestor.kind()) { + in_constraint_where_clause = true; + } + if ast::PartitionItem::can_cast(ancestor.kind()) { + in_partition_item = true; + } if ast::Insert::can_cast(ancestor.kind()) { if in_column_list { return Some(NameRefClass::InsertColumn); @@ -273,6 +361,7 @@ pub(crate) enum NameClass { CreateTable(ast::CreateTable), WithTable(ast::WithTable), CreateIndex(ast::CreateIndex), + CreateSequence(ast::CreateSequence), CreateType(ast::CreateType), CreateFunction(ast::CreateFunction), CreateAggregate(ast::CreateAggregate), @@ -307,6 +396,9 @@ pub(crate) fn classify_name(name: &ast::Name) -> Option { if let Some(create_index) = ast::CreateIndex::cast(ancestor.clone()) { return Some(NameClass::CreateIndex(create_index)); } + if let Some(create_sequence) = ast::CreateSequence::cast(ancestor.clone()) { + return Some(NameClass::CreateSequence(create_sequence)); + } if let Some(create_type) = ast::CreateType::cast(ancestor.clone()) { return Some(NameClass::CreateType(create_type)); } diff --git a/crates/squawk_ide/src/document_symbols.rs b/crates/squawk_ide/src/document_symbols.rs index 615a5c96..4b25d32c 100644 --- a/crates/squawk_ide/src/document_symbols.rs +++ b/crates/squawk_ide/src/document_symbols.rs @@ -9,6 +9,7 @@ use crate::resolve::{ #[derive(Debug)] pub enum DocumentSymbolKind { + Schema, Table, View, MaterializedView, @@ -40,6 +41,11 @@ pub fn document_symbols(file: &ast::SourceFile) -> Vec { for stmt in file.stmts() { match stmt { + ast::Stmt::CreateSchema(create_schema) => { + if let Some(symbol) = create_schema_symbol(create_schema) { + symbols.push(symbol); + } + } ast::Stmt::CreateTable(create_table) => { if let Some(symbol) = create_table_symbol(&binder, create_table) { symbols.push(symbol); @@ -135,6 +141,37 @@ fn create_cte_table_symbol(with_table: ast::WithTable) -> Option }) } +fn create_schema_symbol(create_schema: ast::CreateSchema) -> Option { + let (name, focus_range) = if let Some(name_node) = create_schema.name() { + ( + name_node.syntax().text().to_string(), + name_node.syntax().text_range(), + ) + } else if let Some(schema_name_ref) = create_schema + .schema_authorization() + .and_then(|authorization| authorization.role()) + .and_then(|role| role.name_ref()) + { + ( + schema_name_ref.syntax().text().to_string(), + schema_name_ref.syntax().text_range(), + ) + } else { + return None; + }; + + let full_range = create_schema.syntax().text_range(); + + Some(DocumentSymbol { + name, + detail: None, + kind: DocumentSymbolKind::Schema, + full_range, + focus_range, + children: vec![], + }) +} + fn create_table_symbol( binder: &binder::Binder, create_table: ast::CreateTable, @@ -425,6 +462,7 @@ mod tests { fn symbol_to_group<'a>(symbol: &DocumentSymbol, sql: &'a str) -> Group<'a> { let kind = match symbol.kind { + DocumentSymbolKind::Schema => "schema", DocumentSymbolKind::Table => "table", DocumentSymbolKind::View => "view", DocumentSymbolKind::MaterializedView => "materialized view", @@ -530,6 +568,36 @@ create table users ( "); } + #[test] + fn create_schema() { + assert_snapshot!(symbols(" +create schema foo; +"), @r" + info: schema: foo + ╭▸ + 2 │ create schema foo; + │ ┬─────────────┯━━ + │ │ │ + │ │ focus range + ╰╴full range + "); + } + + #[test] + fn create_schema_authorization() { + assert_snapshot!(symbols(" +create schema authorization foo; +"), @r" + info: schema: foo + ╭▸ + 2 │ create schema authorization foo; + │ ┬───────────────────────────┯━━ + │ │ │ + │ │ focus range + ╰╴full range + "); + } + #[test] fn create_function() { assert_snapshot!( diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index dfaf76db..8eee3def 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -232,6 +232,34 @@ drop table t$0; "); } + #[test] + fn goto_drop_sequence() { + assert_snapshot!(goto(" +create sequence s; +drop sequence s$0; +"), @r" + ╭▸ + 2 │ create sequence s; + │ ─ 2. destination + 3 │ drop sequence s; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_sequence_with_schema() { + assert_snapshot!(goto(" +create sequence foo.s; +drop sequence foo.s$0; +"), @r" + ╭▸ + 2 │ create sequence foo.s; + │ ─ 2. destination + 3 │ drop sequence foo.s; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_drop_table_with_schema() { assert_snapshot!(goto(" @@ -321,6 +349,286 @@ create table t$0(x bigint, y bigint); "); } + #[test] + fn goto_foreign_key_references_table() { + assert_snapshot!(goto(" +create table foo(id int); +create table bar( + id int, + foo_id int, + foreign key (foo_id) references foo$0(id) +); +"), @r" + ╭▸ + 2 │ create table foo(id int); + │ ─── 2. destination + ‡ + 6 │ foreign key (foo_id) references foo(id) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_foreign_key_references_column() { + assert_snapshot!(goto(" +create table foo(id int); +create table bar( + id int, + foo_id int, + foreign key (foo_id) references foo(id$0) +); +"), @r" + ╭▸ + 2 │ create table foo(id int); + │ ── 2. destination + ‡ + 6 │ foreign key (foo_id) references foo(id) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_foreign_key_local_column() { + assert_snapshot!(goto(" +create table bar( + id int, + foo_id int, + foreign key (foo_id$0) references foo(id) +); +"), @r" + ╭▸ + 4 │ foo_id int, + │ ────── 2. destination + 5 │ foreign key (foo_id) references foo(id) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_check_constraint_column() { + assert_snapshot!(goto(" +create table t ( + b int check (b > 10), + c int check (c$0 > 10) no inherit +); +"), @r" + ╭▸ + 4 │ c int check (c > 10) no inherit + │ ┬ ─ 1. source + │ │ + ╰╴ 2. destination + "); + } + + #[test] + fn goto_generated_column() { + assert_snapshot!(goto(" +create table t ( + a int, + b int generated always as ( + a$0 * 2 + ) stored +); +"), @r" + ╭▸ + 3 │ a int, + │ ─ 2. destination + 4 │ b int generated always as ( + 5 │ a * 2 + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_check_constraint_column() { + assert_snapshot!(goto(" +create table t ( + a int, + b text, + check (a$0 > b) +); +"), @r" + ╭▸ + 3 │ a int, + │ ─ 2. destination + 4 │ b text, + 5 │ check (a > b) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_unique_constraint_column() { + assert_snapshot!(goto(" +create table t ( + a int, + b text, + unique (a$0) +); +"), @r" + ╭▸ + 3 │ a int, + │ ─ 2. destination + 4 │ b text, + 5 │ unique (a) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_primary_key_constraint_column() { + assert_snapshot!(goto(" +create table t ( + id bigint generated always as identity, + inserted_at timestamptz not null default now(), + primary key (id, inserted_at$0) +); +"), @r" + ╭▸ + 4 │ inserted_at timestamptz not null default now(), + │ ─────────── 2. destination + 5 │ primary key (id, inserted_at) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_not_null_constraint_column() { + assert_snapshot!(goto(" +create table t ( + id integer, + name text, + not null name$0 +); +"), @r" + ╭▸ + 4 │ name text, + │ ──── 2. destination + 5 │ not null name + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_exclude_constraint_column() { + assert_snapshot!(goto(" +create table circles ( + c circle, + exclude using gist (c$0 with &&) +); +"), @r" + ╭▸ + 3 │ c circle, + │ ─ 2. destination + 4 │ exclude using gist (c with &&) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_exclude_constraint_include_column() { + assert_snapshot!(goto(" +create table t ( + a int, + b text, + exclude using btree ( a with > ) + include (a$0, b) +); +"), @r" + ╭▸ + 3 │ a int, + │ ─ 2. destination + ‡ + 6 │ include (a, b) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_exclude_constraint_where_column() { + assert_snapshot!(goto(" +create table t ( + a int, + b text, + exclude using btree ( a with > ) + where ( a$0 > 10 and b like '%foo' ) +); +"), @r" + ╭▸ + 3 │ a int, + │ ─ 2. destination + ‡ + 6 │ where ( a > 10 and b like '%foo' ) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_partition_by_column() { + assert_snapshot!(goto(" +create table t ( + id bigint generated always as identity, + inserted_at timestamptz not null default now() +) partition by range (inserted_at$0); +"), @r" + ╭▸ + 4 │ inserted_at timestamptz not null default now() + │ ─────────── 2. destination + 5 │ ) partition by range (inserted_at); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_table_partition_of_table() { + assert_snapshot!(goto(" +create table t (); +create table t_2026_01_02 partition of t$0 + for values from ('2026-01-02') to ('2026-01-03'); +"), @r" + ╭▸ + 2 │ create table t (); + │ ─ 2. destination + 3 │ create table t_2026_01_02 partition of t + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_table_like_clause() { + assert_snapshot!(goto(" +create table large_data_table(a text); +create table t ( + a text, + like large_data_table$0, + b integer +); +"), @r" + ╭▸ + 2 │ create table large_data_table(a text); + │ ──────────────── 2. destination + ‡ + 5 │ like large_data_table, + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_table_inherits() { + assert_snapshot!(goto(" +create table bar(a int); +create table t (a int) +inherits (foo.bar, bar$0, buzz); +"), @r" + ╭▸ + 2 │ create table bar(a int); + │ ─── 2. destination + 3 │ create table t (a int) + 4 │ inherits (foo.bar, bar, buzz); + ╰╴ ─ 1. source + "); + } + #[test] fn goto_drop_temp_table_shadows_public() { // temp tables shadow public tables when no schema is specified @@ -2112,6 +2420,20 @@ select a$0 from ((select 1 a)); "); } + #[test] + fn goto_subquery_column_star_table() { + assert_snapshot!(goto(" +create table foo.t(a int); +select a$0 from (select * from foo.t); +"), @r" + ╭▸ + 2 │ create table foo.t(a int); + │ ─ 2. destination + 3 │ select a from (select * from foo.t); + ╰╴ ─ 1. source + "); + } + #[test] fn goto_insert_table() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index d073cab7..4441f46e 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -18,7 +18,16 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { | NameRefClass::InsertColumn | NameRefClass::DeleteWhereColumn | NameRefClass::UpdateWhereColumn - | NameRefClass::UpdateSetColumn => return hover_column(file, &name_ref, &binder), + | NameRefClass::UpdateSetColumn + | NameRefClass::CheckConstraintColumn + | NameRefClass::GeneratedColumn + | NameRefClass::UniqueConstraintColumn + | NameRefClass::PrimaryKeyConstraintColumn + | NameRefClass::NotNullConstraintColumn + | NameRefClass::ExcludeConstraintColumn + | NameRefClass::PartitionByColumn => { + return hover_column(file, &name_ref, &binder); + } NameRefClass::TypeReference | NameRefClass::DropType => { return hover_type(file, &name_ref, &binder); } @@ -47,9 +56,17 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { | NameRefClass::UpdateTable | NameRefClass::SelectFromTable | NameRefClass::UpdateFromTable - | NameRefClass::SelectQualifiedColumnTable => { + | NameRefClass::SelectQualifiedColumnTable + | NameRefClass::ForeignKeyTable + | NameRefClass::LikeTable + | NameRefClass::InheritsTable + | NameRefClass::PartitionOfTable => { return hover_table(file, &name_ref, &binder); } + NameRefClass::ForeignKeyColumn | NameRefClass::ForeignKeyLocalColumn => { + return hover_column(file, &name_ref, &binder); + } + NameRefClass::DropSequence => return hover_sequence(file, &name_ref, &binder), NameRefClass::DropIndex => return hover_index(file, &name_ref, &binder), NameRefClass::DropFunction => return hover_function(file, &name_ref, &binder), NameRefClass::DropAggregate => return hover_aggregate(file, &name_ref, &binder), @@ -87,6 +104,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { NameClass::CreateIndex(create_index) => { return format_create_index(&create_index, &binder); } + NameClass::CreateSequence(create_sequence) => { + return format_create_sequence(&create_sequence, &binder); + } NameClass::CreateType(create_type) => { return format_create_type(&create_type, &binder); } @@ -285,6 +305,23 @@ fn hover_index( format_create_index(&create_index, binder) } +fn hover_sequence( + file: &ast::SourceFile, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let sequence_ptr = resolve::resolve_name_ref(binder, name_ref)?; + + let root = file.syntax(); + let sequence_name_node = sequence_ptr.to_node(root); + + let create_sequence = sequence_name_node + .ancestors() + .find_map(ast::CreateSequence::cast)?; + + format_create_sequence(&create_sequence, binder) +} + fn hover_type( file: &ast::SourceFile, name_ref: &ast::NameRef, @@ -387,6 +424,21 @@ fn view_schema(create_view: &ast::CreateView, binder: &binder::Binder) -> Option search_path.first().map(|s| s.to_string()) } +fn sequence_schema( + create_sequence: &ast::CreateSequence, + binder: &binder::Binder, +) -> Option { + let is_temp = + create_sequence.temp_token().is_some() || create_sequence.temporary_token().is_some(); + if is_temp { + return Some("pg_temp".to_string()); + } + + let position = create_sequence.syntax().text_range().start(); + let search_path = binder.search_path_at(position); + search_path.first().map(|s| s.to_string()) +} + fn format_create_index(create_index: &ast::CreateIndex, binder: &binder::Binder) -> Option { let index_name = create_index.name()?.syntax().text().to_string(); @@ -405,6 +457,23 @@ fn format_create_index(create_index: &ast::CreateIndex, binder: &binder::Binder) )) } +fn format_create_sequence( + create_sequence: &ast::CreateSequence, + binder: &binder::Binder, +) -> Option { + let path = create_sequence.path()?; + let segment = path.segment()?; + let sequence_name = segment.name()?.syntax().text().to_string(); + + let schema = if let Some(qualifier) = path.qualifier() { + qualifier.syntax().text().to_string() + } else { + sequence_schema(create_sequence, binder)? + }; + + Some(format!("sequence {}.{}", schema, sequence_name)) +} + fn index_schema(create_index: &ast::CreateIndex, binder: &binder::Binder) -> Option { let position = create_index.syntax().text_range().start(); let search_path = binder.search_path_at(position); diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index b7a859ad..e0844b95 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -91,6 +91,122 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let position = name_ref.syntax().text_range().start(); resolve_view(binder, &view_name, &schema, position) } + NameRefClass::DropSequence => { + let path = find_containing_path(name_ref)?; + let sequence_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + resolve_sequence(binder, &sequence_name, &schema, position) + } + NameRefClass::ForeignKeyTable => { + let foreign_key = name_ref + .syntax() + .ancestors() + .find_map(ast::ForeignKeyConstraint::cast)?; + let path = foreign_key.path()?; + let table_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + resolve_table(binder, &table_name, &schema, position) + } + NameRefClass::ForeignKeyColumn => { + let foreign_key = name_ref + .syntax() + .ancestors() + .find_map(ast::ForeignKeyConstraint::cast)?; + let path = foreign_key.path()?; + let column_name = Name::from_node(name_ref); + resolve_column_for_path(binder, &path, column_name) + } + NameRefClass::ForeignKeyLocalColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::CheckConstraintColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::GeneratedColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::UniqueConstraintColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::PrimaryKeyConstraintColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::NotNullConstraintColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::ExcludeConstraintColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::PartitionByColumn => { + let create_table = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateTable::cast)?; + let column_name = Name::from_node(name_ref); + find_column_in_create_table(&create_table, &column_name) + } + NameRefClass::PartitionOfTable => { + let path = find_containing_path(name_ref)?; + let table_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + resolve_table(binder, &table_name, &schema, position) + } + NameRefClass::LikeTable => { + let like_clause = name_ref + .syntax() + .ancestors() + .find_map(ast::LikeClause::cast)?; + let path = like_clause.path()?; + let table_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + resolve_table(binder, &table_name, &schema, position) + } + NameRefClass::InheritsTable => { + let path = find_containing_path(name_ref)?; + let table_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + resolve_table(binder, &table_name, &schema, position) + } NameRefClass::DropFunction => { let function_sig = name_ref .syntax() @@ -289,6 +405,21 @@ fn resolve_view( resolve_for_kind(binder, view_name, schema, position, SymbolKind::View) } +fn resolve_sequence( + binder: &Binder, + sequence_name: &Name, + schema: &Option, + position: TextSize, +) -> Option { + resolve_for_kind( + binder, + sequence_name, + schema, + position, + SymbolKind::Sequence, + ) +} + fn resolve_for_kind( binder: &Binder, name: &Name, @@ -677,11 +808,11 @@ fn resolve_from_item_for_column( ) -> Option { let column_name = Name::from_node(name_ref); if let Some(paren_select) = from_item.paren_select() { - return resolve_subquery_column(&paren_select, &column_name); + return resolve_subquery_column(binder, &paren_select, name_ref, &column_name); } if let Some(paren_expr) = from_item.paren_expr() { - return resolve_column_from_paren_expr(&paren_expr, &column_name); + return resolve_column_from_paren_expr(binder, &paren_expr, name_ref, &column_name); } let (table_name, schema) = if let Some(name_ref_node) = from_item.name_ref() { @@ -1189,7 +1320,9 @@ fn resolve_cte_column( } fn resolve_subquery_column( + binder: &Binder, paren_select: &ast::ParenSelect, + name_ref: &ast::NameRef, column_name: &Name, ) -> Option { let select_variant = paren_select.select()?; @@ -1207,6 +1340,27 @@ fn resolve_subquery_column( { return Some(SyntaxNodePtr::new(&node)); } + if matches!(col_name, ColumnName::Star) { + if let Some(from_clause) = subquery_select.from_clause() { + for from_item in from_clause.from_items() { + if let Some(result) = + resolve_from_item_for_column(binder, &from_item, name_ref) + { + return Some(result); + } + } + + for join_expr in from_clause.join_exprs() { + if let Some(result) = + resolve_from_join_expr(&join_expr, &|from_item: &ast::FromItem| { + resolve_from_item_for_column(binder, from_item, name_ref) + }) + { + return Some(result); + } + } + } + } } } @@ -1214,7 +1368,9 @@ fn resolve_subquery_column( } fn resolve_column_from_paren_expr( + binder: &Binder, paren_expr: &ast::ParenExpr, + name_ref: &ast::NameRef, column_name: &Name, ) -> Option { if let Some(select) = paren_expr.select() { @@ -1234,13 +1390,13 @@ fn resolve_column_from_paren_expr( } if let Some(ast::Expr::ParenExpr(paren_expr)) = paren_expr.expr() { - return resolve_column_from_paren_expr(&paren_expr, column_name); + return resolve_column_from_paren_expr(binder, &paren_expr, name_ref, column_name); } if let Some(from_item) = paren_expr.from_item() && let Some(paren_select) = from_item.paren_select() { - return resolve_subquery_column(&paren_select, column_name); + return resolve_subquery_column(binder, &paren_select, name_ref, column_name); } None diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index c0aff9e2..225da79c 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -52,6 +52,7 @@ pub(crate) enum SymbolKind { Schema, Type, View, + Sequence, } #[derive(Clone, Debug)] diff --git a/crates/squawk_server/src/lib.rs b/crates/squawk_server/src/lib.rs index 916057c2..f10bfd53 100644 --- a/crates/squawk_server/src/lib.rs +++ b/crates/squawk_server/src/lib.rs @@ -335,6 +335,7 @@ fn handle_document_symbol( name: sym.name, detail: sym.detail, kind: match sym.kind { + DocumentSymbolKind::Schema => SymbolKind::NAMESPACE, DocumentSymbolKind::Table => SymbolKind::STRUCT, DocumentSymbolKind::View => SymbolKind::STRUCT, DocumentSymbolKind::MaterializedView => SymbolKind::STRUCT, diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index 982d6736..8e5be107 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -392,6 +392,7 @@ fn convert_document_symbol( name: symbol.name, detail: symbol.detail, kind: match symbol.kind { + squawk_ide::document_symbols::DocumentSymbolKind::Schema => "schema", squawk_ide::document_symbols::DocumentSymbolKind::Table => "table", squawk_ide::document_symbols::DocumentSymbolKind::View => "view", squawk_ide::document_symbols::DocumentSymbolKind::MaterializedView => {