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
28 changes: 11 additions & 17 deletions crates/squawk_ide/src/ast_nav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,18 @@ pub(crate) fn find_cte_with_table(
.ancestors()
.find_map(|query| ast::WithQuery::cast(query)?.with_clause())?;

let ref_start = name_ref.syntax().text_range().start();
let is_recursive = with_clause.recursive_token().is_some();
for with_table in with_clause.with_tables() {
if let Some(name) = with_table.name()
&& Name::from_node(&name) == *cte_name
{
// Skip if we're inside this CTE's definition (CTE doesn't shadow itself)
if !is_recursive
&& with_table
.syntax()
.text_range()
.contains_range(name_ref.syntax().text_range())
{
continue;
}
return Some(with_table);
}
}
None

with_clause
.with_tables()
// Without RECURSIVE, only CTEs before the reference are visible.
.filter(|with_table| is_recursive || with_table.syntax().text_range().end() <= ref_start)
.find(|with_table| {
with_table
.name()
.is_some_and(|name| Name::from_node(&name) == *cte_name)
})
}

pub(crate) fn iter_values_columns(values: &ast::Values) -> impl Iterator<Item = (Name, ast::Expr)> {
Expand Down
126 changes: 51 additions & 75 deletions crates/squawk_ide/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,45 +280,7 @@ fn select_columns_with_types(
target_list_columns_with_types_in_file(db, file, &target_list, from_clause.as_ref())
}
ast::SelectVariant::Values(values) => columns_from_values(values),
ast::SelectVariant::Table(table) => {
let Some(path) = table.relation_name().and_then(|r| r.path()) else {
return vec![];
};
let Some((schema, table_name)) = name::schema_and_name_path(&path) else {
return vec![];
};
let name_ref = path.segment().and_then(|s| s.name_ref());
let position = table.syntax().text_range().start();
let schemas = bind(db, file).resolved_schemas(position, schema.as_ref());
// Try CTE resolution first since resolve_table_name doesn't handle CTEs
if let Some((ptr, kind)) =
resolve_table_like(db, name_ref.as_ref(), &table_name, &schemas, file)
{
let tree = parse(db, file).tree();
let node = ptr.to_node(tree.syntax());
match kind {
LocationKind::Table => {
if let Some(with_table) = node.ancestors().find_map(ast::WithTable::cast) {
return with_table_columns_with_types(db, file, with_table);
}
if let Some(t) = node.ancestors().find_map(ast::CreateTableLike::cast) {
return table_columns(db, file, &t);
}
}
LocationKind::View => {
if let Some(v) = node.ancestors().find_map(ast::CreateViewLike::cast) {
return view_like_columns_with_types(db, file, &v);
}
}
_ => (),
}
}
// Fall back to builtins for schema-qualified names
if let Some(resolved) = resolve_table_name(db, &table_name, &schemas, file) {
return resolved_to_columns_with_types(db, resolved.file_id, resolved.value, 0);
}
vec![]
}
ast::SelectVariant::Table(table) => table_query_columns_with_types(db, file, table),
ast::SelectVariant::SelectInto(select_into) => {
let Some(target_list) = select_into.select_clause().and_then(|c| c.target_list())
else {
Expand All @@ -336,6 +298,51 @@ fn select_columns_with_types(
}
}

fn table_query_columns_with_types(
db: &dyn Db,
file: File,
table: &ast::Table,
) -> Vec<(Name, Option<Type>)> {
let Some(path) = table.relation_name().and_then(|r| r.path()) else {
return vec![];
};
let Some((schema, table_name)) = name::schema_and_name_path(&path) else {
return vec![];
};
let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) else {
return vec![];
};
let position = name_ref.syntax().text_range().start();
let schemas = bind(db, file).resolved_schemas(position, schema.as_ref());
// Try CTE resolution first since resolve_table_name doesn't handle CTEs
if let Some((ptr, kind)) = resolve_table_like(db, Some(&name_ref), &table_name, &schemas, file)
{
let tree = parse(db, file).tree();
let node = ptr.to_node(tree.syntax());
match kind {
LocationKind::Table => {
if let Some(with_table) = node.ancestors().find_map(ast::WithTable::cast) {
return with_table_columns_with_types(db, file, with_table);
}
if let Some(t) = node.ancestors().find_map(ast::CreateTableLike::cast) {
return table_columns(db, file, &t);
}
}
LocationKind::View => {
if let Some(v) = node.ancestors().find_map(ast::CreateViewLike::cast) {
return view_like_columns_with_types(db, file, &v);
}
}
_ => (),
}
}
// Fall back to builtins for schema-qualified names
if let Some(resolved) = resolve_table_name(db, &table_name, &schemas, file) {
return resolved_to_columns_with_types(db, resolved.file_id, resolved.value, 0);
}
vec![]
}

fn columns_from_returning_clause_with_types(
db: &dyn Db,
file: File,
Expand Down Expand Up @@ -497,6 +504,10 @@ fn with_table_query_columns_with_types(
return columns_from_values(&values);
}

if let ast::WithQuery::Table(table) = query {
return table_query_columns_with_types(db, file, &table);
}

if let Some(columns) = columns_from_returning_clause_with_types(db, file, &query) {
return columns;
}
Expand Down Expand Up @@ -734,8 +745,6 @@ fn select_variant_columns_with_types(
file: File,
select_variant: &ast::SelectVariant,
) -> Vec<(Name, Option<Type>)> {
let tree = parse(db, file).tree();
let root = tree.syntax();
match select_variant {
ast::SelectVariant::Values(values) => columns_from_values(values),
ast::SelectVariant::Select(select) => {
Expand Down Expand Up @@ -767,40 +776,7 @@ fn select_variant_columns_with_types(
};
select_variant_columns_with_types(db, file, &lhs)
}
ast::SelectVariant::Table(table) => {
let Some(path) = table.relation_name().and_then(|r| r.path()) else {
return vec![];
};
let Some((schema, table_name)) = name::schema_and_name_path(&path) else {
return vec![];
};
let name_ref = path.segment().and_then(|segment| segment.name_ref());
let position = table.syntax().text_range().start();
let schemas = bind(db, file).resolved_schemas(position, schema.as_ref());
let Some((ptr, kind)) =
resolve_table_like(db, name_ref.as_ref(), &table_name, &schemas, file)
else {
return vec![];
};
let node = ptr.to_node(root);
match kind {
LocationKind::View => node
.ancestors()
.find_map(ast::CreateViewLike::cast)
.map(|v| view_like_columns_with_types(db, file, &v))
.unwrap_or_default(),
LocationKind::Table => {
if let Some(with_table) = node.ancestors().find_map(ast::WithTable::cast) {
return with_table_columns_with_types(db, file, with_table);
}
node.ancestors()
.find_map(ast::CreateTableLike::cast)
.map(|t| table_columns(db, file, &t))
.unwrap_or_default()
}
_ => vec![],
}
}
ast::SelectVariant::Table(table) => table_query_columns_with_types(db, file, table),
}
}

Expand Down
146 changes: 146 additions & 0 deletions crates/squawk_ide/src/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,112 @@ from
);
}

#[test]
fn goto_cte_forward_ref_not_found() {
// b is defined after a, so a can't reference it in a non-recursive WITH
// ERROR: relation "b" does not exist
goto_not_found(
"
with
a as (select * from b$0),
b as (select 1 x)
select * from a;
",
);
}

#[test]
fn goto_cte_forward_ref_ignored() {
assert_snapshot!(goto("
create table b(c int);
with
a as (select c$0 from b),
b as (select 1 c)
select c from a;
"), @"
╭▸
2 │ create table b(c int);
│ ─ 2. destination
3 │ with
4 │ a as (select c from b),
╰╴ ─ 1. source
");
}

#[test]
fn goto_cte_forward_ref_ignored_inside_table_query() {
assert_snapshot!(goto("
create table b(c int);
with
a as (table b),
b as (select 1 c)
select c$0 from a;
"), @"
╭▸
2 │ create table b(c int);
│ ─ 2. destination
6 │ select c from a;
╰╴ ─ 1. source
");
}

#[test]
fn goto_cte_forward_ref_ignored_inside_create_table_as_star() {
assert_snapshot!(goto("
create table b(c int);
create table ct as
with
a as (select * from b),
b as (select 1 c)
select * from a;
select c$0 from ct;
"), @"
╭▸
2 │ create table b(c int);
│ ─ 2. destination
8 │ select c from ct;
╰╴ ─ 1. source
");
}

#[test]
fn goto_cte_forward_ref_ignored_inside_create_table_as_table() {
assert_snapshot!(goto("
create table b(c int);
create table made as
with
a as (table b),
b as (select 1 c)
table a;
select c$0 from made;
"), @"
╭▸
2 │ create table b(c int);
│ ─ 2. destination
8 │ select c from made;
╰╴ ─ 1. source
");
}

#[test]
fn goto_cte_star_over_subquery_from_item() {
assert_snapshot!(goto("
create table t(c int);
with
a as (select * from (select c from t))
select c$0 from a;
"), @"
╭▸
4 │ a as (select * from (select c from t))
│ ─ 2. destination
5 │ select c from a;
╰╴ ─ 1. source
");
}

#[test]
fn goto_drop_sequence() {
assert_snapshot!(goto("
Expand Down Expand Up @@ -4930,6 +5036,36 @@ select a from x$0;
");
}

#[test]
fn goto_cte_shadows_table_in_from() {
assert_snapshot!(goto("
create table x(a int);
with x as (select 1 a)
select a from x$0;
"), @"
╭▸
3 │ with x as (select 1 a)
│ ─ 2. destination
4 │ select a from x;
╰╴ ─ 1. source
");
}

#[test]
fn goto_cte_shadows_view_column() {
assert_snapshot!(goto("
create view x as select 1 a;
with x as (select 2 a)
select a$0 from x;
"), @"
╭▸
3 │ with x as (select 2 a)
│ ─ 2. destination
4 │ select a from x;
╰╴ ─ 1. source
");
}

#[test]
fn goto_cte_column() {
assert_snapshot!(goto("
Expand Down Expand Up @@ -7195,6 +7331,16 @@ select m.message$0 from users as u join messages as m on u.id = m.user_id;
");
}

#[test]
fn goto_alias_hides_table_name() {
goto_not_found(
"
create table t(a int);
select t$0.a from t as u;
",
);
}

#[test]
fn goto_join_unqualified_column() {
assert_snapshot!(goto("
Expand Down
Loading
Loading