From 89d60a56985c24f47bf55e4129c75235f70c8bdf Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Fri, 26 Dec 2025 14:04:51 -0500 Subject: [PATCH 1/2] ide: goto def on create/drop index --- crates/squawk_ide/src/binder.rs | 23 +++ crates/squawk_ide/src/goto_definition.rs | 217 +++++++++++++++++++++++ crates/squawk_ide/src/resolve.rs | 93 +++++++++- crates/squawk_ide/src/symbols.rs | 1 + 4 files changed, 333 insertions(+), 1 deletion(-) diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index 6651e9d5..f7a4a5c5 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -78,6 +78,7 @@ fn bind_file(b: &mut Binder, file: &ast::SourceFile) { fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) { match stmt { ast::Stmt::CreateTable(create_table) => bind_create_table(b, create_table), + ast::Stmt::CreateIndex(create_index) => bind_create_index(b, create_index), ast::Stmt::Set(set) => bind_set(b, set), _ => {} } @@ -106,6 +107,28 @@ fn bind_create_table(b: &mut Binder, create_table: ast::CreateTable) { b.scopes[root].insert(table_name, table_id); } +fn bind_create_index(b: &mut Binder, create_index: ast::CreateIndex) { + let Some(name) = create_index.name() else { + return; + }; + + let index_name = Name::new(name.syntax().text().to_string()); + let name_ptr = SyntaxNodePtr::new(name.syntax()); + + let Some(schema) = b.current_search_path().first().cloned() else { + return; + }; + + let index_id = b.symbols.alloc(Symbol { + kind: SymbolKind::Index, + ptr: name_ptr, + schema, + }); + + let root = b.root_scope(); + b.scopes[root].insert(index_name, index_id); +} + fn item_name(path: &ast::Path) -> Option { let segment = path.segment()?; diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index e220b1b4..8f491ecd 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -691,4 +691,221 @@ table t$0; ╰╴ ─ 1. source "); } + + #[test] + fn goto_drop_index() { + assert_snapshot!(goto(" +create index idx_name on t(x); +drop index idx_name$0; +"), @r" + ╭▸ + 2 │ create index idx_name on t(x); + │ ──────── 2. destination + 3 │ drop index idx_name; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_index_with_schema() { + assert_snapshot!(goto(r#" +set search_path to public; +create index idx_name on t(x); +drop index public.idx_name$0; +"#), @r" + ╭▸ + 3 │ create index idx_name on t(x); + │ ──────── 2. destination + 4 │ drop index public.idx_name; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_index_defined_after() { + assert_snapshot!(goto(" +drop index idx_name$0; +create index idx_name on t(x); +"), @r" + ╭▸ + 2 │ drop index idx_name; + │ ─ 1. source + 3 │ create index idx_name on t(x); + ╰╴ ──────── 2. destination + "); + } + + #[test] + fn goto_index_definition_returns_self() { + assert_snapshot!(goto(" +create index idx_name$0 on t(x); +"), @r" + ╭▸ + 2 │ create index idx_name on t(x); + │ ┬──────┬ + │ │ │ + │ │ 1. source + ╰╴ 2. destination + "); + } + + #[test] + fn goto_drop_index_with_search_path() { + assert_snapshot!(goto(r#" +create index idx_name on t(x); +set search_path to bar; +create index idx_name on f(x); +set search_path to default; +drop index idx_name$0; +"#), @r" + ╭▸ + 2 │ create index idx_name on t(x); + │ ──────── 2. destination + ‡ + 6 │ drop index idx_name; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_index_multiple() { + assert_snapshot!(goto(" +create index idx1 on t(x); +create index idx2 on t(y); +drop index idx1, idx2$0; +"), @r" + ╭▸ + 3 │ create index idx2 on t(y); + │ ──── 2. destination + 4 │ drop index idx1, idx2; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_table() { + assert_snapshot!(goto(" +create table users(id int); +create index idx_users on users$0(id); +"), @r" + ╭▸ + 2 │ create table users(id int); + │ ───── 2. destination + 3 │ create index idx_users on users(id); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_table_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int); +create index idx_users on public.users$0(id); +"), @r" + ╭▸ + 2 │ create table public.users(id int); + │ ───── 2. destination + 3 │ create index idx_users on public.users(id); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_table_with_search_path() { + assert_snapshot!(goto(r#" +set search_path to foo; +create table foo.users(id int); +create index idx_users on users$0(id); +"#), @r" + ╭▸ + 3 │ create table foo.users(id int); + │ ───── 2. destination + 4 │ create index idx_users on users(id); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_temp_table() { + assert_snapshot!(goto(" +create temp table users(id int); +create index idx_users on users$0(id); +"), @r" + ╭▸ + 2 │ create temp table users(id int); + │ ───── 2. destination + 3 │ create index idx_users on users(id); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_column() { + assert_snapshot!(goto(" +create table users(id int, email text); +create index idx_email on users(email$0); +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ create index idx_email on users(email); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_first_column() { + assert_snapshot!(goto(" +create table users(id int, email text); +create index idx_id on users(id$0); +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ── 2. destination + 3 │ create index idx_id on users(id); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_multiple_columns() { + assert_snapshot!(goto(" +create table users(id int, email text, name text); +create index idx_users on users(id, email$0, name); +"), @r" + ╭▸ + 2 │ create table users(id int, email text, name text); + │ ───── 2. destination + 3 │ create index idx_users on users(id, email, name); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_column_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text); +create index idx_email on public.users(email$0); +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text); + │ ───── 2. destination + 3 │ create index idx_email on public.users(email); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_index_column_temp_table() { + assert_snapshot!(goto(" +create temp table users(id int, email text); +create index idx_email on users(email$0); +"), @r" + ╭▸ + 2 │ create temp table users(id int, email text); + │ ───── 2. destination + 3 │ create index idx_email on users(email); + ╰╴ ─ 1. source + "); + } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 4869afeb..a251a60b 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -6,28 +6,44 @@ use squawk_syntax::{ use crate::binder::Binder; use crate::symbols::{Name, Schema, SymbolKind}; +use squawk_syntax::SyntaxNode; #[derive(Debug)] enum NameRefContext { DropTable, Table, + DropIndex, + CreateIndex, + CreateIndexColumn, } pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Option { let context = classify_name_ref_context(name_ref)?; match context { - NameRefContext::DropTable | NameRefContext::Table => { + NameRefContext::DropTable | NameRefContext::Table | NameRefContext::CreateIndex => { 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) } + NameRefContext::DropIndex => { + let path = find_containing_path(name_ref)?; + let index_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + resolve_index(binder, &index_name, &schema, position) + } + NameRefContext::CreateIndexColumn => { + resolve_create_index_column(binder, name_ref) + } } } fn classify_name_ref_context(name_ref: &ast::NameRef) -> Option { + let mut in_partition_item = false; + for ancestor in name_ref.syntax().ancestors() { if ast::DropTable::can_cast(ancestor.kind()) { return Some(NameRefContext::DropTable); @@ -35,6 +51,18 @@ fn classify_name_ref_context(name_ref: &ast::NameRef) -> Option if ast::Table::can_cast(ancestor.kind()) { return Some(NameRefContext::Table); } + if ast::DropIndex::can_cast(ancestor.kind()) { + return Some(NameRefContext::DropIndex); + } + if ast::PartitionItem::can_cast(ancestor.kind()) { + in_partition_item = true; + } + if ast::CreateIndex::can_cast(ancestor.kind()) { + if in_partition_item { + return Some(NameRefContext::CreateIndexColumn); + } + return Some(NameRefContext::CreateIndex); + } } None @@ -68,6 +96,69 @@ fn resolve_table( None } +fn resolve_index( + binder: &Binder, + index_name: &Name, + schema: &Option, + position: TextSize, +) -> Option { + let symbols = binder.scopes[binder.root_scope()].get(index_name)?; + + if let Some(schema) = schema { + let symbol_id = symbols.iter().copied().find(|id| { + let symbol = &binder.symbols[*id]; + symbol.kind == SymbolKind::Index && &symbol.schema == schema + })?; + return Some(binder.symbols[symbol_id].ptr); + } else { + let search_path = binder.search_path_at(position); + for search_schema in search_path { + if let Some(symbol_id) = symbols.iter().copied().find(|id| { + let symbol = &binder.symbols[*id]; + symbol.kind == SymbolKind::Index && &symbol.schema == search_schema + }) { + return Some(binder.symbols[symbol_id].ptr); + } + } + } + None +} + +fn resolve_create_index_column(binder: &Binder, name_ref: &ast::NameRef) -> Option { + let column_name = Name::new(name_ref.syntax().text().to_string()); + + let create_index = name_ref.syntax().ancestors().find_map(ast::CreateIndex::cast)?; + let relation_name = create_index.relation_name()?; + let path = relation_name.path()?; + + let table_name = extract_table_name(&path)?; + let schema = extract_schema_name(&path); + let position = name_ref.syntax().text_range().start(); + + let table_ptr = resolve_table(binder, &table_name, &schema, position)?; + + let root: &SyntaxNode = &name_ref.syntax().ancestors().last()?; + let table_name_node = table_ptr.to_node(root); + + let create_table = table_name_node + .ancestors() + .find_map(ast::CreateTable::cast)?; + + let table_arg_list = create_table.table_arg_list()?; + + for arg in table_arg_list.args() { + if let ast::TableArg::Column(column) = arg { + if let Some(col_name) = column.name() { + if Name::new(col_name.syntax().text().to_string()) == column_name { + return Some(SyntaxNodePtr::new(col_name.syntax())); + } + } + } + } + + None +} + fn find_containing_path(name_ref: &ast::NameRef) -> Option { for ancestor in name_ref.syntax().ancestors() { if let Some(path) = ast::Path::cast(ancestor) { diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index 4287597b..c0d295c4 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -33,6 +33,7 @@ fn normalize_identifier(text: &str) -> SmolStr { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum SymbolKind { Table, + Index, } #[derive(Clone, Debug)] From d1a258668103c07f7ec46a41918a5500f4c5be3c Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Fri, 26 Dec 2025 14:12:21 -0500 Subject: [PATCH 2/2] cleanup --- crates/squawk_ide/src/resolve.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index a251a60b..f1962da3 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -35,9 +35,7 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti let position = name_ref.syntax().text_range().start(); resolve_index(binder, &index_name, &schema, position) } - NameRefContext::CreateIndexColumn => { - resolve_create_index_column(binder, name_ref) - } + NameRefContext::CreateIndexColumn => resolve_create_index_column(binder, name_ref), } } @@ -127,7 +125,10 @@ fn resolve_index( fn resolve_create_index_column(binder: &Binder, name_ref: &ast::NameRef) -> Option { let column_name = Name::new(name_ref.syntax().text().to_string()); - let create_index = name_ref.syntax().ancestors().find_map(ast::CreateIndex::cast)?; + let create_index = name_ref + .syntax() + .ancestors() + .find_map(ast::CreateIndex::cast)?; let relation_name = create_index.relation_name()?; let path = relation_name.path()?; @@ -147,12 +148,11 @@ fn resolve_create_index_column(binder: &Binder, name_ref: &ast::NameRef) -> Opti let table_arg_list = create_table.table_arg_list()?; for arg in table_arg_list.args() { - if let ast::TableArg::Column(column) = arg { - if let Some(col_name) = column.name() { - if Name::new(col_name.syntax().text().to_string()) == column_name { - return Some(SyntaxNodePtr::new(col_name.syntax())); - } - } + if let ast::TableArg::Column(column) = arg + && let Some(col_name) = column.name() + && Name::new(col_name.syntax().text().to_string()) == column_name + { + return Some(SyntaxNodePtr::new(col_name.syntax())); } }