diff --git a/crates/squawk_ide/src/ast_nav.rs b/crates/squawk_ide/src/ast_nav.rs index f37f75a7..9d41da22 100644 --- a/crates/squawk_ide/src/ast_nav.rs +++ b/crates/squawk_ide/src/ast_nav.rs @@ -2,11 +2,12 @@ /// /// There shouldn't be any dependency on Salsa. use squawk_syntax::{ - SyntaxNode, SyntaxToken, + SyntaxKind, SyntaxNode, SyntaxToken, ast::{self, AstNode}, }; use std::iter; +use crate::name; use crate::symbols::Name; pub(crate) fn find_cte_with_table( @@ -319,19 +320,119 @@ pub(crate) fn unwrap_paren_expr(expr: ast::Expr) -> impl Iterator Option { - match merge.using_on_clause()?.from_list_item()? { - ast::FromListItem::FromItem(from_item) => Some(from_item), - ast::FromListItem::JoinExpr(_) => None, - } -} - pub(crate) fn iter_from_clause( from_clause: &ast::FromClause, ) -> impl Iterator { iter_from_items(from_clause.items()) } +// A FROM item can't see its siblings, unless it's lateral, then it can see +// the items before it. An `on` clause can only see the items in its own join. +pub(crate) fn visible_from_items( + from_items: impl Iterator, + node: &SyntaxNode, +) -> Vec { + let mut from_items = from_items.peekable(); + if let Some(from_item) = from_items.peek() + && let Some(join_expr) = enclosing_on_clause_join_expr(node, from_item) + { + return iter_join_expr(&join_expr).collect(); + } + let mut visible = vec![]; + collect_visible_from_items(from_items, node, &mut visible); + visible +} + +fn enclosing_on_clause_join_expr( + node: &SyntaxNode, + from_item: &ast::FromItem, +) -> Option { + let from_list = enclosing_from_list(from_item.syntax())?; + node.ancestors() + .filter_map(ast::OnClause::cast) + .filter_map(|on_clause| { + let join = on_clause.syntax().parent().and_then(ast::Join::cast)?; + join.syntax().parent().and_then(ast::JoinExpr::cast) + }) + .find(|join_expr| enclosing_from_list(join_expr.syntax()).as_ref() == Some(&from_list)) +} + +fn enclosing_from_list(node: &SyntaxNode) -> Option { + node.ancestors().find(|ancestor| { + ast::FromClause::can_cast(ancestor.kind()) + || ast::UsingClause::can_cast(ancestor.kind()) + || ast::UsingOnClause::can_cast(ancestor.kind()) + }) +} + +fn collect_visible_from_items( + from_items: impl Iterator, + node: &SyntaxNode, + visible: &mut Vec, +) { + for from_item in from_items { + if !from_item + .syntax() + .text_range() + .contains_range(node.text_range()) + { + visible.push(from_item); + continue; + } + hide_disallowed_lateral_items(&from_item, visible); + if let ast::FromItem::ParenFromItem(paren) = &from_item + && let Some(paren_expr) = paren.paren_expr() + { + collect_visible_from_items( + iter_from_items(paren_expr.from_list_item().into_iter()), + node, + visible, + ); + } else if !is_lateral_from_item(&from_item) { + visible.clear(); + } + return; + } +} + +fn hide_disallowed_lateral_items(from_item: &ast::FromItem, visible: &mut Vec) { + let Some(join) = from_item.syntax().parent().and_then(ast::Join::cast) else { + return; + }; + if !matches!( + join.join_type(), + Some(ast::JoinType::JoinRight(_) | ast::JoinType::JoinFull(_)) + ) { + return; + } + let Some(lhs) = join + .syntax() + .parent() + .and_then(ast::JoinExpr::cast) + .and_then(|join_expr| join_expr.from_list_item()) + else { + return; + }; + let hidden: Vec<_> = iter_from_items(std::iter::once(lhs)).collect(); + visible.retain(|from_item| !hidden.contains(from_item)); +} + +fn is_lateral_from_item(from_item: &ast::FromItem) -> bool { + match from_item { + ast::FromItem::ExprFromItem(_) + | ast::FromItem::FunctionFromItem(_) + | ast::FromItem::JsonTableFromItem(_) + | ast::FromItem::RowsFromItem(_) + | ast::FromItem::XmlTableFromItem(_) => true, + ast::FromItem::GraphTableFromItem(_) + | ast::FromItem::ParenFromItem(_) + | ast::FromItem::RelationFromItem(_) => from_item + .syntax() + .children_with_tokens() + .any(|it| it.kind() == SyntaxKind::LATERAL_KW), + } +} + pub(crate) fn iter_join_expr(join_expr: &ast::JoinExpr) -> impl Iterator { iter_from_items(std::iter::once(ast::FromListItem::JoinExpr( join_expr.clone(), @@ -371,3 +472,26 @@ impl Iterator for FromItemIter { None } } + +pub(crate) enum RoutineKind { + Function, + Procedure, +} + +pub(crate) fn enclosing_routine_name( + node: &SyntaxNode, +) -> Option<(Name, ast::PathSegment, RoutineKind)> { + node.ancestors().find_map(|ancestor| { + let (path, kind) = + if let Some(create_function) = ast::CreateFunction::cast(ancestor.clone()) { + (create_function.name()?.path()?, RoutineKind::Function) + } else { + ( + ast::CreateProcedure::cast(ancestor)?.name()?.path()?, + RoutineKind::Procedure, + ) + }; + let (_, routine_name) = name::schema_and_name_definition(&path)?; + Some((routine_name, path.segment()?, kind)) + }) +} diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index 100d79fc..7cb17c6a 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -1,4 +1,4 @@ -use crate::{location::LocationKind, name, symbols::Name}; +use crate::{ast_nav, location::LocationKind, symbols::Name}; use squawk_syntax::{ SyntaxKind, SyntaxNode, ast::{self, AstNode, LitKind}, @@ -508,6 +508,7 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { let mut in_returning_clause = false; let mut in_set_clause = false; let mut in_set_expr = false; + let mut in_using_clause = false; let mut in_where_clause = false; let mut in_when_clause = false; let mut in_when_condition = false; @@ -515,6 +516,11 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { if ast::ArgList::can_cast(ancestor.kind()) { in_arg_list = true; } + if ast::UsingClause::can_cast(ancestor.kind()) + || ast::UsingOnClause::can_cast(ancestor.kind()) + { + in_using_clause = true; + } if ast::WhenCondition::can_cast(ancestor.kind()) { in_when_condition = true; } @@ -575,7 +581,11 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { } } if ast::Merge::can_cast(ancestor.kind()) { - if in_returning_clause || in_on_clause || in_when_clause { + if in_returning_clause + || in_on_clause + || in_when_clause + || (in_using_clause && in_arg_list) + { if is_function_call || is_schema_table_col { return Some(NameRefClass::Schema); } else { @@ -602,6 +612,17 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { return Some(NameRefClass::PolicyQualifiedColumnTable); } } + if ast::ReturnStmt::can_cast(ancestor.kind()) + && ast_nav::enclosing_routine_name(&ancestor).is_some_and(|(routine_name, _, _)| { + Name::from_node(&base_name_ref) == routine_name + }) + { + if is_function_call || is_schema_table_col { + return Some(NameRefClass::Schema); + } else { + return Some(NameRefClass::SelectQualifiedColumnTable); + } + } } return Some(NameRefClass::Schema); } @@ -716,8 +737,8 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { } if ast::ReturnStmt::can_cast(ancestor.kind()) { if let Some(ast::Expr::NameRef(base)) = field_expr.base() - && enclosing_routine_name(&ancestor) - .is_some_and(|routine_name| Name::from_node(&base) == routine_name) + && ast_nav::enclosing_routine_name(&ancestor) + .is_some_and(|(routine_name, _, _)| Name::from_node(&base) == routine_name) { return Some(NameRefClass::SelectColumn); } @@ -1065,7 +1086,10 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { in_returning_clause = true; } if ast::Delete::can_cast(ancestor.kind()) { - if in_returning_clause || in_where_clause { + if in_returning_clause + || in_where_clause + || (in_using_clause && (in_on_clause || in_arg_list)) + { return Some(NameRefClass::DeleteColumn); } if in_using_clause { @@ -1074,7 +1098,11 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { break; } if ast::Update::can_cast(ancestor.kind()) { - if in_returning_clause || in_where_clause || in_set_clause { + if in_returning_clause + || in_where_clause + || in_set_clause + || (in_from_clause && (in_on_clause || in_arg_list)) + { return Some(NameRefClass::UpdateColumn); } if in_from_clause { @@ -1086,7 +1114,11 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { in_when_clause = true; } if ast::Merge::can_cast(ancestor.kind()) { - if in_when_clause || in_returning_clause || in_on_clause { + if in_when_clause + || in_returning_clause + || in_on_clause + || (in_using_clause && in_arg_list) + { return Some(NameRefClass::MergeColumn); } if in_using_clause { @@ -1103,22 +1135,6 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { has_table_name_ref.then_some(NameRefClass::Table) } -fn enclosing_routine_name(node: &SyntaxNode) -> Option { - for ancestor in node.ancestors() { - if let Some(create_function) = ast::CreateFunction::cast(ancestor.clone()) { - let (_, routine_name) = - name::schema_and_name_definition(&create_function.name()?.path()?)?; - return Some(routine_name); - } - if let Some(create_procedure) = ast::CreateProcedure::cast(ancestor) { - let (_, routine_name) = - name::schema_and_name_definition(&create_procedure.name()?.path()?)?; - return Some(routine_name); - } - } - None -} - fn is_grouping_or_distinct_el(node: &SyntaxNode) -> bool { node.ancestors() .skip(1) diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 1e24c452..7a661607 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -1940,6 +1940,33 @@ create function f(x int) returns int language sql return f.x$0 + 1; "); } + #[test] + fn goto_function_name_qualifier_in_sql_body_return_expr() { + assert_snapshot!(goto(" +create function f(x int) returns int language sql return f$0.x + 1; +"), @" + ╭▸ + 2 │ create function f(x int) returns int language sql return f.x + 1; + ╰╴ ─ 2. destination ─ 1. source + "); + } + + #[test] + fn goto_schema_matching_function_name_in_sql_body_return_expr() { + assert_snapshot!(goto(" +create schema f; +create function f.g() returns int language sql return 1; +create function f() returns int language sql return f$0.g(); +"), @" + ╭▸ + 2 │ create schema f; + │ ─ 2. destination + 3 │ create function f.g() returns int language sql return 1; + 4 │ create function f() returns int language sql return f.g(); + ╰╴ ─ 1. source + "); + } + #[test] fn goto_function_param_bogus_qualified_in_sql_body_return_expr() { goto_not_found( @@ -9771,250 +9798,3171 @@ delete from target using src s where s.y$0 = target.id; } #[test] - fn goto_select_from_table() { + fn goto_update_qualified_target_column_shadowed_by_from() { assert_snapshot!(goto(" -create table users(id int, email text); -select * from users$0; -"), @r" +create table t(a int, b int); +create table u(b int); +update t set a = 1 from u where t.b$0 = u.b; +"), @" ╭▸ - 2 │ create table users(id int, email text); - │ ───── 2. destination - 3 │ select * from users; - ╰╴ ─ 1. source + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = 1 from u where t.b = u.b; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_from_table_with_schema() { + fn goto_delete_qualified_target_column_shadowed_by_using() { assert_snapshot!(goto(" -create table public.users(id int, email text); -select * from public.users$0; -"), @r" +create table t(a int, b int); +create table u(b int); +delete from t using u where t.b$0 = u.b; +"), @" ╭▸ - 2 │ create table public.users(id int, email text); - │ ───── 2. destination - 3 │ select * from public.users; - ╰╴ ─ 1. source + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ delete from t using u where t.b = u.b; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_from_table_with_search_path() { + fn goto_update_qualified_source_column_in_set_expr() { assert_snapshot!(goto(" -set search_path to foo; -create table foo.users(id int, email text); -select * from users$0; -"), @r" +create table t(a int, b int); +create table u(b int); +update t set a = u.b$0 from u where t.b = u.b; +"), @" ╭▸ - 3 │ create table foo.users(id int, email text); - │ ───── 2. destination - 4 │ select * from users; - ╰╴ ─ 1. source + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ update t set a = u.b from u where t.b = u.b; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_from_temp_table() { + fn goto_update_qualified_column_in_paren_join_source() { assert_snapshot!(goto(" -create temp table users(id int, email text); -select * from users$0; -"), @r" +create table t(a int, b int); +create table u(b int); +create table w(b int); +update t set a = 1 from (u join w on true) where w.b$0 = t.b; +"), @" ╭▸ - 2 │ create temp table users(id int, email text); - │ ───── 2. destination - 3 │ select * from users; - ╰╴ ─ 1. source + 4 │ create table w(b int); + │ ─ 2. destination + 5 │ update t set a = 1 from (u join w on true) where w.b = t.b; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_from_table_defined_after() { + fn goto_delete_qualified_column_in_subquery_source() { assert_snapshot!(goto(" -select * from users$0; -create table users(id int, email text); -"), @r" +create table t(a int, b int); +create table u(b int); +delete from t using u, (select 1 as b) s where s.b$0 = t.b; +"), @" ╭▸ - 2 │ select * from users; - │ ─ 1. source - 3 │ create table users(id int, email text); - ╰╴ ───── 2. destination + 4 │ delete from t using u, (select 1 as b) s where s.b = t.b; + │ ┬ ─ 1. source + │ │ + ╰╴ 2. destination "); } #[test] - fn goto_select_column() { + fn goto_merge_qualified_target_column_shadowed_by_using() { assert_snapshot!(goto(" -create table users(id int, email text); -select id$0 from users; -"), @r" +create table t(a int, b int); +create table u(b int); +merge into t using u on t.b$0 = u.b when matched then delete; +"), @" ╭▸ - 2 │ create table users(id int, email text); - │ ── 2. destination - 3 │ select id from users; - ╰╴ ─ 1. source + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ merge into t using u on t.b = u.b when matched then delete; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_column_second() { + fn goto_update_aliased_qualified_target_column_shadowed_by_from() { assert_snapshot!(goto(" -create table users(id int, email text); -select id, email$0 from users; -"), @r" +create table t(a int, b int); +create table u(b int); +update t as x set a = 1 from u where x.b$0 = u.b; +"), @" ╭▸ - 2 │ create table users(id int, email text); - │ ───── 2. destination - 3 │ select id, email from users; - ╰╴ ─ 1. source + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t as x set a = 1 from u where x.b = u.b; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_column_with_schema() { + fn goto_select_qualified_column_shadowed_by_other_table() { assert_snapshot!(goto(" -create table public.users(id int, email text); -select email$0 from public.users; -"), @r" +create table t(a int, b int); +create table u(b int); +select * from t, u where t.b$0 = u.b; +"), @" ╭▸ - 2 │ create table public.users(id int, email text); - │ ───── 2. destination - 3 │ select email from public.users; - ╰╴ ─ 1. source + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ select * from t, u where t.b = u.b; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_column_with_search_path() { + fn goto_update_from_ambiguous_column_with_target() { assert_snapshot!(goto(" -set search_path to foo; -create table foo.users(id int, email text); -select id$0 from users; -"), @r" +create table t(a int); +create table u(a int); +update t set a = 1 from u where a$0 = 1;" + ), @" ╭▸ - 3 │ create table foo.users(id int, email text); - │ ── 2. destination - 4 │ select id from users; - ╰╴ ─ 1. source + 2 │ create table t(a int); + │ ─ 2. destination + 3 │ create table u(a int); + │ ─ 3. destination + 4 │ update t set a = 1 from u where a = 1; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_table_as_column() { + fn goto_update_from_ambiguous_column_between_sources() { assert_snapshot!(goto(" -create table t(x bigint, y bigint); -select t$0 from t; -"), @r" +create table t(a int); +create table u(x int); +create table w(x int); +update t set a = x$0 from u, w;" + ), @" ╭▸ - 2 │ create table t(x bigint, y bigint); - │ ─ 2. destination - 3 │ select t from t; - ╰╴ ─ 1. source + 3 │ create table u(x int); + │ ─ 2. destination + 4 │ create table w(x int); + │ ─ 3. destination + 5 │ update t set a = x from u, w; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_table_star_expansion() { + fn goto_delete_using_ambiguous_column_with_target() { assert_snapshot!(goto(" -create table t(id int, a int); -select t$0.* from t; -"), @r" +create table t(a int); +create table u(a int); +delete from t using u where a$0 = 1;" + ), @" ╭▸ - 2 │ create table t(id int, a int); - │ ─ 2. destination - 3 │ select t.* from t; - ╰╴ ─ 1. source + 2 │ create table t(a int); + │ ─ 2. destination + 3 │ create table u(a int); + │ ─ 3. destination + 4 │ delete from t using u where a = 1; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_table_as_column_with_schema() { + fn goto_merge_on_ambiguous_column_with_target() { assert_snapshot!(goto(" -create table public.t(x bigint, y bigint); -select t$0 from public.t; -"), @r" +create table t(a int); +create table u(a int); +merge into t using u on a$0 = 1 when matched then do nothing;" + ), @" ╭▸ - 2 │ create table public.t(x bigint, y bigint); - │ ─ 2. destination - 3 │ select t from public.t; - ╰╴ ─ 1. source + 2 │ create table t(a int); + │ ─ 2. destination + 3 │ create table u(a int); + │ ─ 3. destination + 4 │ merge into t using u on a = 1 when matched then do nothing; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_table_as_column_with_search_path() { + fn goto_merge_not_matched_by_source_column_not_ambiguous() { assert_snapshot!(goto(" -set search_path to foo; -create table foo.users(id int, email text); -select users$0 from users; -"), @r" +create table t(a int); +create table u(a int); +merge into t using u on t.a = u.a + when not matched by source then update set a = a$0 + 1;" + ), @" ╭▸ - 3 │ create table foo.users(id int, email text); - │ ───── 2. destination - 4 │ select users from users; - ╰╴ ─ 1. source + 2 │ create table t(a int); + │ ─ 2. destination + ‡ + 5 │ when not matched by source then update set a = a + 1; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_column_with_same_name_as_table() { + fn goto_update_from_lateral_column_not_ambiguous_with_target() { assert_snapshot!(goto(" -create table t(t int); -select t$0 from t; -"), @r" +create table t(a int); +create table u(a int); +update t set a = 1 from u, lateral (select a$0) v;" + ), @" ╭▸ - 2 │ create table t(t int); + 3 │ create table u(a int); │ ─ 2. destination - 3 │ select t from t; - ╰╴ ─ 1. source + 4 │ update t set a = 1 from u, lateral (select a) v; + ╰╴ ─ 1. source "); } #[test] - fn goto_select_view_name_from_view() { + fn goto_update_from_join_on_unqualified_column() { assert_snapshot!(goto(" -create view boop as select 1 a; -select boop$0 from boop; +create table t(a int); +create table w(y int); +create table z(q int); +update t set a = 1 from w join z on y$0 = 1; "), @" ╭▸ - 2 │ create view boop as select 1 a; - │ ──── 2. destination - 3 │ select boop from boop; - ╰╴ ─ 1. source + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ update t set a = 1 from w join z on y = 1; + ╰╴ ─ 1. source "); } #[test] - fn goto_drop_schema() { + fn goto_update_from_join_on_qualified_column() { assert_snapshot!(goto(" -create schema foo; -drop schema foo$0; -"), @r" +create table t(a int); +create table w(y int); +create table z(q int); +update t set a = 1 from w join z on w.y$0 = z.q; +"), @" ╭▸ - 2 │ create schema foo; - │ ─── 2. destination - 3 │ drop schema foo; - ╰╴ ─ 1. source + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ update t set a = 1 from w join z on w.y = z.q; + ╰╴ ─ 1. source "); } #[test] - fn goto_create_schema_authorization() { + fn goto_update_from_join_on_qualifier() { assert_snapshot!(goto(" -create schema authorization foo$0; -"), @r" +create table t(a int); +create table w(y int); +create table z(q int); +update t set a = 1 from w join z on w$0.y = z.q; +"), @" ╭▸ - 2 │ create schema authorization foo; - │ ┬─┬ - │ │ │ - │ │ 1. source - ╰╴ 2. destination + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ update t set a = 1 from w join z on w.y = z.q; + ╰╴ ─ 1. source "); } #[test] - fn goto_drop_schema_authorization() { + fn goto_update_from_lateral_function_arg() { assert_snapshot!(goto(" -create schema authorization foo; +create table t(a int); +create table u(b int); +create function f(int) returns int as 'select 1' language sql; +update t set a = 1 from u, lateral f(b$0) q; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ create function f(int) returns int as 'select 1' language sql; + 5 │ update t set a = 1 from u, lateral f(b) q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_from_lateral_subquery_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(b int); +update t set a = 1 from u, lateral (select b$0) q; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ update t set a = 1 from u, lateral (select b) q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_from_function_arg_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(b int); +update t set a = 1 from u, generate_series(1, b$0) g; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ update t set a = 1 from u, generate_series(1, b) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_using_join_on_unqualified_column() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +delete from t using w join z on y$0 = 1; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ delete from t using w join z on y = 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_using_join_on_qualified_column() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +delete from t using w join z on w.y$0 = z.q; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ delete from t using w join z on w.y = z.q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_using_join_on_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +delete from t using w join z on w$0.y = z.q; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ delete from t using w join z on w.y = z.q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_using_function_arg_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table u(b int); +delete from t using u, generate_series(1, u$0.b) g; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ delete from t using u, generate_series(1, u.b) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_from_function_arg_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table u(b int); +update t set a = 1 from u, generate_series(1, u$0.b) g; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ update t set a = 1 from u, generate_series(1, u.b) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_join_on_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +merge into t using w join z on w$0.y = z.q on true when matched then delete; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ merge into t using w join z on w.y = z.q on true when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_using_lateral_subquery_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(b int); +delete from t using u, lateral (select b$0) q; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ delete from t using u, lateral (select b) q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_paren_join_on_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +merge into t using (w join z on w$0.y = 1) v on true when matched then delete; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ merge into t using (w join z on w.y = 1) v on true when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_paren_join_on_qualified_column() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +merge into t using (w join z on w.y$0 = 1) v on true when matched then delete; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ merge into t using (w join z on w.y = 1) v on true when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_paren_join_on_unqualified_column() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +merge into t using (w join z on y$0 = 1) v on true when matched then delete; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ merge into t using (w join z on y = 1) v on true when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_join_on_unqualified_column() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +merge into t using w join z on y$0 = 1 on true when matched then delete; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ merge into t using w join z on y = 1 on true when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_on_qualified_column_in_join_source() { + assert_snapshot!(goto(" +create table t(a int); +create table w(y int); +create table z(q int); +merge into t using w join z on true on t.a = w.y$0 when matched then delete; +"), @" + ╭▸ + 3 │ create table w(y int); + │ ─ 2. destination + 4 │ create table z(q int); + 5 │ merge into t using w join z on true on t.a = w.y when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_from_join_using_alias_qualifier() { + assert_snapshot!(goto(" +create table target(a int, b int); +create table u(b int); +create table w(b int); +update target +set a = 1 +from u join w using (b) as j +where j$0.b = target.b; +"), @" + ╭▸ + 7 │ from u join w using (b) as j + │ ─ 2. destination + 8 │ where j.b = target.b; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_from_join_using_alias_column() { + assert_snapshot!(goto(" +create table target(a int, b int); +create table u(b int); +create table w(b int); +update target +set a = 1 +from u join w using (b) as j +where j.b$0 = target.b; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ create table w(b int); + │ ─ 3. destination + ‡ + 8 │ where j.b = target.b; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_using_join_using_alias_column() { + assert_snapshot!(goto(" +create table target(a int, b int); +create table u(b int); +create table w(b int); +delete from target using u join w using (b) as j where j.b$0 = target.b; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ create table w(b int); + │ ─ 3. destination + 5 │ delete from target using u join w using (b) as j where j.b = target.b; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_join_using_alias_qualifier() { + assert_snapshot!(goto(" +create table target(a int, b int); +create table u(b int); +create table w(b int); +merge into target using u join w using (b) as j on j$0.b = target.b when matched then delete; +"), @" + ╭▸ + 5 │ merge into target using u join w using (b) as j on j.b = target.b when matched then delete; + │ ┬ ─ 1. source + │ │ + ╰╴ 2. destination + "); + } + + #[test] + fn goto_merge_using_join_using_alias_column() { + assert_snapshot!(goto(" +create table target(a int, b int); +create table u(b int); +create table w(b int); +merge into target using u join w using (b) as j on j.b$0 = target.b when matched then delete; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ create table w(b int); + │ ─ 3. destination + 5 │ merge into target using u join w using (b) as j on j.b = target.b when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_paren_join_alias_on_qualifier() { + assert_snapshot!(goto(" +create table w(y int); +create table z(q int); +select * from (w join z on w$0.y = 1) v; +"), @" + ╭▸ + 2 │ create table w(y int); + │ ─ 2. destination + 3 │ create table z(q int); + 4 │ select * from (w join z on w.y = 1) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_paren_join_alias_on_qualified_column() { + assert_snapshot!(goto(" +create table w(y int); +create table z(q int); +select * from (w join z on w.y$0 = 1) v; +"), @" + ╭▸ + 2 │ create table w(y int); + │ ─ 2. destination + 3 │ create table z(q int); + 4 │ select * from (w join z on w.y = 1) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_lateral_function_arg() { + assert_snapshot!(goto(" +create table u(b int); +create function f(int) returns int as 'select 1' language sql; +select * from u, lateral f(b$0) q; +"), @" + ╭▸ + 2 │ create table u(b int); + │ ─ 2. destination + 3 │ create function f(int) returns int as 'select 1' language sql; + 4 │ select * from u, lateral f(b) q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = 1 where exists (select 1 from u where u.b = t.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = 1 where exists (select 1 from u where u.b = t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_subquery_target_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = 1 where exists (select 1 from u where u.b = t$0.b); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = 1 where exists (select 1 from u where u.b = t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_subquery_target_alias_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t as x set a = 1 where exists (select 1 from u where u.b = x.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t as x set a = 1 where exists (select 1 from u where u.b = x.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_subquery_target_alias_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t as x set a = 1 where exists (select 1 from u where u.b = x$0.b); +"), @" + ╭▸ + 4 │ update t as x set a = 1 where exists (select 1 from u where u.b = x.b); + ╰╴ ─ 2. destination ─ 1. source + "); + } + + #[test] + fn goto_update_set_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = (select u.b from u where u.b = t.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = (select u.b from u where u.b = t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_set_subquery_target_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = (select u.b from u where u.b = t$0.b); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = (select u.b from u where u.b = t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_set_subquery_target_alias_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t x set a = (select u.b from u where u.b = x.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t x set a = (select u.b from u where u.b = x.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_nested_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = 1 where exists (select 1 from u where exists (select 1 where u.b = t.b$0)); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = 1 where exists (select 1 from u where exists (select 1 where u.b = t.b)); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +delete from t where exists (select 1 from u where u.b = t.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ delete from t where exists (select 1 from u where u.b = t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_subquery_target_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +delete from t where exists (select 1 from u where u.b = t$0.b); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ delete from t where exists (select 1 from u where u.b = t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_subquery_target_alias_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +delete from t as x where exists (select 1 from u where u.b = x.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ delete from t as x where exists (select 1 from u where u.b = x.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_subquery_target_alias_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +delete from t as x where exists (select 1 from u where u.b = x$0.b); +"), @" + ╭▸ + 4 │ delete from t as x where exists (select 1 from u where u.b = x.b); + ╰╴ ─ 2. destination ─ 1. source + "); + } + + #[test] + fn goto_merge_on_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t using u on exists (select 1 where u.b = t.b$0) when matched then delete; +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ merge into t using u on exists (select 1 where u.b = t.b) when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_on_subquery_target_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t using u on exists (select 1 where u.b = t$0.b) when matched then delete; +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ merge into t using u on exists (select 1 where u.b = t.b) when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_on_subquery_target_alias_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t as x using u on exists (select 1 where u.b = x.b$0) when matched then delete; +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ merge into t as x using u on exists (select 1 where u.b = x.b) when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_on_subquery_target_alias_qualifier() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t as x using u on exists (select 1 where u.b = x$0.b) when matched then delete; +"), @" + ╭▸ + 4 │ merge into t as x using u on exists (select 1 where u.b = x.b) when matched then delete; + ╰╴ ─ 2. destination ─ 1. source + "); + } + + #[test] + fn goto_merge_on_subquery_source_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t using u on exists (select 1 where u.b$0 = t.b) when matched then delete; +"), @" + ╭▸ + 3 │ create table u(b int); + │ ─ 2. destination + 4 │ merge into t using u on exists (select 1 where u.b = t.b) when matched then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_when_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t using u on true when matched and exists (select 1 where t.b$0 = 1) then delete; +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ merge into t using u on true when matched and exists (select 1 where t.b = 1) then delete; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_update_set_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +merge into t using u on true when matched then update set a = (select t.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ merge into t using u on true when matched then update set a = (select t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_subquery_target_hidden_by_alias() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t as x set a = 1 where exists (select 1 from u where u.b = t.b$0); +", + ); + } + + #[test] + fn goto_delete_where_subquery_target_hidden_by_alias() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +delete from t as x where exists (select 1 from u where u.b = t$0.b); +", + ); + } + + #[test] + fn goto_merge_update_set_unknown_qualifier() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +merge into t using u on true when matched then update set a = z.b$0; +", + ); + } + + #[test] + fn goto_from_lateral_subquery_sees_earlier_sibling() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u, lateral (select x$0) v; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u, lateral (select x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_lateral_subquery_sees_earlier_sibling_qualified() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u, lateral (select u.x$0) v; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u, lateral (select u.x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_function_arg_sees_earlier_sibling() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u, generate_series(1, x$0) g; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u, generate_series(1, x) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_rows_from_arg_sees_earlier_sibling() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u, rows from (generate_series(1, x$0)) g; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u, rows from (generate_series(1, x)) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_json_table_arg_sees_earlier_sibling() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u, json_table(j$0, '$' columns (a int)) jt; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u, json_table(j, '$' columns (a int)) jt; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_join_lateral_subquery_sees_earlier_sibling() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u join lateral (select x$0) v on true; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u join lateral (select x) v on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_non_lateral_subquery_sees_own_from() { + assert_snapshot!(goto(" +create table u(x int, j jsonb); +select * from u, (select x$0 from u) v; +"), @" + ╭▸ + 2 │ create table u(x int, j jsonb); + │ ─ 2. destination + 3 │ select * from u, (select x from u) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_non_lateral_subquery_hides_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from u, (select x$0) v; +", + ); + } + + #[test] + fn goto_from_non_lateral_subquery_hides_sibling_qualified() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from u, (select u$0.x) v; +", + ); + } + + #[test] + fn goto_from_lateral_subquery_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from lateral (select x$0) v, u; +", + ); + } + + #[test] + fn goto_from_lateral_subquery_hides_later_sibling_qualified() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from lateral (select u.x$0) v, u; +", + ); + } + + #[test] + fn goto_from_function_arg_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from generate_series(1, x$0) g, u; +", + ); + } + + #[test] + fn goto_from_function_arg_hides_later_sibling_qualified() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from generate_series(1, u$0.x) g, u; +", + ); + } + + #[test] + fn goto_from_rows_from_arg_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from rows from (generate_series(1, x$0)) g, u; +", + ); + } + + #[test] + fn goto_from_json_table_arg_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from json_table(j$0, '$' columns (a int)) jt, u; +", + ); + } + + #[test] + fn goto_from_join_non_lateral_subquery_hides_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from u join (select x$0) v on true; +", + ); + } + + #[test] + fn goto_from_join_lateral_subquery_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +select * from lateral (select x$0) v join u on true; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +create table t(a int); +update t set a = 1 from generate_series(1, x$0) g, u; +", + ); + } + + #[test] + fn goto_delete_using_function_arg_hides_later_sibling() { + goto_not_found( + " +create table u(x int, j jsonb); +create table t(a int); +delete from t using generate_series(1, x$0) g, u; +", + ); + } + + #[test] + fn goto_from_function_arg_sees_earlier_join_using_alias() { + assert_snapshot!(goto(" +create table u(b int); +create table w(b int); +select * from u join w using (b) as j, generate_series(1, j$0.b) g; +"), @" + ╭▸ + 4 │ select * from u join w using (b) as j, generate_series(1, j.b) g; + ╰╴ ─ 2. destination ─ 1. source + "); + } + + #[test] + fn goto_from_function_arg_sees_earlier_join_using_alias_column() { + assert_snapshot!(goto(" +create table u(b int); +create table w(b int); +select * from u join w using (b) as j, generate_series(1, j.b$0) g; +"), @" + ╭▸ + 2 │ create table u(b int); + │ ─ 2. destination + 3 │ create table w(b int); + │ ─ 3. destination + 4 │ select * from u join w using (b) as j, generate_series(1, j.b) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_lateral_subquery_sees_earlier_join_using_alias() { + assert_snapshot!(goto(" +create table u(b int); +create table w(b int); +select * from u join w using (b) as j, lateral (select j$0.b) q; +"), @" + ╭▸ + 4 │ select * from u join w using (b) as j, lateral (select j.b) q; + ╰╴ ─ 2. destination ─ 1. source + "); + } + + #[test] + fn goto_from_lateral_subquery_sees_earlier_join_using_alias_column() { + assert_snapshot!(goto(" +create table u(b int); +create table w(b int); +select * from u join w using (b) as j, lateral (select j.b$0) q; +"), @" + ╭▸ + 2 │ create table u(b int); + │ ─ 2. destination + 3 │ create table w(b int); + │ ─ 3. destination + 4 │ select * from u join w using (b) as j, lateral (select j.b) q; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_from_function_arg_hides_later_join_using_alias() { + goto_not_found( + " +create table u(b int); +create table w(b int); +select * from generate_series(1, j$0.b) g, u join w using (b) as j; +", + ); + } + + #[test] + fn goto_from_function_arg_hides_later_join_using_alias_column() { + goto_not_found( + " +create table u(b int); +create table w(b int); +select * from generate_series(1, j.b$0) g, u join w using (b) as j; +", + ); + } + + #[test] + fn goto_from_non_lateral_subquery_hides_join_using_alias() { + goto_not_found( + " +create table u(b int); +create table w(b int); +select * from u join w using (b) as j, (select j$0.b) q; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_later_join_using_alias() { + goto_not_found( + " +create table u(b int); +create table w(b int); +create table t(a int); +update t set a = 1 from generate_series(1, j$0.b) g, u join w using (b) as j; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_later_join_using_alias_column() { + goto_not_found( + " +create table u(b int); +create table w(b int); +create table t(a int); +update t set a = 1 from generate_series(1, j.b$0) g, u join w using (b) as j; +", + ); + } + + #[test] + fn goto_delete_using_function_arg_hides_later_join_using_alias() { + goto_not_found( + " +create table u(b int); +create table w(b int); +create table t(a int); +delete from t using generate_series(1, j$0.b) g, u join w using (b) as j; +", + ); + } + + #[test] + fn goto_outer_level_from_subquery_in_from() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from (select x$0) v) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from (select x) v) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_subquery_in_from_qualified() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from (select u.x$0) v) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from (select u.x) v) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_subquery_in_from_qualifier() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from (select u$0.x) v) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from (select u.x) v) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_subquery_in_from_alias() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from (select o.x$0) v) from u o; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from (select o.x) v) from u o; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_subquery_in_from_where() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select * from u where exists (select 1 from (select x$0) v); +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select * from u where exists (select 1 from (select x) v); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_subquery_in_from_with_sibling() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from w, (select x$0) v) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from w, (select x) v) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_skips_middle_level() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select (select 1 from (select x$0) v) from w) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select (select 1 from (select x) v) from w) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_function_arg() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from generate_series(1, x$0) g) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from generate_series(1, x) g) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_function_arg_qualified() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from generate_series(1, u.x$0) g) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from generate_series(1, u.x) g) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_from_nested_subqueries_in_from() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +select (select 1 from (select 1 from (select x$0) a) b) from u; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + 3 │ create table w(y int); + 4 │ select (select 1 from (select 1 from (select x) a) b) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_nearest_level_wins() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table z(x int); +select (select 1 from (select x$0 from z) v) from u; +"), @" + ╭▸ + 4 │ create table z(x int); + │ ─ 2. destination + 5 │ select (select 1 from (select x from z) v) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_target_from_subquery_in_from() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int, x int); +update t set a = (select 1 from (select x$0) v); +"), @" + ╭▸ + 4 │ create table t(a int, x int); + │ ─ 2. destination + 5 │ update t set a = (select 1 from (select x) v); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_target_from_subquery_in_from_qualified() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int, x int); +update t set a = (select 1 from (select t.x$0) v); +"), @" + ╭▸ + 4 │ create table t(a int, x int); + │ ─ 2. destination + 5 │ update t set a = (select 1 from (select t.x) v); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_target_from_subquery_in_from() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int, x int); +delete from t where exists (select 1 from (select x$0) v); +"), @" + ╭▸ + 4 │ create table t(a int, x int); + │ ─ 2. destination + 5 │ delete from t where exists (select 1 from (select x) v); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_outer_level_not_subquery_output_column() { + goto_not_found( + " +create table u(x int); +create table w(y int); +select (select 1 from (select x$0) v) from w; +", + ); + } + + #[test] + fn goto_outer_level_from_subquery_hides_outer_sibling() { + goto_not_found( + " +create table u(x int); +create table w(y int); +select * from u, (select (select 1 from (select x$0) v)) q; +", + ); + } + + #[test] + fn goto_begin_atomic_param_not_hidden_by_non_lateral_sibling() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from u, (values (x$0)) v; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select 1 from u, (values (x)) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_not_hidden_by_non_lateral_subquery_sibling() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from u, (select x$0) v; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select 1 from u, (select x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_not_hidden_by_later_sibling_function_arg() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from generate_series(1, x$0) g, u; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select 1 from generate_series(1, x) g, u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_not_hidden_by_later_sibling_lateral() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from lateral (select x$0) v, u; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select 1 from lateral (select x) v, u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_from_nested_subquery_in_from() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select (select 1 from (select x$0) v) from w; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select (select 1 from (select x) v) from w; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_in_select_without_column() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select x$0 from w; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select x from w; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_function_qualified() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from u, (select f.x$0) v; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select 1 from u, (select f.x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_column_shadows_param_in_select() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select x$0 from u; + end; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + ‡ + 7 │ select x from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_column_shadows_param_in_lateral() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from u, lateral (select x$0) v; + end; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + ‡ + 7 │ select 1 from u, lateral (select x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_column_shadows_param_in_function_arg() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from u, generate_series(1, x$0) g; + end; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + ‡ + 7 │ select 1 from u, generate_series(1, x) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_in_update_from_non_lateral_sibling() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + update t set a = 1 from u, (select x$0) v; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ update t set a = 1 from u, (select x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_in_update_from_later_sibling_function_arg() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + update t set a = 1 from generate_series(1, x$0) g, u; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ update t set a = 1 from generate_series(1, x) g, u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_in_delete_using_non_lateral_sibling() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + delete from t using u, (select x$0) v; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ delete from t using u, (select x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_function_qualified_in_select() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select f.x$0 from w; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select f.x from w; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_function_qualifier_in_select() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select f$0.x from w; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select f.x from w; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_function_qualifier() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + select 1 from u, (select f$0.x) v; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ select 1 from u, (select f.x) v; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_function_qualified_in_update() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table t(a int); +create function f(x int) returns setof int + begin atomic + update t set a = f.x$0; + end; +"), @" + ╭▸ + 5 │ create function f(x int) returns setof int + │ ─ 2. destination + 6 │ begin atomic + 7 │ update t set a = f.x; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_begin_atomic_param_wrong_qualifier() { + goto_not_found( + " +create table w(y int); +create function f(x int) returns setof int + begin atomic + select g.x$0 from w; + end; +", + ); + } + + #[test] + fn goto_begin_atomic_param_not_reached_through_target_qualifier() { + goto_not_found( + " +create table t(a int); +create function f(x int) returns setof int + begin atomic + update t set a = t.x$0; + end; +", + ); + } + + #[test] + fn goto_begin_atomic_table_named_like_function_shadows_param() { + assert_snapshot!(goto(" +create table f(x text); +create function f(x int) returns setof text + begin atomic + select f.x$0 from f; + end; +"), @" + ╭▸ + 2 │ create table f(x text); + │ ─ 2. destination + ‡ + 5 │ select f.x from f; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_right_join_lateral_hides_left_column() { + goto_not_found( + " +create table a(x int); +select * from a right join lateral (select a.x$0) v on true; +", + ); + } + + #[test] + fn goto_right_join_lateral_hides_left_qualifier() { + goto_not_found( + " +create table a(x int); +select * from a right join lateral (select a$0.x) v on true; +", + ); + } + + #[test] + fn goto_full_join_function_hides_left_column() { + goto_not_found( + " +create table a(x int); +select * from a full join generate_series(1, a.x$0) g on true; +", + ); + } + + #[test] + fn goto_right_join_lateral_keeps_comma_sibling_visible() { + assert_snapshot!(goto(" +create table x(q int); +create table a(y int); +select * from x, a right join lateral (select x.q$0) v on true; +"), @" + ╭▸ + 2 │ create table x(q int); + │ ─ 2. destination + 3 │ create table a(y int); + 4 │ select * from x, a right join lateral (select x.q) v on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_right_join_lateral_sees_own_rhs_sibling() { + assert_snapshot!(goto(" +create table a(y int); +create table b(x int); +select * from a right join (b cross join lateral (select b.x$0) v) on true; +"), @" + ╭▸ + 3 │ create table b(x int); + │ ─ 2. destination + 4 │ select * from a right join (b cross join lateral (select b.x) v) on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_left_join_lateral_sees_left_column() { + assert_snapshot!(goto(" +create table a(x int); +select * from a left join lateral (select a.x$0) v on true; +"), @" + ╭▸ + 2 │ create table a(x int); + │ ─ 2. destination + 3 │ select * from a left join lateral (select a.x) v on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_nested_join_lateral_sees_earlier_paren_join() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table z(x int); +select * from (u cross join w) join lateral (select x$0) v on true; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + ‡ + 5 │ select * from (u cross join w) join lateral (select x) v on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_nested_join_lateral_in_paren_sees_outer_earlier() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table z(x int); +select * from u join (w cross join lateral (select x$0) v) on true; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + ‡ + 5 │ select * from u join (w cross join lateral (select x) v) on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_nested_join_lateral_in_paren_sees_paren_earlier() { + assert_snapshot!(goto(" +create table u(x int); +create table w(y int); +create table z(x int); +select * from w join (u cross join lateral (select x$0) v) on true; +"), @" + ╭▸ + 2 │ create table u(x int); + │ ─ 2. destination + ‡ + 5 │ select * from w join (u cross join lateral (select x) v) on true; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_nested_join_non_lateral_in_paren_hides_siblings() { + goto_not_found( + " +create table u(x int); +create table w(y int); +create table z(x int); +select * from w join (u cross join (select x$0) v) on true; +", + ); + } + + #[test] + fn goto_nested_join_non_lateral_in_paren_hides_outer() { + goto_not_found( + " +create table u(x int); +create table w(y int); +create table z(x int); +select * from z join (w cross join (select x$0) v) on true; +", + ); + } + + #[test] + fn goto_nested_join_lateral_hides_self_qualified() { + goto_not_found( + " +create table u(x int); +create table w(y int); +create table z(x int); +select * from u, lateral (select v$0.x) v; +", + ); + } + + #[test] + fn goto_nested_join_lateral_in_paren_hides_later() { + goto_not_found( + " +create table u(x int); +create table w(y int); +create table z(x int); +select * from (w cross join lateral (select x$0) v cross join u); +", + ); + } + + #[test] + fn goto_nested_join_lateral_hides_later_paren_join() { + goto_not_found( + " +create table u(x int); +create table w(y int); +create table z(x int); +select * from lateral (select x$0) v join (u cross join w) on true; +", + ); + } + + #[test] + fn goto_nested_join_aliased_paren_join_hides_inner_name() { + goto_not_found( + " +create table u(x int); +create table w(y int); +create table z(x int); +select * from (u cross join w) p, lateral (select u$0.x) q; +", + ); + } + + #[test] + fn goto_on_clause_subquery_sees_own_join() { + assert_snapshot!(goto(" +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from u join w on exists (select x$0); +"), @" + ╭▸ + 2 │ create table u(x int, b int); + │ ─ 2. destination + ‡ + 8 │ select * from u join w on exists (select x); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_on_clause_falls_through_to_outer_level() { + assert_snapshot!(goto(" +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select (select 1 from w join z on x$0 = 1) from u; +"), @" + ╭▸ + 2 │ create table u(x int, b int); + │ ─ 2. destination + ‡ + 8 │ select (select 1 from w join z on x = 1) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_on_clause_hides_comma_sibling() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from u, w join z on x$0 = 1; +", + ); + } + + #[test] + fn goto_on_clause_hides_comma_sibling_qualified() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from u, w join z on u$0.x = 1; +", + ); + } + + #[test] + fn goto_on_clause_hides_later_comma_sibling() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from w join z on x$0 = 1, u; +", + ); + } + + #[test] + fn goto_on_clause_hides_table_outside_paren_join() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from y join (a join b on a.c = y$0.f) on true; +", + ); + } + + #[test] + fn goto_on_clause_hides_column_outside_paren_join() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from y join (a join b on a.c = f$0) on true; +", + ); + } + + #[test] + fn goto_on_clause_hides_comma_sibling_join_using_alias() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +select * from u join w using (b) as j, a join z on j$0.b = 1; +", + ); + } + + #[test] + fn goto_update_from_on_clause_hides_comma_sibling() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +create table t(a int); +update t set a = 1 from u, w join z on x$0 = 1; +", + ); + } + + #[test] + fn goto_delete_using_on_clause_hides_comma_sibling() { + goto_not_found( + " +create table u(x int, b int); +create table w(y int, b int); +create table z(q int); +create table y(f int); +create table a(c int); +create table b(d int); +create table t(a int); +delete from t using u, w join z on u$0.x = 1; +", + ); + } + + #[test] + fn goto_update_returning_subquery_old_column() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = 1 returning (select old.b$0); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = 1 returning (select old.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_returning_subquery_old_table() { + assert_snapshot!(goto(" +create table t(a int, b int); +create table u(b int); +update t set a = 1 returning (select old$0.b); +"), @" + ╭▸ + 2 │ create table t(a int, b int); + │ ─ 2. destination + 3 │ create table u(b int); + 4 │ update t set a = 1 returning (select old.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_where_old_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t set a = 1 where old.a$0 = 1; +", + ); + } + + #[test] + fn goto_update_where_old_table_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t set a = 1 where old$0.a = 1; +", + ); + } + + #[test] + fn goto_update_set_new_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t set a = new.a$0; +", + ); + } + + #[test] + fn goto_update_where_subquery_old_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t set a = 1 where exists (select old.a$0); +", + ); + } + + #[test] + fn goto_update_where_returning_alias_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t set a = 1 where o$0.a = 1 returning with (old as o) o.a; +", + ); + } + + #[test] + fn goto_update_where_returning_alias_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +update t set a = 1 where o.a$0 = 1 returning with (old as o) o.a; +", + ); + } + + #[test] + fn goto_update_returning_old_alias_hides_old_qualifier() { + goto_not_found( + " +create table t(a int); +update t set a = 1 returning with (old as before) old$0.a; +", + ); + } + + #[test] + fn goto_update_returning_old_alias_hides_old_column() { + goto_not_found( + " +create table t(a int); +update t set a = 1 returning with (old as before) old.a$0; +", + ); + } + + #[test] + fn goto_update_returning_new_alias_hides_new_column() { + goto_not_found( + " +create table t(a int); +update t set a = 1 returning with (new as after) new.a$0; +", + ); + } + + #[test] + fn goto_update_returning_old_alias_keeps_new_visible() { + assert_snapshot!(goto(" +create table t(a int); +update t set a = 1 returning with (old as before) new.a$0; +"), @" + ╭▸ + 2 │ create table t(a int); + │ ─ 2. destination + 3 │ update t set a = 1 returning with (old as before) new.a; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_returning_new_alias_keeps_old_visible() { + assert_snapshot!(goto(" +create table t(a int); +update t set a = 1 returning with (new as after) old.a$0; +"), @" + ╭▸ + 2 │ create table t(a int); + │ ─ 2. destination + 3 │ update t set a = 1 returning with (new as after) old.a; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_delete_where_new_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +delete from t where new.a$0 = 1; +", + ); + } + + #[test] + fn goto_merge_on_old_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +merge into t using u on old.a$0 = u.b when matched then delete; +", + ); + } + + #[test] + fn goto_merge_on_old_table_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +merge into t using u on old$0.a = u.b when matched then delete; +", + ); + } + + #[test] + fn goto_merge_when_old_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +merge into t using u on true when matched and old.a$0 = 1 then delete; +", + ); + } + + #[test] + fn goto_merge_update_set_old_column_not_found() { + goto_not_found( + " +create table t(a int, b int); +create table u(b int); +merge into t using u on true when matched then update set a = old.a$0; +", + ); + } + + #[test] + fn goto_merge_using_function_arg_sees_earlier_qualifier() { + assert_snapshot!(goto(" +create table t(a int); +create table u(x int); +merge into t +using (u cross join generate_series(1, u$0.x) g) s +on true +when matched then do nothing; +"), @" + ╭▸ + 3 │ create table u(x int); + │ ─ 2. destination + 4 │ merge into t + 5 │ using (u cross join generate_series(1, u.x) g) s + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_function_arg_sees_earlier_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(x int); +merge into t +using (u cross join generate_series(1, u.x$0) g) s +on true +when matched then do nothing; +"), @" + ╭▸ + 3 │ create table u(x int); + │ ─ 2. destination + 4 │ merge into t + 5 │ using (u cross join generate_series(1, u.x) g) s + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_merge_using_function_arg_hides_later_qualifier() { + goto_not_found( + " +create table t(a int); +create table u(x int); +merge into t +using (generate_series(1, u$0.x) g cross join u) s +on true +when matched then do nothing; +", + ); + } + + #[test] + fn goto_merge_not_matched_target_condition_hides_target_column() { + goto_not_found( + " +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched and t.a$0 = 1 then do nothing; +", + ); + } + + #[test] + fn goto_merge_not_matched_target_action_hides_target_qualifier() { + goto_not_found( + " +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched then insert values (t$0.a); +", + ); + } + + #[test] + fn goto_merge_not_matched_target_action_hides_unqualified_target_column() { + goto_not_found( + " +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched then insert values (a$0); +", + ); + } + + #[test] + fn goto_merge_not_matched_source_condition_hides_source_column() { + goto_not_found( + " +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched by source and u.b$0 = 1 then do nothing; +", + ); + } + + #[test] + fn goto_merge_not_matched_source_action_hides_source_qualifier() { + goto_not_found( + " +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched by source then update set a = u$0.b; +", + ); + } + + #[test] + fn goto_merge_not_matched_source_action_hides_unqualified_source_column() { + goto_not_found( + " +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched by source then update set a = b$0; +", + ); + } + + #[test] + fn goto_merge_not_matched_source_action_sees_target_column() { + assert_snapshot!(goto(" +create table t(a int); +create table u(b int); +merge into t using u on true +when not matched by source then update set a = t.a$0 + 1; +"), @" + ╭▸ + 2 │ create table t(a int); + │ ─ 2. destination + ‡ + 5 │ when not matched by source then update set a = t.a + 1; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_cte_in_subquery_sees_outer_query() { + assert_snapshot!(goto(" +create table u(x int, b int); +create table w(b int); +select (with c as (select u.x$0) select * from c) from u; +"), @" + ╭▸ + 2 │ create table u(x int, b int); + │ ─ 2. destination + 3 │ create table w(b int); + 4 │ select (with c as (select u.x) select * from c) from u; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_cte_body_hides_main_from_qualifier() { + goto_not_found( + " +create table u(x int, b int); +create table w(b int); +with c as (select u$0.x) select * from u, c; +", + ); + } + + #[test] + fn goto_cte_body_hides_main_from_qualified_column() { + goto_not_found( + " +create table u(x int, b int); +create table w(b int); +with c as (select u.x$0) select * from u, c; +", + ); + } + + #[test] + fn goto_cte_body_hides_main_from_column() { + goto_not_found( + " +create table u(x int, b int); +create table w(b int); +with c as (select x$0) select * from u, c; +", + ); + } + + #[test] + fn goto_cte_body_hides_main_join_using_alias() { + goto_not_found( + " +create table u(x int, b int); +create table w(b int); +with c as (select j$0.b) select * from u join w using (b) as j, c; +", + ); + } + + #[test] + fn goto_cte_body_hides_update_target_qualifier() { + goto_not_found( + " +create table u(x int, b int); +create table w(b int); +create table t(a int); +with c as (select t$0.a) update t set a = 1 from c; +", + ); + } + + #[test] + fn goto_update_from_function_arg_param_when_target_lacks_column() { + assert_snapshot!(goto(" +create table t(a int, z int); +create table u(b int); +create table w(y int); +create function f(p int) returns void + begin atomic + update t set a = 1 from generate_series(1, p$0) g; + end; +"), @" + ╭▸ + 5 │ create function f(p int) returns void + │ ─ 2. destination + 6 │ begin atomic + 7 │ update t set a = 1 from generate_series(1, p) g; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_update_from_lateral_subquery_hides_target_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t set a = 1 from u, lateral (select z$0) v; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_target_qualifier() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t set a = 1 from generate_series(1, t$0.a) g; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_target_qualified_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t set a = 1 from generate_series(1, t.a$0) g; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_target_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t set a = 1 from generate_series(1, a$0) g; +", + ); + } + + #[test] + fn goto_update_from_on_clause_hides_target_qualifier() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t set a = 1 from u join w on t$0.a = 1; +", + ); + } + + #[test] + fn goto_update_from_on_clause_hides_target_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t set a = 1 from u join w on a$0 = 1; +", + ); + } + + #[test] + fn goto_update_from_function_arg_hides_target_alias() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +update t as x set a = 1 from generate_series(1, x$0.a) g; +", + ); + } + + #[test] + fn goto_delete_using_lateral_subquery_hides_target_qualified_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +delete from t using u, lateral (select t.a$0) v; +", + ); + } + + #[test] + fn goto_delete_using_function_arg_hides_target_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +delete from t using generate_series(1, a$0) g; +", + ); + } + + #[test] + fn goto_merge_using_subquery_hides_target_qualifier() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +merge into t using (select t$0.a) s on true when matched then delete; +", + ); + } + + #[test] + fn goto_merge_using_subquery_hides_target_column() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +merge into t using (select a$0) s on true when matched then delete; +", + ); + } + + #[test] + fn goto_update_from_function_arg_target_column_shadows_param() { + goto_not_found( + " +create table t(a int, z int); +create table u(b int); +create table w(y int); +create function f(a int) returns void + begin atomic + update t set a = 1 from generate_series(1, a$0) g; + end; +", + ); + } + + #[test] + fn goto_select_from_table() { + assert_snapshot!(goto(" +create table users(id int, email text); +select * from users$0; +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ select * from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_from_table_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text); +select * from public.users$0; +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text); + │ ───── 2. destination + 3 │ select * from public.users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_from_table_with_search_path() { + assert_snapshot!(goto(" +set search_path to foo; +create table foo.users(id int, email text); +select * from users$0; +"), @r" + ╭▸ + 3 │ create table foo.users(id int, email text); + │ ───── 2. destination + 4 │ select * from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_from_temp_table() { + assert_snapshot!(goto(" +create temp table users(id int, email text); +select * from users$0; +"), @r" + ╭▸ + 2 │ create temp table users(id int, email text); + │ ───── 2. destination + 3 │ select * from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_from_table_defined_after() { + assert_snapshot!(goto(" +select * from users$0; +create table users(id int, email text); +"), @r" + ╭▸ + 2 │ select * from users; + │ ─ 1. source + 3 │ create table users(id int, email text); + ╰╴ ───── 2. destination + "); + } + + #[test] + fn goto_select_column() { + assert_snapshot!(goto(" +create table users(id int, email text); +select id$0 from users; +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ── 2. destination + 3 │ select id from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_column_second() { + assert_snapshot!(goto(" +create table users(id int, email text); +select id, email$0 from users; +"), @r" + ╭▸ + 2 │ create table users(id int, email text); + │ ───── 2. destination + 3 │ select id, email from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_column_with_schema() { + assert_snapshot!(goto(" +create table public.users(id int, email text); +select email$0 from public.users; +"), @r" + ╭▸ + 2 │ create table public.users(id int, email text); + │ ───── 2. destination + 3 │ select email from public.users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_column_with_search_path() { + assert_snapshot!(goto(" +set search_path to foo; +create table foo.users(id int, email text); +select id$0 from users; +"), @r" + ╭▸ + 3 │ create table foo.users(id int, email text); + │ ── 2. destination + 4 │ select id from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_table_as_column() { + assert_snapshot!(goto(" +create table t(x bigint, y bigint); +select t$0 from t; +"), @r" + ╭▸ + 2 │ create table t(x bigint, y bigint); + │ ─ 2. destination + 3 │ select t from t; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_table_star_expansion() { + assert_snapshot!(goto(" +create table t(id int, a int); +select t$0.* from t; +"), @r" + ╭▸ + 2 │ create table t(id int, a int); + │ ─ 2. destination + 3 │ select t.* from t; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_table_as_column_with_schema() { + assert_snapshot!(goto(" +create table public.t(x bigint, y bigint); +select t$0 from public.t; +"), @r" + ╭▸ + 2 │ create table public.t(x bigint, y bigint); + │ ─ 2. destination + 3 │ select t from public.t; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_table_as_column_with_search_path() { + assert_snapshot!(goto(" +set search_path to foo; +create table foo.users(id int, email text); +select users$0 from users; +"), @r" + ╭▸ + 3 │ create table foo.users(id int, email text); + │ ───── 2. destination + 4 │ select users from users; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_column_with_same_name_as_table() { + assert_snapshot!(goto(" +create table t(t int); +select t$0 from t; +"), @r" + ╭▸ + 2 │ create table t(t int); + │ ─ 2. destination + 3 │ select t from t; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_view_name_from_view() { + assert_snapshot!(goto(" +create view boop as select 1 a; +select boop$0 from boop; +"), @" + ╭▸ + 2 │ create view boop as select 1 a; + │ ──── 2. destination + 3 │ select boop from boop; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_schema() { + assert_snapshot!(goto(" +create schema foo; +drop schema foo$0; +"), @r" + ╭▸ + 2 │ create schema foo; + │ ─── 2. destination + 3 │ drop schema foo; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_create_schema_authorization() { + assert_snapshot!(goto(" +create schema authorization foo$0; +"), @r" + ╭▸ + 2 │ create schema authorization foo; + │ ┬─┬ + │ │ │ + │ │ 1. source + ╰╴ 2. destination + "); + } + + #[test] + fn goto_drop_schema_authorization() { + assert_snapshot!(goto(" +create schema authorization foo; drop schema foo$0; "), @r" ╭▸ @@ -13636,6 +16584,195 @@ insert into t values ('c', 'd') on conflict (c) do update set c = excluded.c$0;" "); } + #[test] + fn goto_insert_on_conflict_excluded_table() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = excluded$0.b;" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b int); + │ ─ 2. destination + 3 │ insert into t values (1, 2) on conflict (a) do update set b = excluded.b; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_excluded_whole_row() { + assert_snapshot!(goto(" +create table t(a int primary key, b jsonb); +insert into t values (1, '{}') on conflict (a) do update set b = to_jsonb(excluded$0);" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b jsonb); + │ ─ 2. destination + 3 │ insert into t values (1, '{}') on conflict (a) do update set b = to_jsonb(excluded); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_excluded_view() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +create view v as select a, b from t; +insert into v values (1, 2) on conflict (a) do update set b = excluded$0.b;" + ), @" + ╭▸ + 3 │ create view v as select a, b from t; + │ ─ 2. destination + 4 │ insert into v values (1, 2) on conflict (a) do update set b = excluded.b; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_set_subquery_target_column() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = (select t.b$0);" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b int); + │ ─ 2. destination + 3 │ insert into t values (1, 2) on conflict (a) do update set b = (select t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_set_subquery_target_qualifier() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = (select t$0.b);" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b int); + │ ─ 2. destination + 3 │ insert into t values (1, 2) on conflict (a) do update set b = (select t.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_set_subquery_unqualified_column() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = (select b$0);" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b int); + │ ─ 2. destination + 3 │ insert into t values (1, 2) on conflict (a) do update set b = (select b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_set_subquery_excluded_column() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = (select excluded.b$0);" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b int); + │ ─ 2. destination + 3 │ insert into t values (1, 2) on conflict (a) do update set b = (select excluded.b); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_where_subquery_excluded_column() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = 1 where exists (select 1 where excluded.b$0 > 1);" + ), @" + ╭▸ + 2 │ create table t(a int primary key, b int); + │ ─ 2. destination + 3 │ insert into t values (1, 2) on conflict (a) do update set b = 1 where exists (select 1 where excluded.b > 1); + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_where_old_column_not_found() { + goto_not_found( + " +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = 1 where old.b$0 > 1; +", + ); + } + + #[test] + fn goto_insert_returning_excluded_column_not_found() { + goto_not_found( + " +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do update set b = 1 returning excluded.b$0; +", + ); + } + + #[test] + fn goto_insert_on_conflict_nothing_excluded_column_not_found() { + goto_not_found( + " +create table t(a int primary key, b int); +insert into t values (1, 2) on conflict (a) do nothing returning excluded.b$0; +", + ); + } + + #[test] + fn goto_insert_alias_hides_table_in_on_conflict_set() { + goto_not_found( + " +create table t(a int primary key, b int); +insert into t as z values (1, 2) on conflict (a) do update set b = t.b$0; +", + ); + } + + #[test] + fn goto_insert_on_conflict_set_function_param() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +create function f(y int) returns void + begin atomic + insert into t values (1, 2) on conflict (a) do update set b = y$0; + end; +"), @" + ╭▸ + 3 │ create function f(y int) returns void + │ ─ 2. destination + 4 │ begin atomic + 5 │ insert into t values (1, 2) on conflict (a) do update set b = y; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_insert_on_conflict_where_function_param() { + assert_snapshot!(goto(" +create table t(a int primary key, b int); +create function f(y int) returns void + begin atomic + insert into t values (1, 2) on conflict (a) do update set b = 1 where y$0 > 1; + end; +"), @" + ╭▸ + 3 │ create function f(y int) returns void + │ ─ 2. destination + 4 │ begin atomic + 5 │ insert into t values (1, 2) on conflict (a) do update set b = 1 where y > 1; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_insert_on_conflict_qualified_function() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 135426a7..8128b445 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -4378,6 +4378,18 @@ create procedure foo$0() language sql as $$ select 1 $$; "); } + #[test] + fn hover_on_procedure_qualified_parameter_qualifier() { + assert_snapshot!(check_hover(" +create procedure p(x int) language sql begin atomic select p$0.x; end; +"), @r" + hover: procedure public.p(x int) + ╭▸ + 2 │ create procedure p(x int) language sql begin atomic select p.x; end; + ╰╴ ─ hover + "); + } + #[test] fn hover_on_create_procedure_with_explicit_schema() { assert_snapshot!(check_hover(" diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 3169886e..3b311412 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -879,34 +879,42 @@ pub(crate) fn resolve_name_ref( resolve_select_qualified_column_ptr(db, InFile::new(file, name_ref)) } NameRefClass::InsertColumn => { + if column_qualifier_name(name_ref).is_some() { + return resolve_dml_column_ptr( + db, + InFile::new(file, name_ref), + DmlScope::enclosing(name_ref.syntax())?, + ); + } let column_name = Name::from_node(name_ref); let insert = name_ref.syntax().ancestors().find_map(ast::Insert::cast)?; let path = insert.relation_name_ref()?.path_ref()?; - resolve_column_for_path(db, InFile::new(file, &path), column_name) + if let Some(column) = resolve_column_for_path(db, InFile::new(file, &path), column_name) + { + return Some(column); + } + if is_target_definition(name_ref.syntax()) { + return None; + } + let scope = DmlScope::new(insert.syntax(), name_ref.syntax())?; + resolve_dml_table_name_ptr(db, InFile::new(file, name_ref), scope) + .or_else(|| resolve_enclosing_function_param(InFile::new(file, name_ref))) } - NameRefClass::InsertQualifiedColumnTable => { - let insert = name_ref.syntax().ancestors().find_map(ast::Insert::cast)?; - let path = insert.relation_name_ref()?.path_ref()?; - resolve_table_in_returning_clause( + NameRefClass::DeleteColumn | NameRefClass::UpdateColumn | NameRefClass::MergeColumn => { + resolve_dml_column_ptr( db, InFile::new(file, name_ref), - insert.alias().and_then(|alias| alias.name()), - &path, - insert.returning_clause(), + DmlScope::enclosing(name_ref.syntax())?, ) } - NameRefClass::DeleteColumn => resolve_delete_column_ptr(db, InFile::new(file, name_ref)), - NameRefClass::DeleteQualifiedColumnTable => { - resolve_delete_table_name_ptr(db, InFile::new(file, name_ref)) - } - NameRefClass::UpdateColumn => resolve_update_column_ptr(db, InFile::new(file, name_ref)), - NameRefClass::UpdateQualifiedColumnTable => { - resolve_update_table_name_ptr(db, InFile::new(file, name_ref)) - } - NameRefClass::MergeColumn => resolve_merge_column_ptr(db, InFile::new(file, name_ref)), - NameRefClass::MergeQualifiedColumnTable => { - resolve_merge_table_name_ptr(db, InFile::new(file, name_ref)) - } + NameRefClass::InsertQualifiedColumnTable + | NameRefClass::DeleteQualifiedColumnTable + | NameRefClass::UpdateQualifiedColumnTable + | NameRefClass::MergeQualifiedColumnTable => resolve_dml_table_name_ptr( + db, + InFile::new(file, name_ref), + DmlScope::enclosing(name_ref.syntax())?, + ), NameRefClass::JoinUsingColumn => { let join_expr = name_ref .syntax() @@ -1940,7 +1948,14 @@ fn resolve_select_qualified_column_table_name_ptr( return Some(locations); } - let from_item = find_from_item_for_select_qualified_name_ref(table_name_ref, &table_name)?; + let Some(from_item) = find_from_item_for_select_qualified_name_ref(table_name_ref, &table_name) + else { + return DmlScope::enclosing(table_name_ref.syntax()) + .and_then(|scope| { + resolve_dml_table_name_ptr(db, InFile::new(file, table_name_ref), scope) + }) + .or_else(|| resolve_enclosing_routine_name_ptr(InFile::new(file, table_name_ref))); + }; if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) && Name::from_node(&alias_name) == table_name @@ -1981,20 +1996,38 @@ enum ReturningClauseMatch { TableAlias(ast::TableAlias), } +// `old`, `new` and their `returning with` aliases are only visible from inside +// the returning clause fn match_table_in_returning_clause( + node: &SyntaxNode, table_name: &Name, stmt_table_name: &Name, alias: Option<&ast::TableAlias>, returning_clause: Option<&ast::ReturningClause>, ) -> Option { - // Check `returning with (old as alias, new as alias)` + let returning_clause = returning_clause.filter(|returning_clause| { + returning_clause + .syntax() + .text_range() + .contains_range(node.text_range()) + }); + + // Check `returning with (old as alias, new as alias)`. An alias hides the + // corresponding built-in name. + let mut pseudo_table_is_aliased = false; if let Some(option_list) = returning_clause.and_then(|x| x.returning_option_list()) { for option in option_list.returning_options() { - if let Some(alias) = option.name() - && Name::from_node(&alias) == *table_name - { + let (pseudo_table_name, alias) = match option { + ast::ReturningOption::ReturningOld(option) => ("old", option.name()), + ast::ReturningOption::ReturningNew(option) => ("new", option.name()), + }; + let Some(alias) = alias else { + continue; + }; + if Name::from_node(&alias) == *table_name { return Some(ReturningClauseMatch::ReturningAlias(alias)); } + pseudo_table_is_aliased |= *table_name == pseudo_table_name; } } @@ -2004,7 +2037,10 @@ fn match_table_in_returning_clause( return Some(ReturningClauseMatch::TableAlias(alias.clone())); } - if *table_name == "old" || *table_name == "new" { + if returning_clause.is_some() + && (*table_name == "old" || *table_name == "new") + && !pseudo_table_is_aliased + { return Some(ReturningClauseMatch::PseudoTable); } @@ -2032,7 +2068,15 @@ fn resolve_select_qualified_column_ptr( let position = column_name_ref.syntax().text_range().start(); - let (schema, mut table_name) = if let Some(schema) = explicit_schema { + let resolve_dml = |stmt: &SyntaxNode| { + resolve_dml_column_ptr( + db, + InFile::new(file, column_name_ref), + DmlScope::new(stmt, column_name_ref.syntax())?, + ) + }; + + let (schema, table_name) = if let Some(schema) = explicit_schema { (Some(schema), column_table_name) } else { match ast_nav::node_parent_query(column_name_ref.syntax())? { @@ -2045,10 +2089,18 @@ fn resolve_select_qualified_column_ptr( return Some(locations); } - let from_item = find_from_item_for_select_qualified_name_ref( + let Some(from_item) = find_from_item_for_select_qualified_name_ref( column_name_ref, &column_table_name, - )?; + ) else { + return DmlScope::enclosing(column_name_ref.syntax()) + .and_then(|scope| { + resolve_dml_column_ptr(db, InFile::new(file, column_name_ref), scope) + }) + .or_else(|| { + resolve_enclosing_function_param(InFile::new(file, column_name_ref)) + }); + }; if let ast::FromItem::FunctionFromItem(func) = &from_item && let Some(call_expr) = func.call_expr() @@ -2152,48 +2204,10 @@ fn resolve_select_qualified_column_ptr( } name::schema_and_table_from_from_item(&from_item)? } - ast_nav::ParentQuery::Update(update) => { - let path = update.relation_name()?.relation_name_ref()?.path_ref()?; - name::schema_and_name_path(&path)? - } - ast_nav::ParentQuery::Delete(delete) => { - let path = delete.relation_name()?.relation_name_ref()?.path_ref()?; - name::schema_and_name_path(&path)? - } - ast_nav::ParentQuery::Insert(insert) => { - let path = insert.relation_name_ref()?.path_ref()?; - name::schema_and_name_path(&path)? - } - ast_nav::ParentQuery::Merge(merge) => { - // When the qualifier refers to the USING source (by alias or by - // relation name), resolve the column against that source. This - // handles subquery and VALUES sources, where the qualifier is not - // a real table name. - if let Some(from_item) = ast_nav::merge_using_from_item(&merge) { - let matches_source = if let Some(alias_name) = - from_item.alias().and_then(|alias| alias.name()) - { - Name::from_node(&alias_name) == column_table_name - } else if let Some((_, item_name)) = - name::schema_and_table_from_from_item(&from_item) - { - item_name == column_table_name - } else { - false - }; - - if matches_source { - return resolve_from_item_column_ptr( - db, - InFile::new(file, &from_item), - column_name_ref, - ); - } - } - - let path = merge.table_relation_name()?.table_name_ref()?.path_ref()?; - name::schema_and_name_path(&path)? - } + ast_nav::ParentQuery::Update(update) => return resolve_dml(update.syntax()), + ast_nav::ParentQuery::Delete(delete) => return resolve_dml(delete.syntax()), + ast_nav::ParentQuery::Insert(insert) => return resolve_dml(insert.syntax()), + ast_nav::ParentQuery::Merge(merge) => return resolve_dml(merge.syntax()), } }; @@ -2209,9 +2223,6 @@ fn resolve_select_qualified_column_ptr( } return None; } - if let Some(alias_table_name) = resolve_merge_alias(column_name_ref, &table_name) { - table_name = alias_table_name; - } } let schemas = bind(db, file).resolved_schemas(position, schema.as_ref()); @@ -2315,21 +2326,6 @@ pub(crate) fn resolve_table_name( None } -fn resolve_merge_alias(name_ref: &impl ast::NameLike, table_name: &Name) -> Option { - let from_item = name_ref - .syntax() - .ancestors() - .find_map(|x| ast_nav::merge_using_from_item(&ast::Merge::cast(x)?))?; - if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) - && Name::from_node(&alias_name) == *table_name - && let ast::FromItem::RelationFromItem(relation) = &from_item - { - let table_name = Name::from_node(&relation.name_ref()?); - return Some(table_name); - } - None -} - fn resolve_from_item_column_ptr( db: &dyn Db, from_item: InFile<&ast::FromItem>, @@ -2820,7 +2816,10 @@ fn resolve_select_column_ptr( // In the case of ambiguous columns, we'll have multiple matches. // They're an error, but we'll report that elsewhere. let mut results: SmallVec<[Location; 1]> = SmallVec::new(); - for from_item in ast_nav::iter_from_clause(&from_clause) { + for from_item in ast_nav::visible_from_items( + ast_nav::iter_from_clause(&from_clause), + column_name_ref.syntax(), + ) { if let Some(column_ptr) = resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) { @@ -2835,16 +2834,9 @@ fn resolve_select_column_ptr( // A correlated subquery can reference the target relation of an enclosing // DML statement, e.g. `update foo set a = (select b)` where `b` is `foo.b` let in_file = InFile::new(file, column_name_ref); - for ancestor in ast_nav::ancestors_outside_own_with_clause(column_name_ref.syntax()) { - match ancestor.kind() { - SyntaxKind::UPDATE => return resolve_update_column_ptr(db, in_file), - SyntaxKind::DELETE => return resolve_delete_column_ptr(db, in_file), - SyntaxKind::MERGE => return resolve_merge_column_ptr(db, in_file), - _ => (), - } - } - - resolve_enclosing_function_param(in_file) + DmlScope::enclosing(column_name_ref.syntax()) + .and_then(|scope| resolve_dml_column_ptr(db, in_file, scope)) + .or_else(|| resolve_enclosing_function_param(in_file)) } fn resolve_enclosing_function_param( @@ -2854,6 +2846,13 @@ fn resolve_enclosing_function_param( let name_ref = name_ref.value; let param_name = Name::from_node(name_ref); + if let Some(qualifier) = column_qualifier_name(name_ref) + && ast_nav::enclosing_routine_name(name_ref.syntax()) + .is_none_or(|(routine_name, _, _)| routine_name != qualifier) + { + return None; + } + for ancestor in name_ref.syntax().ancestors() { let Some(has_param_list) = ast::HasParamList::cast(ancestor) else { continue; @@ -2877,6 +2876,24 @@ fn resolve_enclosing_function_param( None } +fn resolve_enclosing_routine_name_ptr( + name_ref: InFile<&impl ast::NameLike>, +) -> Option> { + let (routine_name, segment, kind) = ast_nav::enclosing_routine_name(name_ref.value.syntax())?; + if Name::from_node(name_ref.value) != routine_name { + return None; + } + let kind = match kind { + ast_nav::RoutineKind::Function => LocationKind::Function, + ast_nav::RoutineKind::Procedure => LocationKind::Procedure, + }; + Some(smallvec![Location::new( + name_ref.file_id, + segment.syntax().text_range(), + kind + )]) +} + fn resolve_select_group_by_alias_or_column_ptr( db: &dyn Db, column_name_ref: InFile<&impl ast::NameLike>, @@ -3127,6 +3144,16 @@ pub(crate) fn find_from_item_in_from_clause( .find_map(|from_item| find_from_item_matching_qualifier(&from_item, qualifier)) } +fn find_visible_from_item( + from_items: impl Iterator, + name_ref: &impl ast::NameLike, + qualifier: &Name, +) -> Option { + ast_nav::visible_from_items(from_items, name_ref.syntax()) + .iter() + .find_map(|from_item| find_from_item_matching_qualifier(from_item, qualifier)) +} + // `t join u using (a) as j` fn find_join_expr_by_using_alias( join_expr: &ast::JoinExpr, @@ -3145,22 +3172,47 @@ fn find_join_expr_by_using_alias( find_join_expr_by_using_alias(&lhs, qualifier) } +fn source_from_list_items(node: &SyntaxNode) -> Option> { + if let Some(from_clause) = select_like_from_clause(node) { + return Some(from_clause.items().collect()); + } + dml_source_from_list_items(node) +} + +fn dml_source_from_list_items(stmt: &SyntaxNode) -> Option> { + if let Some(update) = ast::Update::cast(stmt.clone()) { + return Some(update.from_clause()?.items().collect()); + } + if let Some(delete) = ast::Delete::cast(stmt.clone()) { + return Some(delete.using_clause()?.items().collect()); + } + if let Some(merge) = ast::Merge::cast(stmt.clone()) { + return Some(vec![merge.using_on_clause()?.from_list_item()?]); + } + None +} + fn find_using_alias_join_expr_for_name_ref( name_ref: &impl ast::NameLike, qualifier: &Name, ) -> Option { - let select = name_ref - .syntax() - .ancestors() - .find(|a| ast::Select::can_cast(a.kind()) || ast::SelectInto::can_cast(a.kind()))?; - let from_clause = select_like_from_clause(&select)?; - from_clause - .items() - .filter_map(|item| match item { - ast::FromListItem::JoinExpr(join_expr) => Some(join_expr), - ast::FromListItem::FromItem(_) => None, - }) - .find_map(|join_expr| find_join_expr_by_using_alias(&join_expr, qualifier)) + ast_nav::ancestors_outside_own_with_clause(name_ref.syntax()).find_map(|ancestor| { + let items = source_from_list_items(&ancestor)?; + let visible = ast_nav::visible_from_items( + ast_nav::iter_from_items(items.iter().cloned()), + name_ref.syntax(), + ); + items + .into_iter() + .filter_map(|item| match item { + ast::FromListItem::JoinExpr(join_expr) => Some(join_expr), + ast::FromListItem::FromItem(_) => None, + }) + .filter_map(|join_expr| find_join_expr_by_using_alias(&join_expr, qualifier)) + .find(|join_expr| { + ast_nav::iter_join_expr(join_expr).all(|from_item| visible.contains(&from_item)) + }) + }) } fn resolve_join_using_alias_table_ptr( @@ -3220,54 +3272,15 @@ fn find_from_item_for_select_qualified_name_ref( name_ref: &impl ast::NameLike, table_name: &Name, ) -> Option { - let select = name_ref.syntax().ancestors().find(|ancestor| { - ast::Select::can_cast(ancestor.kind()) || ast::SelectInto::can_cast(ancestor.kind()) - })?; - - if let Some(from_clause) = select_like_from_clause(&select) - && let Some(from_item) = find_from_item_in_from_clause(&from_clause, table_name) - { - return Some(from_item); - } - - if let Some(lateral_from_item) = name_ref.syntax().ancestors().find_map(|ancestor| { - ast::FromItem::cast(ancestor).filter(|from_item| { - from_item - .syntax() - .children_with_tokens() - .any(|it| it.kind() == SyntaxKind::LATERAL_KW) + ast_nav::ancestors_outside_own_with_clause(name_ref.syntax()) + .filter_map(|ancestor| select_like_from_clause(&ancestor)) + .find_map(|from_clause| { + find_visible_from_item( + ast_nav::iter_from_clause(&from_clause), + name_ref, + table_name, + ) }) - }) { - let lateral_start = lateral_from_item.syntax().text_range().start(); - - for ancestor in lateral_from_item.syntax().ancestors() { - if let Some(from_clause) = ast::Select::cast(ancestor).and_then(|x| x.from_clause()) - && let Some(outer_from_item) = ast_nav::iter_from_clause(&from_clause) - .filter(|item| item.syntax().text_range().start() < lateral_start) - .find_map(|item| find_from_item_matching_qualifier(&item, table_name)) - { - return Some(outer_from_item); - } - } - } - - let inner_select_start = select.text_range().start(); - for ancestor in select.ancestors().skip(1) { - if let Some(outer_from_clause) = ast::Select::cast(ancestor).and_then(|x| x.from_clause()) { - if outer_from_clause - .syntax() - .text_range() - .contains(inner_select_start) - { - continue; - } - if let Some(from_item) = find_from_item_in_from_clause(&outer_from_clause, table_name) { - return Some(from_item); - } - } - } - - None } pub(crate) fn find_column_in_create_table( @@ -4390,6 +4403,7 @@ fn returning_target_kind( if let Some(ast::Expr::FieldExpr(field_expr)) = target.expr() && let Some(table_name) = qualified_star_table_name(&field_expr) && match_table_in_returning_clause( + target.syntax(), &table_name, stmt_table_name, alias, @@ -5353,39 +5367,26 @@ fn resolve_composite_type_from_cast_node( name::schema_and_type_name(&ty) } -fn resolve_update_table_name_ptr( +fn contains_node(container: Option, node: &SyntaxNode) -> bool { + container.is_some_and(|container| { + container + .syntax() + .text_range() + .contains_range(node.text_range()) + }) +} + +fn resolve_dml_source_table_name_ptr( db: &dyn Db, table_name_ref: InFile<&impl ast::NameLike>, + from_items: impl Iterator, + table_name: &Name, ) -> Option> { - let file = table_name_ref.file_id; - let table_name_ref = table_name_ref.value; - let table_name = Name::from_node(table_name_ref); - let update = table_name_ref - .syntax() - .ancestors() - .find_map(ast::Update::cast)?; - - if let Some(from_clause) = update.from_clause() { - for from_item in ast_nav::iter_from_clause(&from_clause) { - if let Some(result) = resolve_from_item_table_name_ptr( - db, - InFile::new(file, table_name_ref), - &from_item, - &table_name, - ) { - return Some(result); - } - } + if let Some(locations) = resolve_join_using_alias_table_ptr(table_name_ref, table_name) { + return Some(locations); } - - let path = update.relation_name()?.relation_name_ref()?.path_ref()?; - resolve_table_in_returning_clause( - db, - InFile::new(file, table_name_ref), - update.alias().and_then(|alias| alias.name()), - &path, - update.returning_clause(), - ) + let from_item = find_visible_from_item(from_items, table_name_ref.value, table_name)?; + resolve_from_item_table_name_ptr(db, table_name_ref, &from_item, table_name) } fn resolve_from_item_table_name_ptr( @@ -5447,191 +5448,271 @@ fn column_qualifier_name(column_name_ref: &impl ast::NameLike) -> Option { } } -fn resolve_update_column_ptr( +fn resolve_dml_source_column_ptr( db: &dyn Db, - column_name_ref: InFile<&impl ast::NameLike>, + file: File, + from_items: impl Iterator, + column_name_ref: &impl ast::NameLike, ) -> Option> { - let file = column_name_ref.file_id; - let column_name_ref = column_name_ref.value; - let column_name = Name::from_node(column_name_ref); - let update = column_name_ref - .syntax() - .ancestors() - .find_map(ast::Update::cast)?; + if let Some(qualifier) = column_qualifier_name(column_name_ref) { + if let Some(locations) = + resolve_join_using_alias_column_ptr(db, InFile::new(file, column_name_ref), &qualifier) + { + return Some(locations); + } + let from_item = find_visible_from_item(from_items, column_name_ref, &qualifier)?; + return resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref); + } + let results: SmallVec<[Location; 1]> = + ast_nav::visible_from_items(from_items, column_name_ref.syntax()) + .into_iter() + .filter_map(|from_item| { + resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) + }) + .flatten() + .collect(); + (!results.is_empty()).then_some(results) +} + +fn is_visible_excluded(qualifier: &Name, node: &SyntaxNode) -> bool { + *qualifier == "excluded" + && node.ancestors().any(|ancestor| { + ast::ConflictDoUpdateSet::can_cast(ancestor.kind()) + || ast::ConflictDoSelect::can_cast(ancestor.kind()) + }) +} - // The left-hand side of `set` is always a target-table column, so `from` - // tables are only considered for right-hand side expressions and predicates. +struct DmlScope { + source_items: Vec, + in_source: bool, + source_visible: bool, + target_visible: bool, + path: Option, + alias: Option, + returning_clause: Option, +} + +impl DmlScope { + fn new(stmt: &SyntaxNode, node: &SyntaxNode) -> Option { + let mut scope = if let Some(update) = ast::Update::cast(stmt.clone()) { + DmlScope { + source_items: vec![], + in_source: false, + source_visible: true, + target_visible: true, + path: update + .relation_name() + .and_then(|it| it.relation_name_ref()) + .and_then(|it| it.path_ref()), + alias: update.alias().and_then(|alias| alias.name()), + returning_clause: update.returning_clause(), + } + } else if let Some(delete) = ast::Delete::cast(stmt.clone()) { + DmlScope { + source_items: vec![], + in_source: false, + source_visible: true, + target_visible: true, + path: delete + .relation_name() + .and_then(|it| it.relation_name_ref()) + .and_then(|it| it.path_ref()), + alias: delete.alias().and_then(|alias| alias.name()), + returning_clause: delete.returning_clause(), + } + } else if let Some(merge) = ast::Merge::cast(stmt.clone()) { + let (source_visible, target_visible) = + match node.ancestors().find_map(ast::MergeWhenClause::cast) { + Some(ast::MergeWhenClause::MergeWhenNotMatchedSource(_)) => (false, true), + Some(ast::MergeWhenClause::MergeWhenNotMatchedTarget(_)) => (true, false), + Some(ast::MergeWhenClause::MergeWhenMatched(_)) | None => (true, true), + }; + DmlScope { + source_items: vec![], + in_source: false, + source_visible, + target_visible, + path: merge + .table_relation_name() + .and_then(|it| it.table_name_ref()) + .and_then(|it| it.path_ref()), + alias: merge.alias().and_then(|alias| alias.name()), + returning_clause: merge.returning_clause(), + } + } else if let Some(insert) = ast::Insert::cast(stmt.clone()) { + // The insert target is only visible from `on conflict` and + // `returning`, not from the rows being inserted + DmlScope { + source_items: vec![], + in_source: false, + source_visible: false, + target_visible: contains_node(insert.on_conflict_clause(), node) + || contains_node(insert.returning_clause(), node), + path: insert.relation_name_ref().and_then(|it| it.path_ref()), + alias: insert.alias().and_then(|alias| alias.name()), + returning_clause: insert.returning_clause(), + } + } else { + return None; + }; + let source_list_items = dml_source_from_list_items(stmt).unwrap_or_default(); + scope.in_source = source_list_items + .iter() + .any(|item| item.syntax().text_range().contains_range(node.text_range())); + scope.source_items = ast_nav::iter_from_items(source_list_items.into_iter()).collect(); + Some(scope) + } + + fn enclosing(node: &SyntaxNode) -> Option { + ast_nav::ancestors_outside_own_with_clause(node) + .find_map(|ancestor| DmlScope::new(&ancestor, node)) + } + + fn qualifier_matches_target(&self, node: &SyntaxNode, qualifier: &Name) -> Option { + let (_, stmt_table_name) = name::schema_and_name_path(self.path.as_ref()?)?; + Some( + match_table_in_returning_clause( + node, + qualifier, + &stmt_table_name, + self.alias.as_ref(), + self.returning_clause.as_ref(), + ) + .is_some(), + ) + } +} + +// The left-hand side of `set` and an insert column list always name +// target-table columns +fn is_target_definition(node: &SyntaxNode) -> bool { let mut in_set_clause = false; let mut in_set_expr = false; - for ancestor in column_name_ref.syntax().ancestors() { + let mut in_column_target_list = false; + for ancestor in node.ancestors() { if ast::SetClause::can_cast(ancestor.kind()) { in_set_clause = true; } if ast::SetExpr::can_cast(ancestor.kind()) { in_set_expr = true; } - } - let is_set_target = in_set_clause && !in_set_expr; - - // `update t set a = b from u` - if !is_set_target && let Some(from_clause) = update.from_clause() { - for from_item in ast_nav::iter_from_clause(&from_clause) { - if let Some(result) = - resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) - { - return Some(result); - } + if ast::ColumnTargetList::can_cast(ancestor.kind()) { + in_column_target_list = true; } } - - // `update t set a = b` - let path = update.relation_name()?.relation_name_ref()?.path_ref()?; - - if let Some(qualifier) = column_qualifier_name(column_name_ref) { - let (_, stmt_table_name) = name::schema_and_name_path(&path)?; - match_table_in_returning_clause( - &qualifier, - &stmt_table_name, - update.alias().and_then(|alias| alias.name()).as_ref(), - update.returning_clause().as_ref(), - )?; - } - - resolve_column_for_path(db, InFile::new(file, &path), column_name).or_else(|| { - (!is_set_target) - .then(|| resolve_enclosing_function_param(InFile::new(file, column_name_ref))) - .flatten() - }) + (in_set_clause && !in_set_expr) || in_column_target_list } -fn resolve_delete_column_ptr( +fn resolve_dml_column_ptr( db: &dyn Db, column_name_ref: InFile<&impl ast::NameLike>, + scope: DmlScope, ) -> Option> { let file = column_name_ref.file_id; let column_name_ref = column_name_ref.value; - let column_name = Name::from_node(column_name_ref); - let delete = column_name_ref - .syntax() - .ancestors() - .find_map(ast::Delete::cast)?; + let param = || resolve_enclosing_function_param(InFile::new(file, column_name_ref)); + let qualifier = column_qualifier_name(column_name_ref); + let is_target_definition = is_target_definition(column_name_ref.syntax()); - if let Some(using_clause) = delete.using_clause() { - for from_item in ast_nav::iter_from_items(using_clause.items()) { - if let Some(ptr) = - resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) - { - return Some(ptr); - } - } + let source_column = if scope.source_visible && !is_target_definition { + resolve_dml_source_column_ptr( + db, + file, + scope.source_items.iter().cloned(), + column_name_ref, + ) + } else { + None + }; + if source_column.is_some() && (scope.in_source || qualifier.is_some()) { + return source_column; } - let path = delete.relation_name()?.relation_name_ref()?.path_ref()?; + let path = scope.path.as_ref()?; - if let Some(qualifier) = column_qualifier_name(column_name_ref) { - let (_, stmt_table_name) = name::schema_and_name_path(&path)?; - match_table_in_returning_clause( - &qualifier, - &stmt_table_name, - delete.alias().and_then(|alias| alias.name()).as_ref(), - delete.returning_clause().as_ref(), - )?; + if !scope.target_visible && !is_target_definition { + return source_column.or_else(param); } - resolve_column_for_path(db, InFile::new(file, &path), column_name) - .or_else(|| resolve_enclosing_function_param(InFile::new(file, column_name_ref))) -} - -fn resolve_delete_table_name_ptr( - db: &dyn Db, - table_name_ref: InFile<&impl ast::NameLike>, -) -> Option> { - let file = table_name_ref.file_id; - let table_name_ref = table_name_ref.value; - let table_name = Name::from_node(table_name_ref); - let delete = table_name_ref - .syntax() - .ancestors() - .find_map(ast::Delete::cast)?; + if let Some(qualifier) = &qualifier + && !is_visible_excluded(qualifier, column_name_ref.syntax()) + && !scope.qualifier_matches_target(column_name_ref.syntax(), qualifier)? + { + return param(); + } - if let Some(using_clause) = delete.using_clause() { - for from_item in ast_nav::iter_from_items(using_clause.items()) { - if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) { - if Name::from_node(&alias_name) == table_name { - return Some(smallvec![Location::new( - file, - alias_name.syntax().text_range(), - LocationKind::Table - )]); - } - } else if let ast::FromItem::RelationFromItem(relation) = &from_item - && let Some(item_name_ref) = relation.name_ref() - { - let item_name = Name::from_node(&item_name_ref); - if item_name == table_name { - let position = table_name_ref.syntax().text_range().start(); - let schemas = bind(db, file).resolved_schemas(position, None); - let (ptr, kind) = - resolve_table_like(db, Some(table_name_ref), &item_name, &schemas, file)?; - return Some(smallvec![Location::new(file, ptr.text_range(), kind)]); - } - } + let target_column = resolve_column_for_path( + db, + InFile::new(file, path), + Name::from_node(column_name_ref), + ); + // `invalid reference to FROM-clause entry for table "t"` + if target_column.is_some() && scope.in_source { + return None; + } + if let Some(mut target) = target_column { + // An unqualified column present in both the target and a source is + // ambiguous, so return both. + if let Some(source) = source_column { + target.extend(source); } + return Some(target); + } + if let Some(source) = source_column { + return Some(source); } - let path = delete.relation_name()?.relation_name_ref()?.path_ref()?; - resolve_table_in_returning_clause( - db, - InFile::new(file, table_name_ref), - delete.alias().and_then(|alias| alias.name()), - &path, - delete.returning_clause(), - ) + if is_target_definition { + return None; + } + if qualifier.is_none() { + return resolve_dml_table_name_ptr(db, InFile::new(file, column_name_ref), scope) + .or_else(param); + } + param() } -fn resolve_merge_column_ptr( +fn resolve_dml_table_name_ptr( db: &dyn Db, - column_name_ref: InFile<&impl ast::NameLike>, + table_name_ref: InFile<&impl ast::NameLike>, + scope: DmlScope, ) -> Option> { - let file = column_name_ref.file_id; - let column_name_ref = column_name_ref.value; - let column_name = Name::from_node(column_name_ref); - let merge = column_name_ref - .syntax() - .ancestors() - .find_map(ast::Merge::cast)?; + let file = table_name_ref.file_id; + let table_name = Name::from_node(table_name_ref.value); - let mut in_set_clause = false; - let mut in_set_expr = false; - let mut in_insert_column_list = false; - for ancestor in column_name_ref.syntax().ancestors() { - if ast::SetClause::can_cast(ancestor.kind()) { - in_set_clause = true; - } - if ast::SetExpr::can_cast(ancestor.kind()) { - in_set_expr = true; - } - if ast::ColumnTargetList::can_cast(ancestor.kind()) { - in_insert_column_list = true; - } + if scope.source_visible + && !scope.source_items.is_empty() + && let Some(result) = resolve_dml_source_table_name_ptr( + db, + table_name_ref, + scope.source_items.into_iter(), + &table_name, + ) + { + return Some(result); } - let is_set_target = in_set_clause && !in_set_expr; - if !is_set_target - && !in_insert_column_list - && let Some(from_item) = ast_nav::merge_using_from_item(&merge) - && let Some(ptr) = - resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) - { - return Some(ptr); + if scope.in_source || !scope.target_visible { + return resolve_enclosing_routine_name_ptr(table_name_ref); } - let path = merge.table_relation_name()?.table_name_ref()?.path_ref()?; - resolve_column_for_path(db, InFile::new(file, &path), column_name).or_else(|| { - (!is_set_target && !in_insert_column_list) - .then(|| resolve_enclosing_function_param(InFile::new(file, column_name_ref))) - .flatten() - }) + let path = scope.path?; + + if is_visible_excluded(&table_name, table_name_ref.value.syntax()) { + let (schema, stmt_table_name) = name::schema_and_name_path(&path)?; + let position = table_name_ref.value.syntax().text_range().start(); + let schemas = bind(db, file).resolved_schemas(position, schema.as_ref()); + let (ptr, kind) = resolve_view_or_table(db, &stmt_table_name, &schemas, file)?; + return Some(smallvec![Location::new(file, ptr.text_range(), kind)]); + } + + resolve_table_in_returning_clause( + db, + table_name_ref, + scope.alias, + &path, + scope.returning_clause, + ) } // TODO: I think we could use trait(s) here to simplify this and have the @@ -5648,12 +5729,15 @@ fn resolve_table_in_returning_clause( let table_name = Name::from_node(table_name_ref); let (schema, stmt_table_name) = name::schema_and_name_path(path)?; - let matched = match_table_in_returning_clause( + let Some(matched) = match_table_in_returning_clause( + table_name_ref.syntax(), &table_name, &stmt_table_name, alias.as_ref(), returning_clause.as_ref(), - )?; + ) else { + return resolve_enclosing_routine_name_ptr(InFile::new(file, table_name_ref)); + }; let position = table_name_ref.syntax().text_range().start(); let schemas = bind(db, file).resolved_schemas(position, schema.as_ref()); @@ -5688,54 +5772,6 @@ fn resolve_table_in_returning_clause( } } -fn resolve_merge_table_name_ptr( - db: &dyn Db, - table_name_ref: InFile<&impl ast::NameLike>, -) -> Option> { - let file = table_name_ref.file_id; - let table_name_ref = table_name_ref.value; - let table_name = Name::from_node(table_name_ref); - let merge = table_name_ref - .syntax() - .ancestors() - .find_map(ast::Merge::cast)?; - - let path = merge.table_relation_name()?.table_name_ref()?.path_ref()?; - - // Check USING clause for the source table - MERGE-specific. - // A source alias hides the underlying table name. - if let Some(from_item) = ast_nav::merge_using_from_item(&merge) { - if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) { - if Name::from_node(&alias_name) == table_name { - return Some(smallvec![Location::new( - file, - alias_name.syntax().text_range(), - LocationKind::Table - )]); - } - } else if let ast::FromItem::RelationFromItem(relation) = &from_item - && let Some(item_name_ref) = relation.name_ref() - { - let item_name = Name::from_node(&item_name_ref); - if item_name == table_name { - let position = table_name_ref.syntax().text_range().start(); - let schemas = bind(db, file).resolved_schemas(position, None); - let (ptr, kind) = - resolve_table_like(db, Some(table_name_ref), &item_name, &schemas, file)?; - return Some(smallvec![Location::new(file, ptr.text_range(), kind)]); - } - } - } - - resolve_table_in_returning_clause( - db, - InFile::new(file, table_name_ref), - merge.alias().and_then(|alias| alias.name()), - &path, - merge.returning_clause(), - ) -} - fn find_param_in_func_def( db: &dyn Db, function_ptr: InFile,