diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index aacaa50e..8a3017b9 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -283,6 +283,9 @@ fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) { ast::Stmt::Listen(listen) => bind_listen(b, listen), ast::Stmt::Set(set) => bind_set(b, set), ast::Stmt::CreatePolicy(create_policy) => bind_create_policy(b, create_policy), + ast::Stmt::CreatePropertyGraph(create_property_graph) => { + bind_create_property_graph(b, create_property_graph) + } _ => (), } } @@ -773,6 +776,30 @@ fn bind_create_policy(b: &mut Binder, create_policy: ast::CreatePolicy) { b.scopes[root].insert(policy_name, policy_id); } +fn bind_create_property_graph(b: &mut Binder, create_property_graph: ast::CreatePropertyGraph) { + let Some(path) = create_property_graph.path() else { + return; + }; + let Some(property_graph_name) = item_name(&path) else { + return; + }; + let name_ptr = path_to_ptr(&path); + let Some(schema) = schema_name(b, &path, false) else { + return; + }; + + let property_graph_id = b.symbols.alloc(Symbol { + kind: SymbolKind::PropertyGraph, + ptr: name_ptr, + schema: Some(schema), + params: None, + table: None, + }); + + let root = b.root_scope(); + b.scopes[root].insert(property_graph_name, property_graph_id); +} + fn bind_create_event_trigger(b: &mut Binder, create_event_trigger: ast::CreateEventTrigger) { let Some(name) = create_event_trigger.name() else { return; diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index 1a8d594a..ece1f496 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -39,6 +39,7 @@ pub(crate) enum NameRefClass { PreparedStatement, Procedure, ProcedureCall, + PropertyGraph, QualifiedColumn, Role, Routine, @@ -310,6 +311,16 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { return Some(NameRefClass::Schema); } + if let Some(parent) = node.parent() + && let Some(path) = ast::PathSegment::cast(parent) + .and_then(|p| p.syntax().parent().and_then(ast::Path::cast)) + && let Some(stmt_parent) = path.syntax().parent() + && (ast::AlterPropertyGraph::can_cast(stmt_parent.kind()) + || ast::DropPropertyGraph::can_cast(stmt_parent.kind())) + { + return Some(NameRefClass::PropertyGraph); + } + // Check for function/procedure reference in CREATE OPERATOR / CREATE AGGREGATE // before the type check for ancestor in node.ancestors() { @@ -373,6 +384,10 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { || ast::Table::can_cast(ancestor.kind()) || ast::Inherits::can_cast(ancestor.kind()) || ast::PartitionOf::can_cast(ancestor.kind()) + || ast::VertexTableDef::can_cast(ancestor.kind()) + || ast::EdgeTableDef::can_cast(ancestor.kind()) + || ast::SourceVertexTable::can_cast(ancestor.kind()) + || ast::DestVertexTable::can_cast(ancestor.kind()) { return Some(NameRefClass::Table); } @@ -754,6 +769,7 @@ pub(crate) enum NameClass { name: ast::Name, }, CreateView(ast::CreateView), + CreatePropertyGraph(ast::CreatePropertyGraph), DeclareCursor(ast::Declare), PrepareStatement(ast::Prepare), Listen(ast::Listen), @@ -829,6 +845,9 @@ pub(crate) fn classify_name(name: &ast::Name) -> Option { } return Some(NameClass::CreateView(create_view)); } + if let Some(create_property_graph) = ast::CreatePropertyGraph::cast(ancestor.clone()) { + return Some(NameClass::CreatePropertyGraph(create_property_graph)); + } if let Some(declare) = ast::Declare::cast(ancestor.clone()) { return Some(NameClass::DeclareCursor(declare)); } @@ -931,6 +950,9 @@ pub(crate) fn classify_def_node(def_node: &SyntaxNode) -> Option { if ast::CreatePolicy::can_cast(ancestor.kind()) { return Some(NameRefClass::Policy); } + if ast::CreatePropertyGraph::can_cast(ancestor.kind()) { + return Some(NameRefClass::PropertyGraph); + } if ast::Declare::can_cast(ancestor.kind()) { return Some(NameRefClass::Cursor); } diff --git a/crates/squawk_ide/src/document_symbols.rs b/crates/squawk_ide/src/document_symbols.rs index 341642fd..1feac484 100644 --- a/crates/squawk_ide/src/document_symbols.rs +++ b/crates/squawk_ide/src/document_symbols.rs @@ -21,6 +21,7 @@ pub enum DocumentSymbolKind { EventTrigger, Role, Policy, + PropertyGraph, Type, Enum, Index, @@ -152,6 +153,11 @@ pub fn document_symbols(db: &dyn Db, file: File) -> Vec { symbols.push(symbol); } } + ast::Stmt::CreatePropertyGraph(create_property_graph) => { + if let Some(symbol) = create_property_graph_symbol(create_property_graph) { + symbols.push(symbol); + } + } ast::Stmt::CreateType(create_type) => { if let Some(symbol) = create_type_symbol(&binder, create_type) { symbols.push(symbol); @@ -679,6 +685,27 @@ fn create_policy_symbol(create_policy: ast::CreatePolicy) -> Option Option { + let path = create_property_graph.path()?; + let name_node = path.segment()?.name()?; + + let name = path.syntax().text().to_string(); + + let full_range = create_property_graph.syntax().text_range(); + let focus_range = name_node.syntax().text_range(); + + Some(DocumentSymbol { + name, + detail: None, + kind: DocumentSymbolKind::PropertyGraph, + full_range, + focus_range, + children: vec![], + }) +} + fn create_type_symbol( binder: &binder::Binder, create_type: ast::CreateType, @@ -892,6 +919,7 @@ mod tests { DocumentSymbolKind::EventTrigger => "event trigger", DocumentSymbolKind::Role => "role", DocumentSymbolKind::Policy => "policy", + DocumentSymbolKind::PropertyGraph => "property graph", DocumentSymbolKind::Type => "type", DocumentSymbolKind::Enum => "enum", DocumentSymbolKind::Index => "index", @@ -1395,6 +1423,23 @@ create function my_schema.hello() returns void as $$ select 1; $$ language sql; "); } + #[test] + fn create_property_graph() { + assert_snapshot!(symbols(" +create property graph foo.bar + vertex tables (t key (a) no properties); +"), @" + info: property graph: foo.bar + ╭▸ + 2 │ create property graph foo.bar + │ │ ━━━ focus range + │ ┌─┘ + │ │ + 3 │ │ vertex tables (t key (a) no properties); + ╰╴└─────────────────────────────────────────┘ full range + "); + } + #[test] fn create_type() { assert_snapshot!( diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index 3d4fc059..3859792c 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -146,6 +146,7 @@ pub enum LocationKind { Policy, PreparedStatement, Procedure, + PropertyGraph, Role, Schema, Sequence, @@ -188,6 +189,7 @@ impl From for LocationKind { NameRefClass::NamedArgParameter => LocationKind::NamedArgParameter, NameRefClass::Policy => LocationKind::Policy, NameRefClass::PreparedStatement => LocationKind::PreparedStatement, + NameRefClass::PropertyGraph => LocationKind::PropertyGraph, NameRefClass::Role => LocationKind::Role, NameRefClass::Schema => LocationKind::Schema, NameRefClass::Sequence => LocationKind::Sequence, @@ -9343,4 +9345,69 @@ select '10'::dec$0; ╰╴ ─ 1. source "); } + + #[test] + fn goto_create_property_graph() { + assert_snapshot!(goto(" +create table buzz.boo(a int, b int); +create property graph foo.bar + vertex tables (buzz.boo$0 key (a, b) no properties) + edge tables (foo.bar key (x, y) + source key (a, b) references k (t, y) + destination key (q, t) references a (r, j) + properties all columns); +"), @" + ╭▸ + 2 │ create table buzz.boo(a int, b int); + │ ─── 2. destination + 3 │ create property graph foo.bar + 4 │ vertex tables (buzz.boo key (a, b) no properties) + ╰╴ ─ 1. source + "); + + assert_snapshot!(goto(" +create table foo.bar(x int, y int); +create property graph g + vertex tables (boo key (a, b) no properties) + edge tables (foo.bar$0 key (x, y) + source key (a, b) references k (t, y) + destination key (q, t) references a (r, j) + properties all columns); +"), @" + ╭▸ + 2 │ create table foo.bar(x int, y int); + │ ─── 2. destination + ‡ + 5 │ edge tables (foo.bar key (x, y) + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_property_graph() { + assert_snapshot!(goto(" +create property graph foo.bar vertex tables (t key (a) no properties); +drop property graph foo.ba$0r; +"), @" + ╭▸ + 2 │ create property graph foo.bar vertex tables (t key (a) no properties); + │ ─── 2. destination + 3 │ drop property graph foo.bar; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_alter_property_graph() { + assert_snapshot!(goto(" +create property graph foo.bar vertex tables (t key (a) no properties); +alter property graph foo.ba$0r rename to baz; +"), @" + ╭▸ + 2 │ create property graph foo.bar vertex tables (t key (a) no properties); + │ ─── 2. destination + 3 │ alter property graph foo.bar rename to baz; + ╰╴ ─ 1. source + "); + } } diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index f72d931f..169a7606 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -191,6 +191,9 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option { NameClass::CreateView(create_view) => { return format_create_view(&create_view, &binder); } + NameClass::CreatePropertyGraph(create_property_graph) => { + return format_create_property_graph(&create_property_graph, &binder); + } NameClass::DeclareCursor(declare) => { return format_declare_cursor(&declare); } @@ -286,6 +289,7 @@ fn hover_name_ref( 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), + NameRefClass::PropertyGraph => hover_property_graph(root, name_ref, binder), } } @@ -1107,6 +1111,24 @@ fn hover_policy( format_create_policy(&create_policy, binder) } +fn hover_property_graph( + root: &SyntaxNode, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let property_graph_ptr = resolve::resolve_name_ref_ptrs(binder, root, name_ref)? + .into_iter() + .next()?; + + let property_graph_name_node = property_graph_ptr.to_node(root); + + let create_property_graph = property_graph_name_node + .ancestors() + .find_map(ast::CreatePropertyGraph::cast)?; + + format_create_property_graph(&create_property_graph, binder) +} + fn hover_event_trigger( root: &SyntaxNode, name_ref: &ast::NameRef, @@ -1447,6 +1469,18 @@ fn format_create_policy( ))) } +fn format_create_property_graph( + create_property_graph: &ast::CreatePropertyGraph, + binder: &binder::Binder, +) -> Option { + let path = create_property_graph.path()?; + let (schema, name) = resolve::resolve_property_graph_info(binder, &path)?; + Some(Hover::snippet(format!( + "property graph {}.{}", + schema, name + ))) +} + fn format_create_event_trigger(create_event_trigger: &ast::CreateEventTrigger) -> Option { let name = create_event_trigger.name()?.syntax().text().to_string(); Some(Hover::snippet(format!("event trigger {}", name))) @@ -4972,11 +5006,49 @@ notify updates$0; assert_snapshot!(check_hover(" listen updates; unlisten updates$0; -"), @r" +"), @" hover: listen updates ╭▸ 3 │ unlisten updates; ╰╴ ─ hover "); } + + #[test] + fn hover_property_graph_on_create() { + assert_snapshot!(check_hover(" +create property graph foo.ba$0r vertex tables (t key (a) no properties); +"), @" + hover: property graph foo.bar + ╭▸ + 2 │ create property graph foo.bar vertex tables (t key (a) no properties); + ╰╴ ─ hover + "); + } + + #[test] + fn hover_property_graph_on_drop() { + assert_snapshot!(check_hover(" +create property graph foo.bar vertex tables (t key (a) no properties); +drop property graph foo.ba$0r; +"), @" + hover: property graph foo.bar + ╭▸ + 3 │ drop property graph foo.bar; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_property_graph_on_alter() { + assert_snapshot!(check_hover(" +create property graph foo.bar vertex tables (t key (a) no properties); +alter property graph foo.ba$0r rename to baz; +"), @" + hover: property graph foo.bar + ╭▸ + 3 │ alter property graph foo.bar rename to baz; + ╰╴ ─ hover + "); + } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 15f33169..1a4f4c4e 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -198,6 +198,13 @@ pub(crate) fn resolve_name_ref( resolve_event_trigger_name_ptr(binder, &event_trigger_name) .map(|ptr| (smallvec![ptr], LocationKind::EventTrigger)) } + NameRefClass::PropertyGraph => { + let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; + let (property_graph_name, schema) = extract_table_schema_from_path(&path)?; + let position = name_ref.syntax().text_range().start(); + resolve_property_graph_name_ptr(binder, &property_graph_name, &schema, position) + .map(|ptr| (smallvec![ptr], LocationKind::PropertyGraph)) + } NameRefClass::Database => { let database_name = Name::from_node(name_ref); resolve_database_name_ptr(binder, &database_name) @@ -752,6 +759,20 @@ fn resolve_event_trigger_name_ptr( binder.lookup(event_trigger_name, SymbolKind::EventTrigger) } +fn resolve_property_graph_name_ptr( + binder: &Binder, + property_graph_name: &Name, + schema: &Option, + position: TextSize, +) -> Option { + binder.lookup_with( + property_graph_name, + SymbolKind::PropertyGraph, + position, + schema, + ) +} + fn resolve_tablespace_name_ptr(binder: &Binder, tablespace_name: &Name) -> Option { binder.lookup(tablespace_name, SymbolKind::Tablespace) } @@ -3403,6 +3424,13 @@ pub(crate) fn resolve_type_info(binder: &Binder, path: &ast::Path) -> Option<(Sc resolve_symbol_info(binder, path, SymbolKind::Type) } +pub(crate) fn resolve_property_graph_info( + binder: &Binder, + path: &ast::Path, +) -> Option<(Schema, String)> { + resolve_symbol_info(binder, path, SymbolKind::PropertyGraph) +} + pub(crate) fn resolve_view_info(binder: &Binder, path: &ast::Path) -> Option<(Schema, String)> { resolve_symbol_info(binder, path, SymbolKind::View) } diff --git a/crates/squawk_ide/src/semantic_tokens.rs b/crates/squawk_ide/src/semantic_tokens.rs index e45f21cb..bdf0af91 100644 --- a/crates/squawk_ide/src/semantic_tokens.rs +++ b/crates/squawk_ide/src/semantic_tokens.rs @@ -203,6 +203,7 @@ impl TryFrom for SemanticTokenType { | LocationKind::Index | LocationKind::Policy | LocationKind::PreparedStatement + | LocationKind::PropertyGraph | LocationKind::Role | LocationKind::Server | LocationKind::Tablespace diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index ba8305e0..a68b9370 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -64,6 +64,7 @@ pub(crate) enum SymbolKind { EventTrigger, Role, Policy, + PropertyGraph, } #[derive(Clone, Debug, PartialEq)] diff --git a/crates/squawk_server/src/handlers/document_symbol.rs b/crates/squawk_server/src/handlers/document_symbol.rs index df5842ab..a74050e0 100644 --- a/crates/squawk_server/src/handlers/document_symbol.rs +++ b/crates/squawk_server/src/handlers/document_symbol.rs @@ -63,6 +63,7 @@ pub(crate) fn handle_document_symbol( DocumentSymbolKind::EventTrigger => SymbolKind::EVENT, DocumentSymbolKind::Role => SymbolKind::CLASS, DocumentSymbolKind::Policy => SymbolKind::VARIABLE, + DocumentSymbolKind::PropertyGraph => SymbolKind::STRUCT, }, tags: None, range, diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index 10f14571..bf03756d 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -672,6 +672,7 @@ fn convert_document_symbol( squawk_ide::document_symbols::DocumentSymbolKind::EventTrigger => "event_trigger", squawk_ide::document_symbols::DocumentSymbolKind::Role => "role", squawk_ide::document_symbols::DocumentSymbolKind::Policy => "policy", + squawk_ide::document_symbols::DocumentSymbolKind::PropertyGraph => "property_graph", squawk_ide::document_symbols::DocumentSymbolKind::Type => "type", squawk_ide::document_symbols::DocumentSymbolKind::Enum => "enum", squawk_ide::document_symbols::DocumentSymbolKind::Index => "index",