Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions crates/squawk_ide/src/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
");
}
}
102 changes: 99 additions & 3 deletions crates/squawk_ide/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<SyntaxNodePtr> {
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);
Expand Down Expand Up @@ -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<NameRefContext> {
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()) {
Expand Down Expand Up @@ -96,6 +107,24 @@ fn classify_name_ref_context(name_ref: &ast::NameRef) -> Option<NameRefContext>
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
Expand Down Expand Up @@ -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<SyntaxNodePtr> {
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<SyntaxNodePtr> {
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
Expand Down
2 changes: 1 addition & 1 deletion crates/squawk_syntax/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading