diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index 5454f258..1cd1a38f 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -56,6 +56,7 @@ pub(crate) enum NameRefClass { UpdateColumn, UpdateQualifiedColumnTable, View, + Window, } fn is_special_fn(kind: SyntaxKind) -> bool { @@ -603,6 +604,16 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { if ast::ArgList::can_cast(ancestor.kind()) { in_arg_list = true; } + if ast::OverClause::can_cast(ancestor.kind()) && !in_function_name { + if node + .parent() + .is_some_and(|parent| ast::OverClause::can_cast(parent.kind())) + { + return Some(NameRefClass::Window); + } + + return Some(NameRefClass::SelectColumn); + } if ast::CallExpr::can_cast(ancestor.kind()) { if !in_arg_list { in_function_name = true; @@ -934,6 +945,9 @@ pub(crate) fn classify_def_node(def_node: &SyntaxNode) -> Option { } return Some(NameRefClass::FromTable); } + if ast::WindowDef::can_cast(ancestor.kind()) { + return Some(NameRefClass::Window); + } if ast::AsName::can_cast(ancestor.kind()) || ast::ParenSelect::can_cast(ancestor.kind()) || ast::Values::can_cast(ancestor.kind()) diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 1e27d593..97b3bd2d 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -9001,4 +9001,122 @@ create operator ||| (leftarg = int, rightarg = int, procedure = f$0); ╰╴ ─ 1. source "); } + + #[test] + fn goto_cte_window_partition_column_from_create_table_if_not_exists() { + assert_snapshot!(goto(" +create table t ( + id bigint primary key, + group_col text not null, + update_date date not null +); + +with row_number_added as ( + select + *, + row_number() over ( + partition by group_col$0 + order by update_date desc + ) as rn + from t +) +select * from row_number_added +"), @" + ╭▸ + 4 │ group_col text not null, + │ ───────── 2. destination + ‡ + 12 │ partition by group_col + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_cte_window_order_column_from_create_table_if_not_exists() { + assert_snapshot!(goto(" +create table t ( + id bigint primary key, + group_col text not null, + update_date date not null +); + +with row_number_added as ( + select + *, + row_number() over ( + partition by group_col + order by update_date$0 desc + ) as rn + from t +) +select * from row_number_added +"), @" + ╭▸ + 5 │ update_date date not null + │ ─────────── 2. destination + ‡ + 13 │ order by update_date desc + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_cte_window_partition_function_call_from_create_table() { + assert_snapshot!(goto(" +create function length(text) returns int language internal; + +create table t ( + id bigint primary key, + group_col text not null, + update_date date not null +); + +with row_number_added as ( + select + *, + row_number() over ( + partition by length$0(group_col) + order by update_date$0 desc + ) as rn + from t +) +select * from row_number_added +"), @" + ╭▸ + 2 │ create function length(text) returns int language internal; + │ ────── 2. destination + ‡ + 14 │ partition by length(group_col) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_select_window_def_reuse() { + assert_snapshot!(goto(" +create table tbl ( + id bigint primary key, + group_col text not null, + update_date date not null, + value text +); +select + id, + group_col, + row_number() over w as rn, + lag(value) over w$0 as prev_value +from tbl +window w as ( + partition by group_col + order by update_date desc +); +"), @r" + ╭▸ + 12 │ lag(value) over w as prev_value + │ ─ 1. source + 13 │ from tbl + 14 │ window w as ( + ╰╴ ─ 2. destination + "); + } } diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 68bd6440..301978f9 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -227,6 +227,7 @@ fn hover_name_ref( NameRefClass::Cursor => hover_cursor(root, name_ref, binder), NameRefClass::PreparedStatement => hover_prepared_statement(root, name_ref, binder), NameRefClass::Channel => hover_channel(root, name_ref, binder), + NameRefClass::Window => hover_window(root, name_ref, binder), } } @@ -1183,6 +1184,22 @@ fn hover_channel( format_listen(&listen) } +fn hover_window( + root: &SyntaxNode, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let window_ptr = resolve::resolve_name_ref_ptrs(binder, root, name_ref)? + .into_iter() + .next()?; + let window_name_node = window_ptr.to_node(root); + let window_def = window_name_node + .ancestors() + .find_map(ast::WindowDef::cast)?; + + Some(format!("window {}", window_def.syntax().text())) +} + fn hover_type( root: &SyntaxNode, name_ref: &ast::NameRef, @@ -4491,6 +4508,36 @@ select a$0 from part_2026_01_02; "); } + #[test] + fn hover_select_window_def_reuse() { + assert_snapshot!(check_hover(" +create table tbl ( + id bigint primary key, + group_col text not null, + update_date date not null, + value text +); +select + id, + group_col, + row_number() over w as rn, + lag(value) over w$0 as prev_value +from tbl +window w as ( + partition by group_col + order by update_date desc +); +"), @r" +hover: window w as ( + partition by group_col + order by update_date desc + ) + ╭▸ +12 │ lag(value) over w as prev_value + ╰╴ ─ hover + "); + } + #[test] fn hover_create_table_like_multi_star() { assert_snapshot!(check_hover(" diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index fdcba651..4a6df5a3 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -134,6 +134,7 @@ pub(crate) fn resolve_name_ref_ptrs( let position = name_ref.syntax().text_range().start(); resolve_view_name_ptr(binder, &view_name, &schema, position).map(|ptr| smallvec![ptr]) } + NameRefClass::Window => resolve_window_name_ptr(name_ref).map(|ptr| smallvec![ptr]), NameRefClass::Sequence => { let (sequence_name, schema) = extract_table_schema_from_name_ref(name_ref)?; let position = name_ref.syntax().text_range().start(); @@ -1594,6 +1595,22 @@ fn resolve_select_column_ptr( None } +fn resolve_window_name_ptr(name_ref: &ast::NameRef) -> Option { + let window_name = Name::from_node(name_ref); + let select = name_ref.syntax().ancestors().find_map(ast::Select::cast)?; + let window_clause = select.window_clause()?; + + for window_def in window_clause.window_defs() { + if let Some(name) = window_def.name() + && Name::from_node(&name) == window_name + { + return Some(SyntaxNodePtr::new(name.syntax())); + } + } + + None +} + fn resolve_delete_column_ptr( binder: &Binder, root: &SyntaxNode,