diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 06be97ed..91de756a 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -1041,4 +1041,232 @@ select myschema.foo$0(); ╰╴ ─ 1. source "); } + + #[test] + fn goto_insert_table() { + assert_snapshot!(goto(" +create table users(id int, email text); +insert into users$0(id, email) values (1, 'test@example.com'); +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ insert into users(id, email) values (1, 'test@example.com'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_table_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text); +insert into public.users$0(id, email) values (1, 'test@example.com'); +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text); + │ ───── 2. destination + 3 │ insert into public.users(id, email) values (1, 'test@example.com'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_column() { + assert_snapshot!(goto(" +create table users(id int, email text); +insert into users(id$0, email) values (1, 'test@example.com'); +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ── 2. destination + 3 │ insert into users(id, email) values (1, 'test@example.com'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_column_second() { + assert_snapshot!(goto(" +create table users(id int, email text); +insert into users(id, email$0) values (1, 'test@example.com'); +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ insert into users(id, email) values (1, 'test@example.com'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_column_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text); +insert into public.users(email$0) values ('test@example.com'); +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text); + │ ───── 2. destination + 3 │ insert into public.users(email) values ('test@example.com'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_table_with_search_path() { + assert_snapshot!(goto(" +set search_path to foo; +create table foo.users(id int, email text); +insert into users$0(id, email) values (1, 'test@example.com'); +"), @r" + ╭▸ + 3 │ create table foo.users(id int, email text); + │ ───── 2. destination + 4 │ insert into users(id, email) values (1, 'test@example.com'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_column_with_search_path() { + assert_snapshot!(goto(" +set search_path to myschema; +create table myschema.users(id int, email text, name text); +insert into users(email$0, name) values ('test@example.com', 'Test'); +"), @r" + ╭▸ + 3 │ create table myschema.users(id int, email text, name text); + │ ───── 2. destination + 4 │ insert into users(email, name) values ('test@example.com', 'Test'); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_table() { + assert_snapshot!(goto(" +create table users(id int, email text); +delete from users$0 where id = 1; +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ delete from users where id = 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_table_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text); +delete from public.users$0 where id = 1; +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text); + │ ───── 2. destination + 3 │ delete from public.users where id = 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_table_with_search_path() { + assert_snapshot!(goto(" +set search_path to foo; +create table foo.users(id int, email text); +delete from users$0 where id = 1; +"), @r" + ╭▸ + 3 │ create table foo.users(id int, email text); + │ ───── 2. destination + 4 │ delete from users where id = 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_temp_table() { + assert_snapshot!(goto(" +create temp table users(id int, email text); +delete from users$0 where id = 1; +"), @r" + ╭▸ + 2 │ create temp table users(id int, email text); + │ ───── 2. destination + 3 │ delete from users where id = 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_column() { + assert_snapshot!(goto(" +create table users(id int, email text); +delete from users where id$0 = 1; +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ── 2. destination + 3 │ delete from users where id = 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_column_second() { + assert_snapshot!(goto(" +create table users(id int, email text); +delete from users where email$0 = 'test@example.com'; +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ delete from users where email = 'test@example.com'; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_column_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text, name text); +delete from public.users where name$0 = 'Test'; +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text, name text); + │ ──── 2. destination + 3 │ delete from public.users where name = 'Test'; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_column_with_search_path() { + assert_snapshot!(goto(" +set search_path to myschema; +create table myschema.users(id int, email text, active boolean); +delete from users where active$0 = true; +"), @r" + ╭▸ + 3 │ create table myschema.users(id int, email text, active boolean); + │ ────── 2. destination + 4 │ delete from users where active = true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_multiple_columns() { + assert_snapshot!(goto(" +create table users(id int, email text, active boolean); +delete from users where id$0 = 1 and active = true; +"), @r" + ╭▸ + 2 │ create table users(id int, email text, active boolean); + │ ── 2. destination + 3 │ delete from users where id = 1 and active = true; + ╰╴ ─ 1. source + "); + } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index a9ff3145..058574a4 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -7,7 +7,6 @@ use squawk_syntax::{ use crate::binder::Binder; pub(crate) use crate::symbols::Schema; use crate::symbols::{Name, SymbolKind}; -use squawk_syntax::SyntaxNode; #[derive(Debug)] enum NameRefContext { @@ -18,13 +17,21 @@ enum NameRefContext { CreateIndex, CreateIndexColumn, SelectFunctionCall, + InsertTable, + InsertColumn, + DeleteTable, + DeleteWhereColumn, } 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::CreateIndex => { + NameRefContext::DropTable + | NameRefContext::Table + | NameRefContext::CreateIndex + | NameRefContext::InsertTable + | NameRefContext::DeleteTable => { let path = find_containing_path(name_ref)?; let table_name = extract_table_name(&path)?; let schema = extract_schema_name(&path); @@ -61,12 +68,16 @@ pub(crate) fn resolve_name_ref(binder: &Binder, name_ref: &ast::NameRef) -> Opti resolve_function(binder, &function_name, &schema, position) } NameRefContext::CreateIndexColumn => resolve_create_index_column(binder, name_ref), + NameRefContext::InsertColumn => resolve_insert_column(binder, name_ref), + NameRefContext::DeleteWhereColumn => resolve_delete_where_column(binder, name_ref), } } fn classify_name_ref_context(name_ref: &ast::NameRef) -> Option { let mut in_partition_item = false; let mut in_call_expr = false; + let mut in_column_list = false; + let mut in_where_clause = false; for ancestor in name_ref.syntax().ancestors() { if ast::DropTable::can_cast(ancestor.kind()) { @@ -96,6 +107,24 @@ fn classify_name_ref_context(name_ref: &ast::NameRef) -> Option if ast::Select::can_cast(ancestor.kind()) && in_call_expr { return Some(NameRefContext::SelectFunctionCall); } + if ast::ColumnList::can_cast(ancestor.kind()) { + in_column_list = true; + } + if ast::Insert::can_cast(ancestor.kind()) { + if in_column_list { + return Some(NameRefContext::InsertColumn); + } + return Some(NameRefContext::InsertTable); + } + if ast::WhereClause::can_cast(ancestor.kind()) { + in_where_clause = true; + } + if ast::Delete::can_cast(ancestor.kind()) { + if in_where_clause { + return Some(NameRefContext::DeleteWhereColumn); + } + return Some(NameRefContext::DeleteTable); + } } None @@ -179,7 +208,74 @@ fn resolve_create_index_column(binder: &Binder, name_ref: &ast::NameRef) -> Opti let table_ptr = resolve_table(binder, &table_name, &schema, position)?; - let root: &SyntaxNode = &name_ref.syntax().ancestors().last()?; + let root = &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 + && let Some(col_name) = column.name() + && Name::new(col_name.syntax().text().to_string()) == column_name + { + return Some(SyntaxNodePtr::new(col_name.syntax())); + } + } + + None +} + +fn resolve_insert_column(binder: &Binder, name_ref: &ast::NameRef) -> Option { + let column_name = Name::new(name_ref.syntax().text().to_string()); + + let insert = name_ref.syntax().ancestors().find_map(ast::Insert::cast)?; + let path = insert.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 = &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 + && let Some(col_name) = column.name() + && Name::new(col_name.syntax().text().to_string()) == column_name + { + return Some(SyntaxNodePtr::new(col_name.syntax())); + } + } + + None +} + +fn resolve_delete_where_column(binder: &Binder, name_ref: &ast::NameRef) -> Option { + let column_name = Name::new(name_ref.syntax().text().to_string()); + + let delete = name_ref.syntax().ancestors().find_map(ast::Delete::cast)?; + let relation_name = delete.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 = &name_ref.syntax().ancestors().last()?; let table_name_node = table_ptr.to_node(root); let create_table = table_name_node diff --git a/crates/squawk_syntax/src/lib.rs b/crates/squawk_syntax/src/lib.rs index 935eafe8..0a028aed 100644 --- a/crates/squawk_syntax/src/lib.rs +++ b/crates/squawk_syntax/src/lib.rs @@ -259,7 +259,7 @@ fn api_walkthrough() { // Besides the "typed" AST API, there's an untyped CST one as well. // To switch from AST to CST, call `.syntax()` method: - let func_option_syntax: &SyntaxNode = func_option.syntax(); + let func_option_syntax = func_option.syntax(); // Note how `expr` and `bin_expr` are in fact the same node underneath: assert!(func_option_syntax == option.syntax());