From 5c0f9d3ecacf2641d6da9fa0cfcbe8364df618b4 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 14 Jan 2026 12:11:21 -0500 Subject: [PATCH] ide: goto def/hover/document symbol support for prepare stmt --- crates/squawk_ide/src/binder.rs | 20 +++++++ crates/squawk_ide/src/classify.rs | 8 +++ crates/squawk_ide/src/document_symbols.rs | 39 +++++++++++++ crates/squawk_ide/src/goto_definition.rs | 28 +++++++++ crates/squawk_ide/src/hover.rs | 69 +++++++++++++++++++++++ crates/squawk_ide/src/resolve.rs | 11 ++++ crates/squawk_ide/src/symbols.rs | 1 + crates/squawk_server/src/lib.rs | 1 + crates/squawk_wasm/src/lib.rs | 3 + 9 files changed, 180 insertions(+) diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index c33b79dd..74779101 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -193,6 +193,7 @@ fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) { ast::Stmt::CreateServer(create_server) => bind_create_server(b, create_server), ast::Stmt::CreateExtension(create_extension) => bind_create_extension(b, create_extension), ast::Stmt::Declare(declare) => bind_declare_cursor(b, declare), + ast::Stmt::Prepare(prepare) => bind_prepare(b, prepare), ast::Stmt::Set(set) => bind_set(b, set), _ => {} } @@ -568,6 +569,25 @@ fn bind_declare_cursor(b: &mut Binder, declare: ast::Declare) { b.scopes[root].insert(cursor_name, cursor_id); } +fn bind_prepare(b: &mut Binder, prepare: ast::Prepare) { + let Some(name) = prepare.name() else { + return; + }; + + let statement_name = Name::from_node(&name); + let name_ptr = SyntaxNodePtr::new(name.syntax()); + + let statement_id = b.symbols.alloc(Symbol { + kind: SymbolKind::PreparedStatement, + ptr: name_ptr, + schema: None, + params: None, + }); + + let root = b.root_scope(); + b.scopes[root].insert(statement_name, statement_id); +} + fn item_name(path: &ast::Path) -> Option { let segment = path.segment()?; diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index 15aa503f..cc81a5e1 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -93,6 +93,7 @@ pub(crate) enum NameRefClass { AttachPartition, NamedArgParameter, Cursor, + PreparedStatement, } fn is_special_fn(kind: SyntaxKind) -> bool { @@ -362,6 +363,9 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option { return Some(NameRefClass::Cursor); } + if ast::Execute::can_cast(ancestor.kind()) || ast::Deallocate::can_cast(ancestor.kind()) { + return Some(NameRefClass::PreparedStatement); + } if ast::DropTable::can_cast(ancestor.kind()) { return Some(NameRefClass::DropTable); } @@ -724,6 +728,7 @@ pub(crate) enum NameClass { }, CreateView(ast::CreateView), DeclareCursor(ast::Declare), + PrepareStatement(ast::Prepare), } pub(crate) fn classify_name(name: &ast::Name) -> Option { @@ -790,6 +795,9 @@ pub(crate) fn classify_name(name: &ast::Name) -> Option { if let Some(declare) = ast::Declare::cast(ancestor.clone()) { return Some(NameClass::DeclareCursor(declare)); } + if let Some(prepare) = ast::Prepare::cast(ancestor.clone()) { + return Some(NameClass::PrepareStatement(prepare)); + } } if let Some(with_table) = with_table_parent { diff --git a/crates/squawk_ide/src/document_symbols.rs b/crates/squawk_ide/src/document_symbols.rs index 0791f4e2..a6d76fbb 100644 --- a/crates/squawk_ide/src/document_symbols.rs +++ b/crates/squawk_ide/src/document_symbols.rs @@ -21,6 +21,7 @@ pub enum DocumentSymbolKind { Column, Variant, Cursor, + PreparedStatement, } #[derive(Debug)] @@ -87,6 +88,11 @@ pub fn document_symbols(file: &ast::SourceFile) -> Vec { symbols.push(symbol); } } + ast::Stmt::Prepare(prepare) => { + if let Some(symbol) = create_prepare_symbol(prepare) { + symbols.push(symbol); + } + } ast::Stmt::Select(select) => { symbols.extend(cte_table_symbols(select)); } @@ -444,6 +450,23 @@ fn create_declare_cursor_symbol(declare: ast::Declare) -> Option }) } +fn create_prepare_symbol(prepare: ast::Prepare) -> Option { + let name_node = prepare.name()?; + let name = name_node.syntax().text().to_string(); + + let full_range = prepare.syntax().text_range(); + let focus_range = name_node.syntax().text_range(); + + Some(DocumentSymbol { + name, + detail: None, + kind: DocumentSymbolKind::PreparedStatement, + full_range, + focus_range, + children: vec![], + }) +} + #[cfg(test)] mod tests { use super::*; @@ -494,6 +517,7 @@ mod tests { DocumentSymbolKind::Column => "column", DocumentSymbolKind::Variant => "variant", DocumentSymbolKind::Cursor => "cursor", + DocumentSymbolKind::PreparedStatement => "prepared statement", }; let title = if let Some(detail) = &symbol.detail { @@ -906,6 +930,21 @@ declare c scroll cursor for select * from t; "); } + #[test] + fn prepare_statement() { + assert_snapshot!(symbols(" +prepare stmt as select 1; +"), @r" + info: prepared statement: stmt + ╭▸ + 2 │ prepare stmt as select 1; + │ ┬───────┯━━━──────────── + │ │ │ + │ │ focus range + ╰╴full range + "); + } + #[test] fn empty_file() { symbols_not_found("") diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 9a247d7a..c13c556d 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -344,6 +344,34 @@ move forward 10 from c$0; "); } + #[test] + fn goto_execute_prepared_statement() { + assert_snapshot!(goto(" +prepare stmt as select 1; +execute stmt$0; +"), @r" + ╭▸ + 2 │ prepare stmt as select 1; + │ ──── 2. destination + 3 │ execute stmt; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_deallocate_prepared_statement() { + assert_snapshot!(goto(" +prepare stmt as select 1; +deallocate stmt$0; +"), @r" + ╭▸ + 2 │ prepare stmt as select 1; + │ ──── 2. destination + 3 │ deallocate stmt; + ╰╴ ─ 1. source + "); + } + #[test] fn goto_delete_where_current_of_cursor() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 14bf5f98..9c0ebed2 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -169,6 +169,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { NameRefClass::Cursor => { return hover_cursor(root, &name_ref, &binder); } + NameRefClass::PreparedStatement => { + return hover_prepared_statement(root, &name_ref, &binder); + } } } @@ -225,6 +228,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { NameClass::DeclareCursor(declare) => { return format_declare_cursor(&declare); } + NameClass::PrepareStatement(prepare) => { + return format_prepare(&prepare); + } } } @@ -794,6 +800,21 @@ fn hover_cursor( format_declare_cursor(&declare) } +fn hover_prepared_statement( + root: &SyntaxNode, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let statement_ptr = resolve::resolve_name_ref(binder, root, name_ref)? + .into_iter() + .next()?; + let statement_name_node = statement_ptr.to_node(root); + let prepare = statement_name_node + .ancestors() + .find_map(ast::Prepare::cast)?; + format_prepare(&prepare) +} + fn hover_type( root: &SyntaxNode, name_ref: &ast::NameRef, @@ -820,6 +841,16 @@ fn format_declare_cursor(declare: &ast::Declare) -> Option { )) } +fn format_prepare(prepare: &ast::Prepare) -> Option { + let name = prepare.name()?; + let stmt = prepare.preparable_stmt()?; + Some(format!( + "prepare {} as {}", + name.syntax().text(), + stmt.syntax().text() + )) +} + fn format_create_table( create_table: &impl ast::HasCreateTable, binder: &binder::Binder, @@ -4013,4 +4044,42 @@ move forward 10 from c$0; ╰╴ ─ hover "); } + + #[test] + fn hover_on_prepare_statement() { + assert_snapshot!(check_hover(" +prepare stmt$0 as select 1; +"), @r" + hover: prepare stmt as select 1 + ╭▸ + 2 │ prepare stmt as select 1; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_on_execute_prepared_statement() { + assert_snapshot!(check_hover(" +prepare stmt as select 1; +execute stmt$0; +"), @r" + hover: prepare stmt as select 1 + ╭▸ + 3 │ execute stmt; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_on_deallocate_prepared_statement() { + assert_snapshot!(check_hover(" +prepare stmt as select 1; +deallocate stmt$0; +"), @r" + hover: prepare stmt as select 1 + ╭▸ + 3 │ deallocate stmt; + ╰╴ ─ hover + "); + } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index da0a5242..6727712d 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -64,6 +64,10 @@ pub(crate) fn resolve_name_ref( .lookup(&cursor_name, SymbolKind::Cursor) .map(|ptr| smallvec![ptr]) } + NameRefClass::PreparedStatement => { + let statement_name = Name::from_node(name_ref); + resolve_prepared_statement_name_ptr(binder, &statement_name).map(|ptr| smallvec![ptr]) + } NameRefClass::SelectFromTable | NameRefClass::UpdateFromTable | NameRefClass::MergeUsingTable @@ -492,6 +496,13 @@ fn resolve_tablespace_name_ptr(binder: &Binder, tablespace_name: &Name) -> Optio binder.lookup(tablespace_name, SymbolKind::Tablespace) } +fn resolve_prepared_statement_name_ptr( + binder: &Binder, + statement_name: &Name, +) -> Option { + binder.lookup(statement_name, SymbolKind::PreparedStatement) +} + fn resolve_database_name_ptr(binder: &Binder, database_name: &Name) -> Option { binder.lookup(database_name, SymbolKind::Database) } diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index 79c06f2f..f0ae920a 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -54,6 +54,7 @@ pub(crate) enum SymbolKind { View, Sequence, Cursor, + PreparedStatement, Tablespace, Database, Server, diff --git a/crates/squawk_server/src/lib.rs b/crates/squawk_server/src/lib.rs index 840e7310..ae0bb337 100644 --- a/crates/squawk_server/src/lib.rs +++ b/crates/squawk_server/src/lib.rs @@ -378,6 +378,7 @@ fn handle_document_symbol( DocumentSymbolKind::Column => SymbolKind::FIELD, DocumentSymbolKind::Variant => SymbolKind::ENUM_MEMBER, DocumentSymbolKind::Cursor => SymbolKind::VARIABLE, + DocumentSymbolKind::PreparedStatement => SymbolKind::VARIABLE, }, tags: None, range, diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index 93de7b90..01c7b9a2 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -411,6 +411,9 @@ fn convert_document_symbol( squawk_ide::document_symbols::DocumentSymbolKind::Column => "column", squawk_ide::document_symbols::DocumentSymbolKind::Variant => "variant", squawk_ide::document_symbols::DocumentSymbolKind::Cursor => "cursor", + squawk_ide::document_symbols::DocumentSymbolKind::PreparedStatement => { + "prepared_statement" + } } .to_string(), start_line: full_start_wide.line,