From a4b03e9c707b9ce2a86dc1d963b911c47259398a Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 23 Oct 2025 17:57:31 +0200 Subject: [PATCH 1/8] removed parser --- crates/pgt_completions/src/providers/roles.rs | 2 +- .../src/relevance/filtering.rs | 11 + crates/pgt_hover/src/hovered_node.rs | 4 +- .../pgt_treesitter/src/context/base_parser.rs | 6 - crates/pgt_treesitter/src/context/mod.rs | 37 +- .../src/context/revoke_parser.rs | 342 ------------------ crates/pgt_treesitter_grammar/grammar.js | 115 +++++- 7 files changed, 131 insertions(+), 386 deletions(-) delete mode 100644 crates/pgt_treesitter/src/context/revoke_parser.rs diff --git a/crates/pgt_completions/src/providers/roles.rs b/crates/pgt_completions/src/providers/roles.rs index b7664349c..c54677dee 100644 --- a/crates/pgt_completions/src/providers/roles.rs +++ b/crates/pgt_completions/src/providers/roles.rs @@ -292,7 +292,7 @@ mod tests { QueryWithCursorPosition::cursor_marker() ), format!( - "revoke all on table userse from owner, {}", + "revoke all on table users from owner, {}", QueryWithCursorPosition::cursor_marker() ), ]; diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index c11390bd1..1d7456e1a 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -245,6 +245,17 @@ impl CompletionFilter<'_> { WrappingClause::SetStatement => ctx .before_cursor_matches_kind(&["keyword_role", "keyword_authorization"]), + WrappingClause::RevokeStatement => { + ctx.matches_ancestor_history(&["role_specification"]) + || ctx.node_under_cursor.as_ref().is_some_and(|k| { + k.kind() == "identifier" + && ctx.before_cursor_matches_kind(&[ + "keyword_revoke", + "keyword_for", + ]) + }) + } + WrappingClause::AlterPolicy | WrappingClause::CreatePolicy => { ctx.before_cursor_matches_kind(&["keyword_to"]) && ctx.matches_ancestor_history(&["policy_to_role"]) diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index b0d1cf0f3..a0ba51ae5 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -127,7 +127,7 @@ impl HoveredNode { } } - "revoke_role" | "grant_role" | "policy_role" => { + "grant_role" | "policy_role" => { Some(HoveredNode::Role(NodeIdentification::Name(node_content))) } @@ -136,7 +136,7 @@ impl HoveredNode { Some(HoveredNode::Column(NodeIdentification::Name(node_content))) } - "revoke_table" | "grant_table" => { + "grant_table" => { if let Some(schema) = ctx.schema_or_alias_name.as_ref() { Some(HoveredNode::Table(NodeIdentification::SchemaAndName(( schema.clone(), diff --git a/crates/pgt_treesitter/src/context/base_parser.rs b/crates/pgt_treesitter/src/context/base_parser.rs index 83b315828..60534fd01 100644 --- a/crates/pgt_treesitter/src/context/base_parser.rs +++ b/crates/pgt_treesitter/src/context/base_parser.rs @@ -14,12 +14,6 @@ impl TokenNavigator { .is_some_and(|c| options.contains(&c.get_word_without_quotes().as_str())) } - pub(crate) fn prev_matches(&self, options: &[&str]) -> bool { - self.previous_token - .as_ref() - .is_some_and(|t| options.contains(&t.get_word_without_quotes().as_str())) - } - pub(crate) fn advance(&mut self) -> Option { // we can't peek back n an iterator, so we'll have to keep track manually. self.previous_token = self.current_token.take(); diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 9c404dadb..44f48b1fa 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -4,14 +4,11 @@ use std::{ }; mod base_parser; mod grant_parser; -mod revoke_parser; use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; use pgt_text_size::{TextRange, TextSize}; -use crate::context::{ - base_parser::CompletionStatementParser, grant_parser::GrantParser, revoke_parser::RevokeParser, -}; +use crate::context::{base_parser::CompletionStatementParser, grant_parser::GrantParser}; #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum WrappingClause<'a> { @@ -34,6 +31,7 @@ pub enum WrappingClause<'a> { SetStatement, AlterRole, DropRole, + RevokeStatement, CreatePolicy, AlterPolicy, @@ -198,8 +196,6 @@ impl<'a> TreesitterContext<'a> { if GrantParser::looks_like_matching_stmt(params.text) { ctx.gather_grant_context(); - } else if RevokeParser::looks_like_matching_stmt(params.text) { - ctx.gather_revoke_context(); } else { ctx.gather_tree_context(); ctx.gather_info_from_ts_queries(); @@ -208,34 +204,6 @@ impl<'a> TreesitterContext<'a> { ctx } - fn gather_revoke_context(&mut self) { - let revoke_context = RevokeParser::get_context(self.text, self.position); - - self.node_under_cursor = Some(NodeUnderCursor::CustomNode { - text: revoke_context.node_text, - range: revoke_context.node_range, - kind: revoke_context.node_kind.clone(), - previous_node_kind: None, - }); - - if revoke_context.node_kind == "revoke_table" { - self.schema_or_alias_name = revoke_context.schema_name.clone(); - } - - if revoke_context.table_name.is_some() { - let mut new = HashSet::new(); - new.insert(revoke_context.table_name.unwrap()); - self.mentioned_relations - .insert(revoke_context.schema_name, new); - } - - self.wrapping_clause_type = match revoke_context.node_kind.as_str() { - "revoke_role" => Some(WrappingClause::ToRoleAssignment), - "revoke_table" => Some(WrappingClause::From), - _ => None, - }; - } - fn gather_grant_context(&mut self) { let grant_context = GrantParser::get_context(self.text, self.position); @@ -686,6 +654,7 @@ impl<'a> TreesitterContext<'a> { "rename_column" => Some(WrappingClause::RenameColumn), "alter_table" => Some(WrappingClause::AlterTable), "set_statement" => Some(WrappingClause::SetStatement), + "revoke_statement" => Some(WrappingClause::RevokeStatement), "column_definitions" => Some(WrappingClause::ColumnDefinitions), "create_policy" => Some(WrappingClause::CreatePolicy), "alter_policy" => Some(WrappingClause::AlterPolicy), diff --git a/crates/pgt_treesitter/src/context/revoke_parser.rs b/crates/pgt_treesitter/src/context/revoke_parser.rs deleted file mode 100644 index 4f5b09ec8..000000000 --- a/crates/pgt_treesitter/src/context/revoke_parser.rs +++ /dev/null @@ -1,342 +0,0 @@ -use pgt_text_size::{TextRange, TextSize}; - -use crate::context::base_parser::{ - CompletionStatementParser, TokenNavigator, WordWithIndex, schema_and_table_name, -}; - -#[derive(Default, Debug, PartialEq, Eq)] -pub(crate) struct RevokeContext { - pub table_name: Option, - pub schema_name: Option, - pub node_text: String, - pub node_range: TextRange, - pub node_kind: String, -} - -/// Simple parser that'll turn a policy-related statement into a context object required for -/// completions. -/// The parser will only work if the (trimmed) sql starts with `create policy`, `drop policy`, or `alter policy`. -/// It can only parse policy statements. -pub(crate) struct RevokeParser { - navigator: TokenNavigator, - context: RevokeContext, - cursor_position: usize, - in_roles_list: bool, - is_revoking_role: bool, -} - -impl CompletionStatementParser for RevokeParser { - type Context = RevokeContext; - const NAME: &'static str = "RevokeParser"; - - fn looks_like_matching_stmt(sql: &str) -> bool { - let lowercased = sql.to_ascii_lowercase(); - let trimmed = lowercased.trim(); - trimmed.starts_with("revoke") - } - - fn parse(mut self) -> Self::Context { - while let Some(token) = self.navigator.advance() { - if token.is_under_cursor(self.cursor_position) { - self.handle_token_under_cursor(token); - } else { - self.handle_token(token); - } - } - - self.context - } - - fn make_parser(tokens: Vec, cursor_position: usize) -> Self { - Self { - navigator: tokens.into(), - context: RevokeContext::default(), - cursor_position, - in_roles_list: false, - is_revoking_role: false, - } - } -} - -impl RevokeParser { - fn handle_token_under_cursor(&mut self, token: WordWithIndex) { - if self.navigator.previous_token.is_none() { - return; - } - - let previous = self.navigator.previous_token.take().unwrap(); - let current = self - .navigator - .current_token - .as_ref() - .map(|w| w.get_word_without_quotes()); - - match previous - .get_word_without_quotes() - .to_ascii_lowercase() - .as_str() - { - "on" if !matches!(current.as_deref(), Some("table")) => self.handle_table(&token), - - "table" => { - self.handle_table(&token); - } - - "from" | "revoke" => { - self.context.node_range = token.get_range(); - self.context.node_kind = "revoke_role".into(); - self.context.node_text = token.get_word(); - } - - "for" if self.is_revoking_role => { - self.context.node_range = token.get_range(); - self.context.node_kind = "revoke_role".into(); - self.context.node_text = token.get_word(); - } - - t => { - if self.in_roles_list && t.ends_with(',') { - self.context.node_kind = "revoke_role".into(); - } - - self.context.node_range = token.get_range(); - self.context.node_text = token.get_word(); - } - } - } - - fn handle_table(&mut self, token: &WordWithIndex) { - if token.get_word_without_quotes().contains('.') { - let (schema_name, table_name) = schema_and_table_name(token); - - let schema_name_len = schema_name.len(); - self.context.schema_name = Some(schema_name); - - let offset: u32 = schema_name_len.try_into().expect("Text too long"); - let range_without_schema = token - .get_range() - .checked_expand_start( - TextSize::new(offset + 1), // kill the dot as well - ) - .expect("Text too long"); - - self.context.node_range = range_without_schema; - self.context.node_kind = "revoke_table".into(); - - // In practice, we should always have a table name. - // The completion sanitization will add a word after a `.` if nothing follows it; - // the token_text will then look like `schema.REPLACED_TOKEN`. - self.context.node_text = table_name.unwrap_or_default(); - } else { - self.context.node_range = token.get_range(); - self.context.node_text = token.get_word(); - self.context.node_kind = "revoke_table".into(); - } - } - - fn handle_token(&mut self, token: WordWithIndex) { - match token.get_word_without_quotes().as_str() { - "on" if !self.navigator.next_matches(&["table"]) => self.table_with_schema(), - - // This is the only case where there is no "GRANT" before the option: - // REVOKE [ { ADMIN | INHERIT | SET } OPTION FOR ] role_name - "option" if !self.navigator.prev_matches(&["grant"]) => { - self.is_revoking_role = true; - } - - "table" => self.table_with_schema(), - - "from" => { - self.in_roles_list = true; - } - - t => { - if self.in_roles_list && !t.ends_with(',') { - self.in_roles_list = false; - } - } - } - } - - fn table_with_schema(&mut self) { - if let Some(token) = self.navigator.advance() { - if token.is_under_cursor(self.cursor_position) { - self.handle_token_under_cursor(token); - } else if token.get_word_without_quotes().contains('.') { - let (schema, maybe_table) = schema_and_table_name(&token); - self.context.schema_name = Some(schema); - self.context.table_name = maybe_table; - } else { - self.context.table_name = Some(token.get_word()); - } - }; - } -} - -#[cfg(test)] -mod tests { - use pgt_text_size::{TextRange, TextSize}; - - use crate::{ - context::base_parser::CompletionStatementParser, - context::revoke_parser::{RevokeContext, RevokeParser}, - }; - - use pgt_test_utils::QueryWithCursorPosition; - - fn with_pos(query: String) -> (usize, String) { - let mut pos: Option = None; - - for (p, c) in query.char_indices() { - if c == QueryWithCursorPosition::cursor_marker() { - pos = Some(p); - break; - } - } - - ( - pos.expect("Please add cursor position!"), - query - .replace(QueryWithCursorPosition::cursor_marker(), "REPLACED_TOKEN") - .to_string(), - ) - } - - #[test] - fn infers_revoke_keyword() { - let (pos, query) = with_pos(format!( - r#" - revoke {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = RevokeParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - RevokeContext { - table_name: None, - schema_name: None, - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(20), TextSize::new(34)), - node_kind: "revoke_role".into(), - } - ); - } - - #[test] - fn infers_table_name() { - let (pos, query) = with_pos(format!( - r#" - revoke select on {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = RevokeParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - RevokeContext { - table_name: None, - schema_name: None, - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(30), TextSize::new(44)), - node_kind: "revoke_table".into(), - } - ); - } - - #[test] - fn infers_schema_and_table_name() { - let (pos, query) = with_pos(format!( - r#" - revoke select on public.{} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = RevokeParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - RevokeContext { - table_name: None, - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(37), TextSize::new(51)), - node_kind: "revoke_table".into(), - } - ); - } - - #[test] - fn infers_role_name() { - let (pos, query) = with_pos(format!( - r#" - revoke select on public.users from {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = RevokeParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - RevokeContext { - table_name: Some("users".into()), - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(48), TextSize::new(62)), - node_kind: "revoke_role".into(), - } - ); - } - - #[test] - fn infers_multiple_roles() { - let (pos, query) = with_pos(format!( - r#" - revoke select on public.users from alice, {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = RevokeParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - RevokeContext { - table_name: Some("users".into()), - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(55), TextSize::new(69)), - node_kind: "revoke_role".into(), - } - ); - } - - #[test] - fn infers_quoted_schema_and_table() { - let (pos, query) = with_pos(format!( - r#" - revoke select on "MySchema"."MyTable" from {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = RevokeParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - RevokeContext { - table_name: Some("MyTable".into()), - schema_name: Some("MySchema".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(56), TextSize::new(70)), - node_kind: "revoke_role".into(), - } - ); - } -} diff --git a/crates/pgt_treesitter_grammar/grammar.js b/crates/pgt_treesitter_grammar/grammar.js index cf33f72bb..9ad689978 100644 --- a/crates/pgt_treesitter_grammar/grammar.js +++ b/crates/pgt_treesitter_grammar/grammar.js @@ -27,6 +27,7 @@ module.exports = grammar({ [$.between_expression, $.binary_expression], [$.time], [$.timestamp], + [$.revoke_on_function, $.revoke_on_table], ], precedences: ($) => [ @@ -270,6 +271,16 @@ module.exports = grammar({ keyword_current_user: (_) => make_keyword("current_user"), keyword_session_user: (_) => make_keyword("session_user"), + keyword_grant: (_) => make_keyword("grant"), + keyword_revoke: (_) => make_keyword("revoke"), + keyword_granted: (_) => make_keyword("granted"), + keyword_privileges: (_) => make_keyword("privileges"), + keyword_inherit: (_) => make_keyword("inherit"), + keyword_maintain: (_) => make_keyword("maintain"), + keyword_functions: (_) => make_keyword("functions"), + keyword_routines: (_) => make_keyword("routines"), + keyword_procedures: (_) => make_keyword("procedures"), + keyword_trigger: (_) => make_keyword("trigger"), keyword_function: (_) => make_keyword("function"), keyword_returns: (_) => make_keyword("returns"), @@ -329,6 +340,7 @@ module.exports = grammar({ keyword_statement: (_) => make_keyword("statement"), keyword_execute: (_) => make_keyword("execute"), keyword_procedure: (_) => make_keyword("procedure"), + keyword_routine: (_) => make_keyword("routine"), keyword_object_id: (_) => make_keyword("object_id"), // Hive Keywords @@ -716,7 +728,8 @@ module.exports = grammar({ $._merge_statement, $.comment_statement, $.set_statement, - $.reset_statement + $.reset_statement, + $.revoke_statement ), _cte: ($) => @@ -3105,6 +3118,106 @@ module.exports = grammar({ returning: ($) => seq($.keyword_returning, $.select_expression), + // todo: add support for various other revoke statements + revoke_statement: ($) => + seq( + $.keyword_revoke, + optional( + choice( + seq($.keyword_grant, $.keyword_option, $.keyword_for), + seq( + optional( + choice($.keyword_admin, $.keyword_inherit, $.keyword_set) + ), + $.keyword_option, + $.keyword_for + ) + ) + ), + choice( + seq( + $.revoke_targets, + choice($.revoke_on_table, $.revoke_on_function, $.revoke_on_all) + ), + $.identifier + ), + $.keyword_from, + comma_list($.role_specification, true), + optional(seq($.keyword_granted, $.keyword_by, $.role_specification)), + optional(choice($.keyword_cascade, $.keyword_restrict)) + ), + + revoke_targets: ($) => + choice( + seq($._revoke_keyword, comma_list($.identifier, false)), + comma_list($.identifier, true) + ), + + _revoke_keyword: ($) => + choice( + comma_list( + choice( + $.keyword_select, + $.keyword_insert, + $.keyword_update, + $.keyword_delete, + $.keyword_truncate, + $.keyword_references, + $.keyword_trigger, + $.keyword_maintain, + $.keyword_execute + ), + true + ), + seq($.keyword_all, optional($.keyword_privileges)) + ), + + revoke_on_function: ($) => + seq( + $.keyword_on, + optional( + choice($.keyword_function, $.keyword_procedure, $.keyword_routine) + ), + comma_list( + seq($.object_reference, optional($.function_arguments)), + true + ) + ), + + revoke_on_table: ($) => + prec( + 1, + seq( + $.keyword_on, + optional($.keyword_table), + comma_list($.object_reference, true) + ) + ), + + revoke_on_all: ($) => + seq( + $.keyword_on, + $.keyword_all, + choice( + $.keyword_tables, + $.keyword_functions, + $.keyword_procedures, + $.keyword_routines + ), + $.keyword_in, + $.keyword_schema, + comma_list($.identifier, true) + ), + + role_specification: ($) => + choice( + seq(optional($.keyword_group), $.identifier), + $.keyword_public, + $.keyword_current_role, + $.keyword_current_user, + $.keyword_session_user + ), + _expression: ($) => prec( 1, From 5f8e7b377b5ea7612e143032d3f58c06e773a19d Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 23 Oct 2025 18:35:45 +0200 Subject: [PATCH 2/8] a little bit of support --- crates/pgt_hover/src/hovered_node.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index a0ba51ae5..ad419f798 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -93,7 +93,16 @@ impl HoveredNode { } } - "identifier" if ctx.matches_one_of_ancestors(&["alter_role", "policy_to_role"]) => { + "identifier" + if ctx.matches_one_of_ancestors(&[ + "alter_role", + "policy_to_role", + "role_specification", + ]) || ctx.before_cursor_matches_kind(&["keyword_revoke"]) => + { + Some(HoveredNode::Role(NodeIdentification::Name(node_content))) + } + "grant_role" | "policy_role" => { Some(HoveredNode::Role(NodeIdentification::Name(node_content))) } @@ -127,10 +136,6 @@ impl HoveredNode { } } - "grant_role" | "policy_role" => { - Some(HoveredNode::Role(NodeIdentification::Name(node_content))) - } - // quoted columns "literal" if ctx.matches_ancestor_history(&["select_expression", "term"]) => { Some(HoveredNode::Column(NodeIdentification::Name(node_content))) From 019a3bcaf5a0d1906e51fb7b870c537b482dc507 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 24 Oct 2025 17:59:21 +0200 Subject: [PATCH 3/8] ok --- crates/pgt_completions/src/relevance/filtering.rs | 10 ++++++++++ crates/pgt_hover/src/hovered_node.rs | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index 1d7456e1a..ddbc66eb8 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -99,6 +99,10 @@ impl CompletionFilter<'_> { CompletionRelevanceData::Table(_) => match clause { WrappingClause::From | WrappingClause::Update => true, + WrappingClause::RevokeStatement => { + ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) + } + WrappingClause::Join { on_node: None } => true, WrappingClause::Join { on_node: Some(on) } => ctx .node_under_cursor @@ -202,6 +206,12 @@ impl CompletionFilter<'_> { | WrappingClause::Update | WrappingClause::Delete => true, + WrappingClause::RevokeStatement => { + (ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) + && ctx.schema_or_alias_name.is_none()) + || ctx.matches_ancestor_history(&["revoke_on_all"]) + } + WrappingClause::Where => { ctx.before_cursor_matches_kind(&["keyword_and", "keyword_where"]) } diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index ad419f798..26a9d2d14 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -32,7 +32,10 @@ impl HoveredNode { let under_cursor = ctx.node_under_cursor.as_ref()?; match under_cursor.kind() { - "identifier" if ctx.matches_ancestor_history(&["relation", "object_reference"]) => { + "identifier" + if ctx.matches_ancestor_history(&["relation", "object_reference"]) + || ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) => + { let num_sibs = ctx.num_siblings(); if ctx.node_under_cursor_is_nth_child(1) && num_sibs > 0 { return Some(HoveredNode::Schema(NodeIdentification::Name(node_content))); From 2b2b6374adb9ee9e6096392c9e7d977aa6fdbcd5 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 24 Oct 2025 18:27:50 +0200 Subject: [PATCH 4/8] rename, add grant grammar --- .../src/relevance/filtering.rs | 13 ++--- crates/pgt_hover/src/hovered_node.rs | 3 +- crates/pgt_treesitter_grammar/grammar.js | 48 +++++++++++++------ 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index ddbc66eb8..e6545e54b 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -99,9 +99,8 @@ impl CompletionFilter<'_> { CompletionRelevanceData::Table(_) => match clause { WrappingClause::From | WrappingClause::Update => true, - WrappingClause::RevokeStatement => { - ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) - } + WrappingClause::RevokeStatement => ctx + .matches_ancestor_history(&["grantable_on_table", "object_reference"]), WrappingClause::Join { on_node: None } => true, WrappingClause::Join { on_node: Some(on) } => ctx @@ -207,9 +206,11 @@ impl CompletionFilter<'_> { | WrappingClause::Delete => true, WrappingClause::RevokeStatement => { - (ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) - && ctx.schema_or_alias_name.is_none()) - || ctx.matches_ancestor_history(&["revoke_on_all"]) + (ctx.matches_ancestor_history(&[ + "grantable_on_table", + "object_reference", + ]) && ctx.schema_or_alias_name.is_none()) + || ctx.matches_ancestor_history(&["grantable_on_all"]) } WrappingClause::Where => { diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 26a9d2d14..7a7e388ac 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -34,7 +34,8 @@ impl HoveredNode { match under_cursor.kind() { "identifier" if ctx.matches_ancestor_history(&["relation", "object_reference"]) - || ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) => + || ctx + .matches_ancestor_history(&["grantable_on_table", "object_reference"]) => { let num_sibs = ctx.num_siblings(); if ctx.node_under_cursor_is_nth_child(1) && num_sibs > 0 { diff --git a/crates/pgt_treesitter_grammar/grammar.js b/crates/pgt_treesitter_grammar/grammar.js index 9ad689978..ad0176d7e 100644 --- a/crates/pgt_treesitter_grammar/grammar.js +++ b/crates/pgt_treesitter_grammar/grammar.js @@ -27,7 +27,7 @@ module.exports = grammar({ [$.between_expression, $.binary_expression], [$.time], [$.timestamp], - [$.revoke_on_function, $.revoke_on_table], + [$.grantable_on_function, $.grantable_on_table], ], precedences: ($) => [ @@ -3118,6 +3118,16 @@ module.exports = grammar({ returning: ($) => seq($.keyword_returning, $.select_expression), + grant_statement: ($) => + seq( + $.keyword_grant, + $._grantable_target_on, + $.keyword_to, + comma_list($.role_specification, true), + optional(seq($.keyword_with, $.keyword_grant, $.keyword_option)), + optional(seq($.keyword_granted, $.keyword_by, $.role_specification)) + ), + // todo: add support for various other revoke statements revoke_statement: ($) => seq( @@ -3134,26 +3144,33 @@ module.exports = grammar({ ) ) ), - choice( - seq( - $.revoke_targets, - choice($.revoke_on_table, $.revoke_on_function, $.revoke_on_all) - ), - $.identifier - ), + $._grantable_target_on, $.keyword_from, comma_list($.role_specification, true), optional(seq($.keyword_granted, $.keyword_by, $.role_specification)), optional(choice($.keyword_cascade, $.keyword_restrict)) ), - revoke_targets: ($) => + _grantable_target_on: ($) => + choice( + seq( + $.grantable_targets, + choice( + $.grantable_on_table, + $.grantable_on_function, + $.grantable_on_all + ) + ), + $.identifier + ), + + grantable_targets: ($) => choice( - seq($._revoke_keyword, comma_list($.identifier, false)), + seq($._grantable, comma_list($.identifier, false)), comma_list($.identifier, true) ), - _revoke_keyword: ($) => + _grantable: ($) => choice( comma_list( choice( @@ -3165,14 +3182,15 @@ module.exports = grammar({ $.keyword_references, $.keyword_trigger, $.keyword_maintain, - $.keyword_execute + $.keyword_execute, + $.keyword_references ), true ), seq($.keyword_all, optional($.keyword_privileges)) ), - revoke_on_function: ($) => + grantable_on_function: ($) => seq( $.keyword_on, optional( @@ -3184,7 +3202,7 @@ module.exports = grammar({ ) ), - revoke_on_table: ($) => + grantable_on_table: ($) => prec( 1, seq( @@ -3194,7 +3212,7 @@ module.exports = grammar({ ) ), - revoke_on_all: ($) => + grantable_on_all: ($) => seq( $.keyword_on, $.keyword_all, From 15f7db19316522a52fe51484f9cb49524f67b9e6 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 24 Oct 2025 18:32:26 +0200 Subject: [PATCH 5/8] ok ok --- .../src/relevance/filtering.rs | 7 +- .../src/context/grant_parser.rs | 418 ------------------ crates/pgt_treesitter/src/context/mod.rs | 43 +- crates/pgt_treesitter_grammar/grammar.js | 3 +- 4 files changed, 9 insertions(+), 462 deletions(-) delete mode 100644 crates/pgt_treesitter/src/context/grant_parser.rs diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index e6545e54b..cbd3972aa 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -249,18 +249,17 @@ impl CompletionFilter<'_> { } CompletionRelevanceData::Role(_) => match clause { - WrappingClause::DropRole - | WrappingClause::AlterRole - | WrappingClause::ToRoleAssignment => true, + WrappingClause::DropRole | WrappingClause::AlterRole => true, WrappingClause::SetStatement => ctx .before_cursor_matches_kind(&["keyword_role", "keyword_authorization"]), - WrappingClause::RevokeStatement => { + WrappingClause::RevokeStatement | WrappingClause::GrantStatement => { ctx.matches_ancestor_history(&["role_specification"]) || ctx.node_under_cursor.as_ref().is_some_and(|k| { k.kind() == "identifier" && ctx.before_cursor_matches_kind(&[ + "keyword_grant", "keyword_revoke", "keyword_for", ]) diff --git a/crates/pgt_treesitter/src/context/grant_parser.rs b/crates/pgt_treesitter/src/context/grant_parser.rs deleted file mode 100644 index c9aebc33b..000000000 --- a/crates/pgt_treesitter/src/context/grant_parser.rs +++ /dev/null @@ -1,418 +0,0 @@ -use pgt_text_size::{TextRange, TextSize}; - -use crate::context::base_parser::{ - CompletionStatementParser, TokenNavigator, WordWithIndex, schema_and_table_name, -}; - -#[derive(Default, Debug, PartialEq, Eq)] -pub(crate) struct GrantContext { - pub table_name: Option, - pub schema_name: Option, - pub node_text: String, - pub node_range: TextRange, - pub node_kind: String, -} - -/// Simple parser that'll turn a policy-related statement into a context object required for -/// completions. -/// The parser will only work if the (trimmed) sql starts with `create policy`, `drop policy`, or `alter policy`. -/// It can only parse policy statements. -pub(crate) struct GrantParser { - navigator: TokenNavigator, - context: GrantContext, - cursor_position: usize, - in_roles_list: bool, -} - -impl CompletionStatementParser for GrantParser { - type Context = GrantContext; - const NAME: &'static str = "GrantParser"; - - fn looks_like_matching_stmt(sql: &str) -> bool { - let lowercased = sql.to_ascii_lowercase(); - let trimmed = lowercased.trim(); - trimmed.starts_with("grant") - } - - fn parse(mut self) -> Self::Context { - while let Some(token) = self.navigator.advance() { - if token.is_under_cursor(self.cursor_position) { - self.handle_token_under_cursor(token); - } else { - self.handle_token(token); - } - } - - self.context - } - - fn make_parser(tokens: Vec, cursor_position: usize) -> Self { - Self { - navigator: tokens.into(), - context: GrantContext::default(), - cursor_position, - in_roles_list: false, - } - } -} - -impl GrantParser { - fn handle_token_under_cursor(&mut self, token: WordWithIndex) { - if self.navigator.previous_token.is_none() { - return; - } - - let previous = self.navigator.previous_token.take().unwrap(); - let current = self - .navigator - .current_token - .as_ref() - .map(|w| w.get_word_without_quotes()); - - match previous - .get_word_without_quotes() - .to_ascii_lowercase() - .as_str() - { - "grant" => { - self.context.node_range = token.get_range(); - self.context.node_kind = "grant_role".into(); - self.context.node_text = token.get_word(); - } - "on" if !matches!(current.as_deref(), Some("table")) => self.handle_table(&token), - - "table" => { - self.handle_table(&token); - } - "to" => { - self.context.node_range = token.get_range(); - self.context.node_kind = "grant_role".into(); - self.context.node_text = token.get_word(); - } - t => { - if self.in_roles_list && t.ends_with(',') { - self.context.node_kind = "grant_role".into(); - } - - self.context.node_range = token.get_range(); - self.context.node_text = token.get_word(); - } - } - } - - fn handle_table(&mut self, token: &WordWithIndex) { - if token.get_word_without_quotes().contains('.') { - let (schema_name, table_name) = schema_and_table_name(token); - - let schema_name_len = schema_name.len(); - self.context.schema_name = Some(schema_name); - - let offset: u32 = schema_name_len.try_into().expect("Text too long"); - let range_without_schema = token - .get_range() - .checked_expand_start( - TextSize::new(offset + 1), // kill the dot as well - ) - .expect("Text too long"); - - self.context.node_range = range_without_schema; - self.context.node_kind = "grant_table".into(); - - // In practice, we should always have a table name. - // The completion sanitization will add a word after a `.` if nothing follows it; - // the token_text will then look like `schema.REPLACED_TOKEN`. - self.context.node_text = table_name.unwrap_or_default(); - } else { - self.context.node_range = token.get_range(); - self.context.node_text = token.get_word(); - self.context.node_kind = "grant_table".into(); - } - } - - fn handle_token(&mut self, token: WordWithIndex) { - match token.get_word_without_quotes().as_str() { - "on" if !self.navigator.next_matches(&[ - "table", - "schema", - "foreign", - "domain", - "sequence", - "database", - "function", - "procedure", - "routine", - "language", - "large", - "parameter", - "schema", - "tablespace", - "type", - ]) => - { - self.table_with_schema() - } - "table" => self.table_with_schema(), - - "to" => { - self.in_roles_list = true; - } - - t => { - if self.in_roles_list && !t.ends_with(',') { - self.in_roles_list = false; - } - } - } - } - - fn table_with_schema(&mut self) { - if let Some(token) = self.navigator.advance() { - if token.is_under_cursor(self.cursor_position) { - self.handle_token_under_cursor(token); - } else if token.get_word_without_quotes().contains('.') { - let (schema, maybe_table) = schema_and_table_name(&token); - self.context.schema_name = Some(schema); - self.context.table_name = maybe_table; - } else { - self.context.table_name = Some(token.get_word()); - } - }; - } -} - -#[cfg(test)] -mod tests { - use pgt_text_size::{TextRange, TextSize}; - - use crate::{ - context::base_parser::CompletionStatementParser, - context::grant_parser::{GrantContext, GrantParser}, - }; - - use pgt_test_utils::QueryWithCursorPosition; - - fn with_pos(query: String) -> (usize, String) { - let mut pos: Option = None; - - for (p, c) in query.char_indices() { - if c == QueryWithCursorPosition::cursor_marker() { - pos = Some(p); - break; - } - } - - ( - pos.expect("Please add cursor position!"), - query - .replace(QueryWithCursorPosition::cursor_marker(), "REPLACED_TOKEN") - .to_string(), - ) - } - - #[test] - fn infers_grant_keyword() { - let (pos, query) = with_pos(format!( - r#" - grant {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: None, - schema_name: None, - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(19), TextSize::new(33)), - node_kind: "grant_role".into(), - } - ); - } - - #[test] - fn infers_table_name() { - let (pos, query) = with_pos(format!( - r#" - grant select on {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: None, - schema_name: None, - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(29), TextSize::new(43)), - node_kind: "grant_table".into(), - } - ); - } - - #[test] - fn infers_table_name_with_keyword() { - let (pos, query) = with_pos(format!( - r#" - grant select on table {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: None, - schema_name: None, - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(35), TextSize::new(49)), - node_kind: "grant_table".into(), - } - ); - } - - #[test] - fn infers_schema_and_table_name() { - let (pos, query) = with_pos(format!( - r#" - grant select on public.{} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: None, - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(36), TextSize::new(50)), - node_kind: "grant_table".into(), - } - ); - } - - #[test] - fn infers_schema_and_table_name_with_keyword() { - let (pos, query) = with_pos(format!( - r#" - grant select on table public.{} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: None, - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(42), TextSize::new(56)), - node_kind: "grant_table".into(), - } - ); - } - - #[test] - fn infers_role_name() { - let (pos, query) = with_pos(format!( - r#" - grant select on public.users to {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: Some("users".into()), - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(45), TextSize::new(59)), - node_kind: "grant_role".into(), - } - ); - } - - #[test] - fn determines_table_name_after_schema() { - let (pos, query) = with_pos(format!( - r#" - grant select on public.{} to test_role - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: None, - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(36), TextSize::new(50)), - node_kind: "grant_table".into(), - } - ); - } - - #[test] - fn infers_quoted_schema_and_table() { - let (pos, query) = with_pos(format!( - r#" - grant select on "MySchema"."MyTable" to {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: Some("MyTable".into()), - schema_name: Some("MySchema".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(53), TextSize::new(67)), - node_kind: "grant_role".into(), - } - ); - } - - #[test] - fn infers_multiple_roles() { - let (pos, query) = with_pos(format!( - r#" - grant select on public.users to alice, {} - "#, - QueryWithCursorPosition::cursor_marker() - )); - - let context = GrantParser::get_context(query.as_str(), pos); - - assert_eq!( - context, - GrantContext { - table_name: Some("users".into()), - schema_name: Some("public".into()), - node_text: "REPLACED_TOKEN".into(), - node_range: TextRange::new(TextSize::new(52), TextSize::new(66)), - node_kind: "grant_role".into(), - } - ); - } -} diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index c373bff9e..06de8c822 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -2,14 +2,10 @@ use std::{ cmp, collections::{HashMap, HashSet}, }; -mod base_parser; -mod grant_parser; use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; use pgt_text_size::{TextRange, TextSize}; -use crate::context::{base_parser::CompletionStatementParser, grant_parser::GrantParser}; - #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum WrappingClause<'a> { Select, @@ -27,11 +23,11 @@ pub enum WrappingClause<'a> { DropColumn, AlterColumn, RenameColumn, - ToRoleAssignment, SetStatement, AlterRole, DropRole, RevokeStatement, + GrantStatement, CreatePolicy, AlterPolicy, @@ -194,44 +190,12 @@ impl<'a> TreesitterContext<'a> { mentioned_columns: HashMap::new(), }; - if GrantParser::looks_like_matching_stmt(params.text) { - ctx.gather_grant_context(); - } else { - ctx.gather_tree_context(); - ctx.gather_info_from_ts_queries(); - } + ctx.gather_tree_context(); + ctx.gather_info_from_ts_queries(); ctx } - fn gather_grant_context(&mut self) { - let grant_context = GrantParser::get_context(self.text, self.position); - - self.node_under_cursor = Some(NodeUnderCursor::CustomNode { - text: grant_context.node_text, - range: grant_context.node_range, - kind: grant_context.node_kind.clone(), - previous_node_kind: None, - }); - - if grant_context.node_kind == "grant_table" { - self.schema_or_alias_name = grant_context.schema_name.clone(); - } - - if grant_context.table_name.is_some() { - let mut new = HashSet::new(); - new.insert(grant_context.table_name.unwrap()); - self.mentioned_relations - .insert(grant_context.schema_name, new); - } - - self.wrapping_clause_type = match grant_context.node_kind.as_str() { - "grant_role" => Some(WrappingClause::ToRoleAssignment), - "grant_table" => Some(WrappingClause::From), - _ => None, - }; - } - fn gather_info_from_ts_queries(&mut self) { let stmt_range = self.wrapping_statement_range.as_ref(); let sql = self.text; @@ -655,6 +619,7 @@ impl<'a> TreesitterContext<'a> { "alter_table" => Some(WrappingClause::AlterTable), "set_statement" => Some(WrappingClause::SetStatement), "revoke_statement" => Some(WrappingClause::RevokeStatement), + "grant_statement" => Some(WrappingClause::RevokeStatement), "column_definitions" => Some(WrappingClause::ColumnDefinitions), "create_policy" => Some(WrappingClause::CreatePolicy), "alter_policy" => Some(WrappingClause::AlterPolicy), diff --git a/crates/pgt_treesitter_grammar/grammar.js b/crates/pgt_treesitter_grammar/grammar.js index ad0176d7e..07a135e52 100644 --- a/crates/pgt_treesitter_grammar/grammar.js +++ b/crates/pgt_treesitter_grammar/grammar.js @@ -729,7 +729,8 @@ module.exports = grammar({ $.comment_statement, $.set_statement, $.reset_statement, - $.revoke_statement + $.revoke_statement, + $.grant_statement ), _cte: ($) => From 6e72b7264de588c39867a6e9ca533f081e2324af Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 24 Oct 2025 18:38:54 +0200 Subject: [PATCH 6/8] featuressss --- crates/pgt_completions/src/relevance/filtering.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index cbd3972aa..118b8f43d 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -99,7 +99,7 @@ impl CompletionFilter<'_> { CompletionRelevanceData::Table(_) => match clause { WrappingClause::From | WrappingClause::Update => true, - WrappingClause::RevokeStatement => ctx + WrappingClause::RevokeStatement | WrappingClause::GrantStatement => ctx .matches_ancestor_history(&["grantable_on_table", "object_reference"]), WrappingClause::Join { on_node: None } => true, @@ -205,7 +205,7 @@ impl CompletionFilter<'_> { | WrappingClause::Update | WrappingClause::Delete => true, - WrappingClause::RevokeStatement => { + WrappingClause::RevokeStatement | WrappingClause::GrantStatement => { (ctx.matches_ancestor_history(&[ "grantable_on_table", "object_reference", From 1e8415be0593981b32feaffaf08a46b346bc0c98 Mon Sep 17 00:00:00 2001 From: Julian Domke <68325451+juleswritescode@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:13:07 +0100 Subject: [PATCH 7/8] [3] refactor: remove custom NodeUnderCursor (#569) --- .../src/relevance/filtering.rs | 44 +- crates/pgt_hover/src/hovered_node.rs | 14 +- crates/pgt_treesitter/src/context/mod.rs | 258 ++++------- .../src/queries/insert_columns.rs | 10 +- .../pgt_treesitter/src/queries/parameters.rs | 2 +- .../pgt_treesitter/src/queries/relations.rs | 12 +- .../src/queries/select_columns.rs | 2 +- .../src/queries/table_aliases.rs | 6 +- .../src/queries/where_columns.rs | 2 +- crates/pgt_treesitter_grammar/grammar.js | 412 +++++++++--------- 10 files changed, 341 insertions(+), 421 deletions(-) diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index 118b8f43d..42962782a 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -1,5 +1,5 @@ use pgt_schema_cache::ProcKind; -use pgt_treesitter::context::{NodeUnderCursor, TreesitterContext, WrappingClause, WrappingNode}; +use pgt_treesitter::context::{TreesitterContext, WrappingClause, WrappingNode}; use super::CompletionRelevanceData; @@ -17,7 +17,11 @@ impl<'a> From> for CompletionFilter<'a> { impl CompletionFilter<'_> { pub fn is_relevant(&self, ctx: &TreesitterContext) -> Option<()> { self.completable_context(ctx)?; - self.check_clause(ctx)?; + + self.check_node_type(ctx) + // we want to rely on treesitter more, so checking the clause is a fallback + .or_else(|| self.check_clause(ctx))?; + self.check_invocation(ctx)?; self.check_mentioned_schema_or_alias(ctx)?; @@ -67,23 +71,20 @@ impl CompletionFilter<'_> { } // No autocompletions if there are two identifiers without a separator. - if ctx.node_under_cursor.as_ref().is_some_and(|n| match n { - NodeUnderCursor::TsNode(node) => node.prev_sibling().is_some_and(|p| { - (p.kind() == "identifier" || p.kind() == "object_reference") - && n.kind() == "identifier" - }), - NodeUnderCursor::CustomNode { .. } => false, + if ctx.node_under_cursor.as_ref().is_some_and(|node| { + node.prev_sibling().is_some_and(|p| { + (p.kind() == "any_identifier" || p.kind() == "object_reference") + && node.kind() == "any_identifier" + }) }) { return None; } // no completions if we're right after an asterisk: // `select * {}` - if ctx.node_under_cursor.as_ref().is_some_and(|n| match n { - NodeUnderCursor::TsNode(node) => node - .prev_sibling() - .is_some_and(|p| (p.kind() == "all_fields") && n.kind() == "identifier"), - NodeUnderCursor::CustomNode { .. } => false, + if ctx.node_under_cursor.as_ref().is_some_and(|node| { + node.prev_sibling() + .is_some_and(|p| (p.kind() == "all_fields") && node.kind() == "any_identifier") }) { return None; } @@ -91,6 +92,21 @@ impl CompletionFilter<'_> { Some(()) } + fn check_node_type(&self, ctx: &TreesitterContext) -> Option<()> { + let kind = ctx.node_under_cursor.as_ref().map(|n| n.kind())?; + + let is_allowed = match kind { + "column_identifier" => { + matches!(self.data, CompletionRelevanceData::Column(_)) + && !ctx.matches_ancestor_history(&["insert_values", "field"]) + && !ctx.node_under_cursor_is_within_field_name("binary_expr_right") + } + _ => false, + }; + + if is_allowed { Some(()) } else { None } + } + fn check_clause(&self, ctx: &TreesitterContext) -> Option<()> { ctx.wrapping_clause_type .as_ref() @@ -257,7 +273,7 @@ impl CompletionFilter<'_> { WrappingClause::RevokeStatement | WrappingClause::GrantStatement => { ctx.matches_ancestor_history(&["role_specification"]) || ctx.node_under_cursor.as_ref().is_some_and(|k| { - k.kind() == "identifier" + k.kind() == "any_identifier" && ctx.before_cursor_matches_kind(&[ "keyword_grant", "keyword_revoke", diff --git a/crates/pgt_hover/src/hovered_node.rs b/crates/pgt_hover/src/hovered_node.rs index 7a7e388ac..322ca9281 100644 --- a/crates/pgt_hover/src/hovered_node.rs +++ b/crates/pgt_hover/src/hovered_node.rs @@ -32,7 +32,7 @@ impl HoveredNode { let under_cursor = ctx.node_under_cursor.as_ref()?; match under_cursor.kind() { - "identifier" + "any_identifier" if ctx.matches_ancestor_history(&["relation", "object_reference"]) || ctx .matches_ancestor_history(&["grantable_on_table", "object_reference"]) => @@ -52,7 +52,7 @@ impl HoveredNode { } } - "identifier" + "any_identifier" if ctx.matches_ancestor_history(&["object_reference"]) && ctx.wrapping_clause_type.as_ref().is_some_and(|clause| { matches!( @@ -73,7 +73,7 @@ impl HoveredNode { } } - "identifier" if ctx.matches_ancestor_history(&["field"]) => { + "column_identifier" => { if let Some(table_or_alias) = ctx.schema_or_alias_name.as_ref() { Some(HoveredNode::Column(NodeIdentification::SchemaAndName(( table_or_alias.clone(), @@ -84,7 +84,9 @@ impl HoveredNode { } } - "identifier" if ctx.matches_ancestor_history(&["invocation", "object_reference"]) => { + "any_identifier" + if ctx.matches_ancestor_history(&["invocation", "object_reference"]) => + { if let Some(schema) = ctx.schema_or_alias_name.as_ref() { Some(HoveredNode::Function(NodeIdentification::SchemaAndName(( schema.clone(), @@ -97,7 +99,7 @@ impl HoveredNode { } } - "identifier" + "any_identifier" if ctx.matches_one_of_ancestors(&[ "alter_role", "policy_to_role", @@ -110,7 +112,7 @@ impl HoveredNode { Some(HoveredNode::Role(NodeIdentification::Name(node_content))) } - "identifier" + "any_identifier" if ( // hover over custom type in `create table` or `returns` (ctx.matches_ancestor_history(&["type", "object_reference"]) diff --git a/crates/pgt_treesitter/src/context/mod.rs b/crates/pgt_treesitter/src/context/mod.rs index 06de8c822..84d2b5d06 100644 --- a/crates/pgt_treesitter/src/context/mod.rs +++ b/crates/pgt_treesitter/src/context/mod.rs @@ -4,7 +4,7 @@ use std::{ }; use crate::queries::{self, QueryResult, TreeSitterQueriesExecutor}; -use pgt_text_size::{TextRange, TextSize}; +use pgt_text_size::TextSize; #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum WrappingClause<'a> { @@ -57,46 +57,6 @@ pub enum WrappingNode { List, } -#[derive(Debug)] -pub enum NodeUnderCursor<'a> { - TsNode(tree_sitter::Node<'a>), - CustomNode { - text: String, - range: TextRange, - kind: String, - previous_node_kind: Option, - }, -} - -impl NodeUnderCursor<'_> { - pub fn start_byte(&self) -> usize { - match self { - NodeUnderCursor::TsNode(node) => node.start_byte(), - NodeUnderCursor::CustomNode { range, .. } => range.start().into(), - } - } - - pub fn end_byte(&self) -> usize { - match self { - NodeUnderCursor::TsNode(node) => node.end_byte(), - NodeUnderCursor::CustomNode { range, .. } => range.end().into(), - } - } - - pub fn kind(&self) -> &str { - match self { - NodeUnderCursor::TsNode(node) => node.kind(), - NodeUnderCursor::CustomNode { kind, .. } => kind.as_str(), - } - } -} - -impl<'a> From> for NodeUnderCursor<'a> { - fn from(node: tree_sitter::Node<'a>) -> Self { - NodeUnderCursor::TsNode(node) - } -} - impl TryFrom<&str> for WrappingNode { type Error = String; @@ -135,7 +95,7 @@ pub struct TreeSitterContextParams<'a> { #[derive(Debug)] pub struct TreesitterContext<'a> { - pub node_under_cursor: Option>, + pub node_under_cursor: Option>, pub tree: &'a tree_sitter::Tree, pub text: &'a str, @@ -284,10 +244,9 @@ impl<'a> TreesitterContext<'a> { } pub fn get_node_under_cursor_content(&self) -> Option { - match self.node_under_cursor.as_ref()? { - NodeUnderCursor::TsNode(node) => self.get_ts_node_content(node), - NodeUnderCursor::CustomNode { text, .. } => Some(text.clone()), - } + self.node_under_cursor + .as_ref() + .and_then(|node| self.get_ts_node_content(node)) } fn gather_tree_context(&mut self) { @@ -339,7 +298,7 @@ impl<'a> TreesitterContext<'a> { // prevent infinite recursion – this can happen with ERROR nodes if current_node_kind == parent_node_kind && ["ERROR", "program"].contains(&parent_node_kind) { - self.node_under_cursor = Some(NodeUnderCursor::from(current_node)); + self.node_under_cursor = Some(current_node); return; } @@ -408,7 +367,7 @@ impl<'a> TreesitterContext<'a> { if current_node.child_count() == 0 || current_node.first_child_for_byte(self.position).is_none() { - self.node_under_cursor = Some(NodeUnderCursor::from(current_node)); + self.node_under_cursor = Some(current_node); return; } @@ -631,8 +590,7 @@ impl<'a> TreesitterContext<'a> { // `node.child_by_field_id(..)` does not work as expected let mut on_node = None; for child in node.children(cursor) { - // 28 is the id for "keyword_on" - if child.kind_id() == 28 { + if child.kind() == "keyword_on" { on_node = Some(child); } } @@ -644,71 +602,52 @@ impl<'a> TreesitterContext<'a> { } pub fn before_cursor_matches_kind(&self, kinds: &[&'static str]) -> bool { - self.node_under_cursor.as_ref().is_some_and(|under_cursor| { - match under_cursor { - NodeUnderCursor::TsNode(node) => { - let mut current = *node; - - // move up to the parent until we're at top OR we have a prev sibling - while current.prev_sibling().is_none() && current.parent().is_some() { - current = current.parent().unwrap(); - } - - current - .prev_sibling() - .is_some_and(|sib| kinds.contains(&sib.kind())) - } + self.node_under_cursor.as_ref().is_some_and(|node| { + let mut current = *node; - NodeUnderCursor::CustomNode { - previous_node_kind, .. - } => previous_node_kind - .as_ref() - .is_some_and(|k| kinds.contains(&k.as_str())), + // move up to the parent until we're at top OR we have a prev sibling + while current.prev_sibling().is_none() && current.parent().is_some() { + current = current.parent().unwrap(); } + + current + .prev_sibling() + .is_some_and(|sib| kinds.contains(&sib.kind())) }) } /// Verifies whether the node_under_cursor has the passed in ancestors in the right order. /// Note that you need to pass in the ancestors in the order as they would appear in the tree: /// - /// If the tree shows `relation > object_reference > identifier` and the "identifier" is a leaf node, + /// If the tree shows `relation > object_reference > any_identifier` and the "any_identifier" is a leaf node, /// you need to pass `&["relation", "object_reference"]`. pub fn matches_ancestor_history(&self, expected_ancestors: &[&'static str]) -> bool { - self.node_under_cursor - .as_ref() - .is_some_and(|under_cursor| match under_cursor { - NodeUnderCursor::TsNode(node) => { - let mut current = Some(*node); + self.node_under_cursor.as_ref().is_some_and(|node| { + let mut current = Some(*node); - for &expected_kind in expected_ancestors.iter().rev() { - current = current.and_then(|n| n.parent()); + for &expected_kind in expected_ancestors.iter().rev() { + current = current.and_then(|n| n.parent()); - match current { - Some(ancestor) if ancestor.kind() == expected_kind => continue, - _ => return false, - } - } - - true + match current { + Some(ancestor) if ancestor.kind() == expected_kind => continue, + _ => return false, } - NodeUnderCursor::CustomNode { .. } => false, - }) + } + + true + }) } /// Verifies whether the node_under_cursor has the passed in ancestors in the right order. /// Note that you need to pass in the ancestors in the order as they would appear in the tree: /// - /// If the tree shows `relation > object_reference > identifier` and the "identifier" is a leaf node, + /// If the tree shows `relation > object_reference > any_identifier` and the "any_identifier" is a leaf node, /// you need to pass `&["relation", "object_reference"]`. pub fn matches_one_of_ancestors(&self, expected_ancestors: &[&'static str]) -> bool { - self.node_under_cursor - .as_ref() - .is_some_and(|under_cursor| match under_cursor { - NodeUnderCursor::TsNode(node) => node - .parent() - .is_some_and(|p| expected_ancestors.contains(&p.kind())), - NodeUnderCursor::CustomNode { .. } => false, - }) + self.node_under_cursor.as_ref().is_some_and(|node| { + node.parent() + .is_some_and(|p| expected_ancestors.contains(&p.kind())) + }) } /// Checks whether the Node under the cursor is the nth child of the parent. @@ -723,9 +662,9 @@ impl<'a> TreesitterContext<'a> { /// * keyword_from [9..13] 'from' /// * relation [14..28] '"auth"."users"' /// * object_reference [14..28] '"auth"."users"' - /// * identifier [14..20] '"auth"' + /// * any_identifier [14..20] '"auth"' /// * . [20..21] '.' - /// * identifier [21..28] '"users"' + /// * any_identifier [21..28] '"users"' /// */ /// /// if node_under_cursor_is_nth_child(1) { @@ -735,32 +674,24 @@ impl<'a> TreesitterContext<'a> { /// } /// ``` pub fn node_under_cursor_is_nth_child(&self, nth: usize) -> bool { - self.node_under_cursor - .as_ref() - .is_some_and(|under_cursor| match under_cursor { - NodeUnderCursor::TsNode(node) => { - let mut cursor = node.walk(); - node.parent().is_some_and(|p| { - p.children(&mut cursor) - .nth(nth - 1) - .is_some_and(|n| n.id() == node.id()) - }) - } - NodeUnderCursor::CustomNode { .. } => false, + self.node_under_cursor.as_ref().is_some_and(|node| { + let mut cursor = node.walk(); + node.parent().is_some_and(|p| { + p.children(&mut cursor) + .nth(nth - 1) + .is_some_and(|n| n.id() == node.id()) }) + }) } /// Returns the number of siblings of the node under the cursor. pub fn num_siblings(&self) -> usize { self.node_under_cursor .as_ref() - .map(|n| match n { - NodeUnderCursor::TsNode(node) => { - // if there's no parent, we're on the top of the tree, - // where we have 0 siblings. - node.parent().map(|p| p.child_count() - 1).unwrap_or(0) - } - NodeUnderCursor::CustomNode { .. } => 0, + .map(|node| { + // if there's no parent, we're on the top of the tree, + // where we have 0 siblings. + node.parent().map(|p| p.child_count() - 1).unwrap_or(0) }) .unwrap_or(0) } @@ -769,34 +700,31 @@ impl<'a> TreesitterContext<'a> { pub fn node_under_cursor_is_within_field_name(&self, name: &str) -> bool { self.node_under_cursor .as_ref() - .map(|n| match n { - NodeUnderCursor::TsNode(node) => { - // It might seem weird that we have to check for the field_name from the parent, - // but TreeSitter wants it this way, since nodes often can only be named in - // the context of their parents. - let root_node = self.tree.root_node(); - let mut cursor = node.walk(); - let mut parent = node.parent(); - - while let Some(p) = parent { - if p == root_node { - break; - } - - if p.children_by_field_name(name, &mut cursor).any(|c| { - let r = c.range(); - // if the parent range contains the node range, the node is of the field_name. - r.start_byte <= node.start_byte() && r.end_byte >= node.end_byte() - }) { - return true; - } else { - parent = p.parent(); - } + .map(|node| { + // It might seem weird that we have to check for the field_name from the parent, + // but TreeSitter wants it this way, since nodes often can only be named in + // the context of their parents. + let root_node = self.tree.root_node(); + let mut cursor = node.walk(); + let mut parent = node.parent(); + + while let Some(p) = parent { + if p == root_node { + break; } - false + if p.children_by_field_name(name, &mut cursor).any(|c| { + let r = c.range(); + // if the parent range contains the node range, the node is of the field_name. + r.start_byte <= node.start_byte() && r.end_byte >= node.end_byte() + }) { + return true; + } else { + parent = p.parent(); + } } - NodeUnderCursor::CustomNode { .. } => false, + + false }) .unwrap_or(false) } @@ -858,8 +786,6 @@ mod tests { use pgt_test_utils::QueryWithCursorPosition; - use super::NodeUnderCursor; - fn get_tree(input: &str) -> tree_sitter::Tree { let mut parser = tree_sitter::Parser::new(); parser @@ -1091,17 +1017,12 @@ mod tests { let node = ctx.node_under_cursor.as_ref().unwrap(); - match node { - NodeUnderCursor::TsNode(node) => { - assert_eq!(ctx.get_ts_node_content(node), Some("select".into())); + assert_eq!(ctx.get_ts_node_content(node), Some("select".into())); - assert_eq!( - ctx.wrapping_clause_type, - Some(crate::context::WrappingClause::Select) - ); - } - _ => unreachable!(), - } + assert_eq!( + ctx.wrapping_clause_type, + Some(crate::context::WrappingClause::Select) + ); } } @@ -1126,12 +1047,7 @@ mod tests { let node = ctx.node_under_cursor.as_ref().unwrap(); - match node { - NodeUnderCursor::TsNode(node) => { - assert_eq!(ctx.get_ts_node_content(node), Some("from".into())); - } - _ => unreachable!(), - } + assert_eq!(ctx.get_ts_node_content(node), Some("from".into())); } #[test] @@ -1152,13 +1068,8 @@ mod tests { let node = ctx.node_under_cursor.as_ref().unwrap(); - match node { - NodeUnderCursor::TsNode(node) => { - assert_eq!(ctx.get_ts_node_content(node), Some("".into())); - assert_eq!(ctx.wrapping_clause_type, None); - } - _ => unreachable!(), - } + assert_eq!(ctx.get_ts_node_content(node), Some("".into())); + assert_eq!(ctx.wrapping_clause_type, None); } #[test] @@ -1181,13 +1092,8 @@ mod tests { let node = ctx.node_under_cursor.as_ref().unwrap(); - match node { - NodeUnderCursor::TsNode(node) => { - assert_eq!(ctx.get_ts_node_content(node), Some("fro".into())); - assert_eq!(ctx.wrapping_clause_type, Some(WrappingClause::Select)); - } - _ => unreachable!(), - } + assert_eq!(ctx.get_ts_node_content(node), Some("fro".into())); + assert_eq!(ctx.wrapping_clause_type, Some(WrappingClause::Select)); } #[test] @@ -1225,13 +1131,13 @@ mod tests { keyword_where [29..34] 'where' binary_expression [35..43] 'id = @id' field [35..37] 'id' - identifier [35..37] 'id' + any_identifier [35..37] 'id' = [38..39] '=' field [40..43] '@id' - identifier [40..43] '@id' + any_identifier [40..43] '@id' @ [40..41] '@' - You can see that the '@' is a child of the "identifier" but has a range smaller than its parent's. + You can see that the '@' is a child of the "any_identifier" but has a range smaller than its parent's. This would crash our context parsing because, at position 42, we weren't at the leaf node but also couldn't go to a child on that position. */ diff --git a/crates/pgt_treesitter/src/queries/insert_columns.rs b/crates/pgt_treesitter/src/queries/insert_columns.rs index e80718321..a46ab7b86 100644 --- a/crates/pgt_treesitter/src/queries/insert_columns.rs +++ b/crates/pgt_treesitter/src/queries/insert_columns.rs @@ -7,14 +7,8 @@ use super::QueryTryFrom; static TS_QUERY: LazyLock = LazyLock::new(|| { static QUERY_STR: &str = r#" - (insert - (object_reference) - (list - "("? - (column) @column - ","? - ")"? - ) + (insert_columns + (column_identifier) @column ) "#; tree_sitter::Query::new(&pgt_treesitter_grammar::LANGUAGE.into(), QUERY_STR) diff --git a/crates/pgt_treesitter/src/queries/parameters.rs b/crates/pgt_treesitter/src/queries/parameters.rs index a137cc8de..cab7b11eb 100644 --- a/crates/pgt_treesitter/src/queries/parameters.rs +++ b/crates/pgt_treesitter/src/queries/parameters.rs @@ -11,7 +11,7 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { [ (field (field_qualifier)? - (identifier) + (column_identifier) ) @reference (parameter) @parameter diff --git a/crates/pgt_treesitter/src/queries/relations.rs b/crates/pgt_treesitter/src/queries/relations.rs index 74a51dcbd..4f94d677c 100644 --- a/crates/pgt_treesitter/src/queries/relations.rs +++ b/crates/pgt_treesitter/src/queries/relations.rs @@ -11,17 +11,17 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (relation (object_reference . - (identifier) @schema_or_table + (any_identifier) @schema_or_table "."? - (identifier)? @table + (any_identifier)? @table )+ ) (insert (object_reference . - (identifier) @schema_or_table + (any_identifier) @schema_or_table "."? - (identifier)? @table + (any_identifier)? @table )+ ) (alter_table @@ -29,9 +29,9 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (keyword_table) (object_reference . - (identifier) @schema_or_table + (any_identifier) @schema_or_table "."? - (identifier)? @table + (any_identifier)? @table )+ ) "#; diff --git a/crates/pgt_treesitter/src/queries/select_columns.rs b/crates/pgt_treesitter/src/queries/select_columns.rs index d8fa1d16a..c1835fe32 100644 --- a/crates/pgt_treesitter/src/queries/select_columns.rs +++ b/crates/pgt_treesitter/src/queries/select_columns.rs @@ -14,7 +14,7 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (object_reference) @alias "." )? - (identifier) @column + (column_identifier) @column ) ) ","? diff --git a/crates/pgt_treesitter/src/queries/table_aliases.rs b/crates/pgt_treesitter/src/queries/table_aliases.rs index 9d771bf71..6c39b2e5e 100644 --- a/crates/pgt_treesitter/src/queries/table_aliases.rs +++ b/crates/pgt_treesitter/src/queries/table_aliases.rs @@ -10,12 +10,12 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (relation (object_reference . - (identifier) @schema_or_table + (any_identifier) @schema_or_table "."? - (identifier)? @table + (any_identifier)? @table ) (keyword_as)? - (identifier) @alias + (any_identifier) @alias ) "#; tree_sitter::Query::new(&pgt_treesitter_grammar::LANGUAGE.into(), QUERY_STR) diff --git a/crates/pgt_treesitter/src/queries/where_columns.rs b/crates/pgt_treesitter/src/queries/where_columns.rs index b3371518c..bf9fda4a4 100644 --- a/crates/pgt_treesitter/src/queries/where_columns.rs +++ b/crates/pgt_treesitter/src/queries/where_columns.rs @@ -16,7 +16,7 @@ static TS_QUERY: LazyLock = LazyLock::new(|| { (object_reference) @alias "." )? - (identifier) @column + (column_identifier) @column ) ) ) diff --git a/crates/pgt_treesitter_grammar/grammar.js b/crates/pgt_treesitter_grammar/grammar.js index 07a135e52..4f57b5ef8 100644 --- a/crates/pgt_treesitter_grammar/grammar.js +++ b/crates/pgt_treesitter_grammar/grammar.js @@ -28,6 +28,7 @@ module.exports = grammar({ [$.time], [$.timestamp], [$.grantable_on_function, $.grantable_on_table], + [$.any_identifier, $.column_identifier], ], precedences: ($) => [ @@ -263,6 +264,8 @@ module.exports = grammar({ keyword_storage: (_) => make_keyword("storage"), keyword_compression: (_) => make_keyword("compression"), + keyword_overriding: () => make_keyword("overriding"), + keyword_system: () => make_keyword("system"), keyword_policy: (_) => make_keyword("policy"), keyword_permissive: (_) => make_keyword("permissive"), keyword_restrictive: (_) => make_keyword("restrictive"), @@ -779,21 +782,7 @@ module.exports = grammar({ ), _show_statement: ($) => - seq( - $.keyword_show, - choice( - $._show_create, - $.keyword_all, // Postgres - $._show_tables // trino/presto - ) - ), - - _show_tables: ($) => - seq( - $.keyword_tables, - optional(seq($.keyword_from, $._qualified_field)), - optional(seq($.keyword_like, $._expression)) - ), + seq($.keyword_show, choice($._show_create, $.keyword_all)), _show_create: ($) => seq( @@ -814,8 +803,8 @@ module.exports = grammar({ cte: ($) => seq( - $.identifier, - optional(paren_list(field("argument", $.identifier), false)), + $.any_identifier, + optional(paren_list(field("argument", $.any_identifier), false)), $.keyword_as, optional(seq(optional($.keyword_not), $.keyword_materialized)), wrapped_in_parenthesis( @@ -871,7 +860,7 @@ module.exports = grammar({ function_argument: ($) => seq( optional($._argmode), - optional($.identifier), + optional($.any_identifier), $.type, optional(seq(choice($.keyword_default, "="), $.literal)) ), @@ -887,7 +876,7 @@ module.exports = grammar({ seq($.keyword_column, alias($._qualified_field, $.object_reference)), // TODO: constraint (on domain) // TODO: conversion - seq($.keyword_database, $.identifier), + seq($.keyword_database, $.any_identifier), // TODO: domain seq($.keyword_extension, $.object_reference), // TODO: event trigger @@ -906,20 +895,25 @@ module.exports = grammar({ // TODO: (procedural) language // TODO: procedure // TODO: publication - seq($.keyword_role, $.identifier), + seq($.keyword_role, $.any_identifier), // TODO: routine // TODO: rule - seq($.keyword_schema, $.identifier), + seq($.keyword_schema, $.any_identifier), seq($.keyword_sequence, $.object_reference), // TODO: server // TODO: statistics // TODO: subscription seq($.keyword_table, $.object_reference), - seq($.keyword_tablespace, $.identifier), + seq($.keyword_tablespace, $.any_identifier), // TODO: text search (configuration|dictionary|parser|template) // TODO: transform for - seq($.keyword_trigger, $.identifier, $.keyword_on, $.object_reference), - seq($.keyword_type, $.identifier), + seq( + $.keyword_trigger, + $.any_identifier, + $.keyword_on, + $.object_reference + ), + seq($.keyword_type, $.any_identifier), seq($.keyword_view, $.object_reference) ), @@ -985,7 +979,6 @@ module.exports = grammar({ $.table_partition, $.stored_as, $.storage_location, - $.table_sort, $.row_format, seq($.keyword_tblproperties, paren_list($.table_option, true)), seq($.keyword_without, $.keyword_oids), @@ -997,7 +990,7 @@ module.exports = grammar({ seq( $.keyword_with, paren_list( - seq($.identifier, optional(seq("=", choice($.literal, $.array)))), + seq($.any_identifier, optional(seq("=", choice($.literal, $.array)))), true ) ), @@ -1031,7 +1024,7 @@ module.exports = grammar({ seq( $.keyword_create, $.keyword_policy, - $.identifier, + $.any_identifier, $.keyword_on, $.object_reference, optional( @@ -1055,13 +1048,13 @@ module.exports = grammar({ alter_policy: ($) => seq( - seq($.keyword_alter, $.keyword_policy, $.identifier), + seq($.keyword_alter, $.keyword_policy, $.any_identifier), optional( seq( $.keyword_on, $.object_reference, choice( - seq($.keyword_rename, $.keyword_to, $.identifier), + seq($.keyword_rename, $.keyword_to, $.any_identifier), $.policy_to_role, optional($.check_or_using_clause) ) @@ -1074,7 +1067,7 @@ module.exports = grammar({ $.keyword_to, comma_list( choice( - $.identifier, + $.any_identifier, $.keyword_public, $.keyword_current_user, $.keyword_current_role, @@ -1090,7 +1083,7 @@ module.exports = grammar({ $.keyword_drop, $.keyword_policy, optional($._if_exists), - $.identifier + $.any_identifier ), optional( seq( @@ -1153,7 +1146,7 @@ module.exports = grammar({ choice( $.literal, $.keyword_default, - $.identifier, + $.any_identifier, $.keyword_on, $.keyword_off ) @@ -1168,14 +1161,14 @@ module.exports = grammar({ seq( $.keyword_session, $.keyword_authorization, - choice($.identifier, $.keyword_default) + choice($.any_identifier, $.keyword_default) ), - seq($.keyword_role, choice($.identifier, $.keyword_none)) + seq($.keyword_role, choice($.any_identifier, $.keyword_none)) ) ), seq( $.keyword_constraints, - choice($.keyword_all, comma_list($.identifier, true)), + choice($.keyword_all, comma_list($.any_identifier, true)), choice($.keyword_deferred, $.keyword_immediate) ), seq($.keyword_transaction, $._transaction_mode), @@ -1202,7 +1195,7 @@ module.exports = grammar({ $.keyword_view, optional($._if_not_exists), $.object_reference, - optional(paren_list($.identifier, false)), + optional(paren_list($.any_identifier, false)), $.keyword_as, $.create_query, optional( @@ -1289,7 +1282,7 @@ module.exports = grammar({ function_declaration: ($) => seq( - $.identifier, + $.any_identifier, $.type, optional( seq( @@ -1349,7 +1342,7 @@ module.exports = grammar({ // regard to the defined language to match either sql, plsql or // plpgsql. Currently the function_body_statement support only sql. And // maybe for other language the function_body should be a string. - $.identifier + $.any_identifier ), function_volatility: ($) => @@ -1391,7 +1384,7 @@ module.exports = grammar({ _operator_class: ($) => seq( - field("opclass", $.identifier), + field("opclass", $.any_identifier), optional( field( "opclass_parameters", @@ -1407,7 +1400,7 @@ module.exports = grammar({ field("function", $.invocation), field("column", $._column) ), - optional(seq($.keyword_collate, $.identifier)), + optional(seq($.keyword_collate, $.any_identifier)), optional($._operator_class), optional($.direction), optional(seq($.keyword_nulls, choice($.keyword_first, $.keyword_last))) @@ -1453,21 +1446,21 @@ module.exports = grammar({ choice( seq( optional($._if_not_exists), - $.identifier, - optional(seq($.keyword_authorization, $.identifier)) + $.any_identifier, + optional(seq($.keyword_authorization, $.any_identifier)) ), - seq($.keyword_authorization, $.identifier) + seq($.keyword_authorization, $.any_identifier) ) ) ), _with_settings: ($) => seq( - field("name", $.identifier), + field("name", $.any_identifier), optional("="), field( "value", - choice($.identifier, alias($._single_quote_string, $.literal)) + choice($.any_identifier, alias($._single_quote_string, $.literal)) ) ), @@ -1476,7 +1469,7 @@ module.exports = grammar({ $.keyword_create, $.keyword_database, optional($._if_not_exists), - $.identifier, + $.any_identifier, optional($.keyword_with), repeat($._with_settings) ), @@ -1485,14 +1478,14 @@ module.exports = grammar({ seq( $.keyword_create, choice($.keyword_user, $.keyword_role, $.keyword_group), - $.identifier, + $.any_identifier, optional($.keyword_with), repeat(choice($._user_access_role_config, $._role_options)) ), _role_options: ($) => choice( - field("option", $.identifier), + field("option", $.any_identifier), seq( $.keyword_valid, $.keyword_until, @@ -1521,7 +1514,7 @@ module.exports = grammar({ $.keyword_admin, $.keyword_user ), - comma_list($.identifier, true) + comma_list($.any_identifier, true) ), create_sequence: ($) => @@ -1575,13 +1568,13 @@ module.exports = grammar({ $.keyword_create, $.keyword_extension, optional($._if_not_exists), - $.identifier, + $.any_identifier, optional($.keyword_with), - optional(seq($.keyword_schema, $.identifier)), + optional(seq($.keyword_schema, $.any_identifier)), optional( seq( $.keyword_version, - choice($.identifier, alias($._literal_string, $.literal)) + choice($.any_identifier, alias($._literal_string, $.literal)) ) ), optional($.keyword_cascade) @@ -1592,7 +1585,7 @@ module.exports = grammar({ $.keyword_create, optional($._or_replace), // mariadb - optional(seq($.keyword_definer, "=", $.identifier)), + optional(seq($.keyword_definer, "=", $.any_identifier)), optional($.keyword_constraint), // sqlite optional($._temporary), @@ -1623,7 +1616,7 @@ module.exports = grammar({ choice($.keyword_old, $.keyword_new), $.keyword_table, optional($.keyword_as), - $.identifier + $.any_identifier ), seq( $.keyword_for, @@ -1631,7 +1624,10 @@ module.exports = grammar({ choice($.keyword_row, $.keyword_statement), // mariadb optional( - seq(choice($.keyword_follows, $.keyword_precedes), $.identifier) + seq( + choice($.keyword_follows, $.keyword_precedes), + $.any_identifier + ) ) ), seq($.keyword_when, wrapped_in_parenthesis($._expression)) @@ -1648,7 +1644,7 @@ module.exports = grammar({ $.keyword_insert, seq( $.keyword_update, - optional(seq($.keyword_of, comma_list($.identifier, true))) + optional(seq($.keyword_of, comma_list($.column_identifier, true))) ), $.keyword_delete, $.keyword_truncate @@ -1665,7 +1661,7 @@ module.exports = grammar({ seq( $.keyword_as, $.column_definitions, - optional(seq($.keyword_collate, $.identifier)) + optional(seq($.keyword_collate, $.any_identifier)) ), seq($.keyword_as, $.keyword_enum, $.enum_elements), seq( @@ -1744,8 +1740,6 @@ module.exports = grammar({ $.add_constraint, $.drop_constraint, $.alter_column, - $.modify_column, - $.change_column, $.drop_column, $.rename_object, $.rename_column, @@ -1768,7 +1762,7 @@ module.exports = grammar({ seq( $.keyword_add, optional($.keyword_constraint), - $.identifier, + $.any_identifier, $.constraint ), @@ -1777,7 +1771,7 @@ module.exports = grammar({ $.keyword_drop, $.keyword_constraint, optional($._if_exists), - $.identifier, + $.any_identifier, optional($._drop_behavior) ), @@ -1786,7 +1780,7 @@ module.exports = grammar({ // TODO constraint management $.keyword_alter, optional($.keyword_column), - field("name", $.identifier), + $.column_identifier, choice( seq( choice($.keyword_set, $.keyword_drop), @@ -1824,46 +1818,24 @@ module.exports = grammar({ ) ), - modify_column: ($) => - seq( - $.keyword_modify, - optional($.keyword_column), - optional($._if_exists), - $.column_definition, - optional($.column_position) - ), - - change_column: ($) => - seq( - $.keyword_change, - optional($.keyword_column), - optional($._if_exists), - field("old_name", $.identifier), - $.column_definition, - optional($.column_position) - ), - column_position: ($) => - choice( - $.keyword_first, - seq($.keyword_after, field("col_name", $.identifier)) - ), + choice($.keyword_first, seq($.keyword_after, $.column_identifier)), drop_column: ($) => seq( $.keyword_drop, optional($.keyword_column), optional($._if_exists), - field("name", $.identifier) + $.column_identifier ), rename_column: ($) => seq( $.keyword_rename, optional($.keyword_column), - field("old_name", $.identifier), + $.column_identifier, $.keyword_to, - field("new_name", $.identifier) + field("new_name", $.any_identifier) ), alter_view: ($) => @@ -1885,17 +1857,17 @@ module.exports = grammar({ seq( $.keyword_alter, $.keyword_schema, - $.identifier, + $.any_identifier, choice($.keyword_rename, $.keyword_owner), $.keyword_to, - $.identifier + $.any_identifier ), alter_database: ($) => seq( $.keyword_alter, $.keyword_database, - $.identifier, + $.any_identifier, optional($.keyword_with), choice( seq($.rename_object), @@ -1904,12 +1876,15 @@ module.exports = grammar({ $.keyword_reset, choice( $.keyword_all, - field("configuration_parameter", $.identifier) + field("configuration_parameter", $.any_identifier) ) ), seq( $.keyword_set, - choice(seq($.keyword_tablespace, $.identifier), $.set_configuration) + choice( + seq($.keyword_tablespace, $.any_identifier), + $.set_configuration + ) ) ) ), @@ -1918,17 +1893,17 @@ module.exports = grammar({ seq( $.keyword_alter, choice($.keyword_role, $.keyword_group, $.keyword_user), - choice($.identifier, $.keyword_all), + choice($.any_identifier, $.keyword_all), choice( $.rename_object, seq(optional($.keyword_with), repeat($._role_options)), seq( - optional(seq($.keyword_in, $.keyword_database, $.identifier)), + optional(seq($.keyword_in, $.keyword_database, $.any_identifier)), choice( seq($.keyword_set, $.set_configuration), seq( $.keyword_reset, - choice($.keyword_all, field("option", $.identifier)) + choice($.keyword_all, field("option", $.any_identifier)) ) ) ) @@ -1937,13 +1912,13 @@ module.exports = grammar({ set_configuration: ($) => seq( - field("option", $.identifier), + field("option", $.any_identifier), choice( seq($.keyword_from, $.keyword_current), seq( choice($.keyword_to, "="), choice( - field("parameter", $.identifier), + field("parameter", $.any_identifier), $.literal, $.keyword_default ) @@ -1956,7 +1931,7 @@ module.exports = grammar({ $.keyword_alter, $.keyword_index, optional($._if_exists), - $.identifier, + $.any_identifier, choice( $.rename_object, seq( @@ -1967,13 +1942,13 @@ module.exports = grammar({ $.keyword_statistics, alias($._natural_number, $.literal) ), - seq($.keyword_reset, paren_list($.identifier, false)), + seq($.keyword_reset, paren_list($.any_identifier, false)), seq( $.keyword_set, choice( - seq($.keyword_tablespace, $.identifier), + seq($.keyword_tablespace, $.any_identifier), paren_list( - seq($.identifier, "=", field("value", $.literal)), + seq($.any_identifier, "=", field("value", $.literal)), false ) ) @@ -2028,7 +2003,7 @@ module.exports = grammar({ $.keyword_set, choice( choice($.keyword_logged, $.keyword_unlogged), - seq($.keyword_schema, $.identifier) + seq($.keyword_schema, $.any_identifier) ) ) ) @@ -2038,7 +2013,7 @@ module.exports = grammar({ seq( $.keyword_alter, $.keyword_type, - $.identifier, + $.any_identifier, choice( $.change_ownership, $.set_schema, @@ -2046,9 +2021,9 @@ module.exports = grammar({ seq( $.keyword_rename, $.keyword_attribute, - $.identifier, + $.any_identifier, $.keyword_to, - $.identifier, + $.any_identifier, optional($._drop_behavior) ), seq( @@ -2072,23 +2047,23 @@ module.exports = grammar({ ), seq( choice( - seq($.keyword_add, $.keyword_attribute, $.identifier, $.type), + seq($.keyword_add, $.keyword_attribute, $.any_identifier, $.type), seq( $.keyword_drop, $.keyword_attribute, optional($._if_exists), - $.identifier + $.any_identifier ), seq( $.keyword_alter, $.keyword_attribute, - $.identifier, + $.any_identifier, optional(seq($.keyword_set, $.keyword_data)), $.keyword_type, $.type ) ), - optional(seq($.keyword_collate, $.identifier)), + optional(seq($.keyword_collate, $.any_identifier)), optional($._drop_behavior) ) ) @@ -2136,7 +2111,7 @@ module.exports = grammar({ $.keyword_drop, $.keyword_schema, optional($._if_exists), - $.identifier, + $.any_identifier, optional($._drop_behavior) ), @@ -2145,7 +2120,7 @@ module.exports = grammar({ $.keyword_drop, $.keyword_database, optional($._if_exists), - $.identifier, + $.any_identifier, optional($.keyword_with), optional($.keyword_force) ), @@ -2155,7 +2130,7 @@ module.exports = grammar({ $.keyword_drop, choice($.keyword_group, $.keyword_role, $.keyword_user), optional($._if_exists), - $.identifier + $.any_identifier ), drop_type: ($) => @@ -2182,7 +2157,7 @@ module.exports = grammar({ $.keyword_index, optional($.keyword_concurrently), optional($._if_exists), - field("name", $.identifier), + field("name", $.any_identifier), optional($._drop_behavior), optional(seq($.keyword_on, $.object_reference)) ), @@ -2192,7 +2167,7 @@ module.exports = grammar({ $.keyword_drop, $.keyword_extension, optional($._if_exists), - comma_list($.identifier, true), + comma_list($.any_identifier, true), optional(choice($.keyword_cascade, $.keyword_restrict)) ), @@ -2209,9 +2184,10 @@ module.exports = grammar({ seq($.keyword_rename, $.keyword_to, $.object_reference), set_schema: ($) => - seq($.keyword_set, $.keyword_schema, field("schema", $.identifier)), + seq($.keyword_set, $.keyword_schema, field("schema", $.any_identifier)), - change_ownership: ($) => seq($.keyword_owner, $.keyword_to, $.identifier), + change_ownership: ($) => + seq($.keyword_owner, $.keyword_to, $.any_identifier), object_id: ($) => seq( @@ -2227,14 +2203,18 @@ module.exports = grammar({ object_reference: ($) => choice( seq( - field("database", $.identifier), + field("database", $.any_identifier), ".", - field("schema", $.identifier), + field("schema", $.any_identifier), ".", - field("name", $.identifier) + field("name", $.any_identifier) ), - seq(field("schema", $.identifier), ".", field("name", $.identifier)), - field("name", $.identifier) + seq( + field("schema", $.any_identifier), + ".", + field("name", $.any_identifier) + ), + field("name", $.any_identifier) ), _copy_statement: ($) => @@ -2270,7 +2250,7 @@ module.exports = grammar({ $.keyword_quote, $.keyword_encoding ), - alias($._literal_string, $.identifier) + alias($._literal_string, $.any_identifier) ), seq( choice( @@ -2290,33 +2270,42 @@ module.exports = grammar({ insert: ($) => seq( - choice($.keyword_insert, $.keyword_replace), + $.keyword_insert, + $.keyword_into, + $.object_reference, + optional($._alias), + optional($.insert_columns), optional( - choice( - $.keyword_low_priority, - $.keyword_delayed, - $.keyword_high_priority + seq( + $.keyword_overriding, + choice($.keyword_user, $.keyword_system), + $.keyword_value ) ), - optional($.keyword_ignore), - optional( - choice( - $.keyword_into, - $.keyword_overwrite // Spark SQL - ) + choice( + seq($.keyword_default, $.keyword_values), + $.insert_values, + $._select_statement ), - $.object_reference, - optional($.table_partition), // Spark SQL - optional(seq($.keyword_as, field("alias", $.identifier))), - // TODO we need a test for `insert...set` - choice($._insert_values, $._set_values), - optional(choice($._on_conflict, $._on_duplicate_key_update)) + optional($._on_conflict) ), + insert_values: ($) => + comma_list( + seq( + $.keyword_values, + paren_list(choice($._expression, $.keyword_default), true) + ), + true + ), + + insert_columns: ($) => paren_list($.column_identifier, true), + _on_conflict: ($) => seq( $.keyword_on, $.keyword_conflict, + // todo: conflict target seq( $.keyword_do, choice( @@ -2326,27 +2315,13 @@ module.exports = grammar({ ) ), - _on_duplicate_key_update: ($) => - seq( - $.keyword_on, - $.keyword_duplicate, - $.keyword_key, - $.keyword_update, - $.assignment_list - ), - assignment_list: ($) => seq($.assignment, repeat(seq(",", $.assignment))), - _insert_values: ($) => - seq( - optional(alias($._column_list, $.list)), - choice(seq($.keyword_values, comma_list($.list, true)), $._dml_read) - ), - _set_values: ($) => seq($.keyword_set, comma_list($.assignment, true)), _column_list: ($) => paren_list(alias($._column, $.column), true), - _column: ($) => choice($.identifier, alias($._literal_string, $.literal)), + _column: ($) => + choice($.column_identifier, alias($._literal_string, $.literal)), _update_statement: ($) => seq($.update, optional($.returning)), @@ -2377,10 +2352,31 @@ module.exports = grammar({ ), $.keyword_then, choice( - $.keyword_delete, + // merge_insert + seq( + $.keyword_insert, + optional(paren_list($.column_identifier, true)), + optional( + seq( + $.keyword_overriding, + choice($.keyword_system, $.keyword_user), + $.keyword_value + ) + ), + choice( + seq($.keyword_default, $.keyword_values), + seq( + $.keyword_values, + paren_list(choice($._expression, $.keyword_default), true) + ) + ) + ), + // merge_update seq($.keyword_update, $._set_values), - seq($.keyword_insert, $._insert_values), - optional($.where) + // merge_delete + $.keyword_delete, + + seq($.keyword_do, $.keyword_nothing) ) ), @@ -2554,9 +2550,6 @@ module.exports = grammar({ ) ), - table_sort: ($) => - seq($.keyword_sort, $.keyword_by, paren_list($.identifier, true)), - table_partition: ($) => seq( choice( @@ -2572,7 +2565,7 @@ module.exports = grammar({ $.keyword_partition ), choice( - paren_list($.identifier, false), // postgres & Impala (CTAS) + paren_list($.any_identifier, false), // postgres & Impala (CTAS) $.column_definitions, // impala/hive external tables paren_list($._key_value_pair, true) // Spark SQL ) @@ -2580,7 +2573,7 @@ module.exports = grammar({ _key_value_pair: ($) => seq( - field("key", $.identifier), + field("key", $.any_identifier), "=", field("value", alias($._literal_string, $.literal)) ), @@ -2614,17 +2607,17 @@ module.exports = grammar({ $.keyword_default, $.keyword_character, $.keyword_set, - $.identifier + $.any_identifier ), - seq($.keyword_collate, $.identifier), + seq($.keyword_collate, $.any_identifier), field("name", $.keyword_default), seq( field( "name", - choice($.keyword_engine, $.identifier, $._literal_string) + choice($.keyword_engine, $.any_identifier, $._literal_string) ), "=", - field("value", choice($.identifier, $._literal_string)) + field("value", choice($.any_identifier, $._literal_string)) ) ), @@ -2638,7 +2631,7 @@ module.exports = grammar({ column_definition: ($) => seq( - field("name", $._column), + $.any_identifier, field("type", $.type), repeat($._column_constraint) ), @@ -2653,7 +2646,7 @@ module.exports = grammar({ seq( $.keyword_references, $.object_reference, - paren_list($.identifier, true), + paren_list($.column_identifier, true), repeat( seq( $.keyword_on, @@ -2665,7 +2658,7 @@ module.exports = grammar({ seq( $.keyword_set, choice($.keyword_null, $.keyword_default), - optional(paren_list($.identifier, true)) + optional(paren_list($.any_identifier, true)) ) ) ) @@ -2722,7 +2715,7 @@ module.exports = grammar({ _constraint_literal: ($) => seq( $.keyword_constraint, - field("name", $.identifier), + field("name", $.any_identifier), choice(seq($._primary_key, $.ordered_columns), seq($._check_constraint)) ), @@ -2752,13 +2745,13 @@ module.exports = grammar({ ), $.keyword_index ), - optional(field("name", $.identifier)), + optional(field("name", $.any_identifier)), $.ordered_columns, optional( seq( $.keyword_references, $.object_reference, - paren_list($.identifier, true), + paren_list($.column_identifier, true), repeat( seq( $.keyword_on, @@ -2770,7 +2763,7 @@ module.exports = grammar({ seq( $.keyword_set, choice($.keyword_null, $.keyword_default), - optional(paren_list($.identifier, true)) + optional(paren_list($.any_identifier, true)) ) ) ) @@ -2817,10 +2810,10 @@ module.exports = grammar({ $.keyword_end ), - field: ($) => field("name", $.identifier), + field: ($) => field("name", $.column_identifier), _qualified_field: ($) => - seq(optional($.field_qualifier), field("name", $.identifier)), + seq(optional($.field_qualifier), $.column_identifier), field_qualifier: ($) => seq(prec.left(optional_parenthesis($.object_reference)), "."), @@ -2889,7 +2882,7 @@ module.exports = grammar({ field( "start", choice( - $.identifier, + $.any_identifier, $.binary_expression, alias($._literal_string, $.literal), alias($._integer, $.literal) @@ -2902,7 +2895,7 @@ module.exports = grammar({ field( "end", choice( - $.identifier, + $.any_identifier, $.binary_expression, alias($._literal_string, $.literal), alias($._integer, $.literal) @@ -2937,7 +2930,12 @@ module.exports = grammar({ ), window_clause: ($) => - seq($.keyword_window, $.identifier, $.keyword_as, $.window_specification), + seq( + $.keyword_window, + $.any_identifier, + $.keyword_as, + $.window_specification + ), window_specification: ($) => wrapped_in_parenthesis( @@ -2952,10 +2950,11 @@ module.exports = grammar({ seq( $.invocation, $.keyword_over, - choice($.identifier, $.window_specification) + choice($.any_identifier, $.window_specification) ), - _alias: ($) => seq(optional($.keyword_as), field("alias", $.identifier)), + _alias: ($) => + seq(optional($.keyword_as), field("alias", $.any_identifier)), from: ($) => seq( @@ -2994,7 +2993,7 @@ module.exports = grammar({ choice($.keyword_force, $.keyword_use, $.keyword_ignore), $.keyword_index, optional(seq($.keyword_for, $.keyword_join)), - wrapped_in_parenthesis(field("index_name", $.identifier)) + wrapped_in_parenthesis(field("index_name", $.any_identifier)) ), join: ($) => @@ -3034,8 +3033,8 @@ module.exports = grammar({ optional( seq( $.keyword_as, - field("alias", $.identifier), - paren_list($.identifier, false) + field("alias", $.any_identifier), + paren_list($.any_identifier, false) ) ) ) @@ -3058,8 +3057,8 @@ module.exports = grammar({ choice($.invocation, $.subquery), optional( choice( - seq($.keyword_as, field("alias", $.identifier)), - field("alias", $.identifier) + seq($.keyword_as, field("alias", $.any_identifier)), + field("alias", $.any_identifier) ) ), $.keyword_on, @@ -3074,8 +3073,8 @@ module.exports = grammar({ choice($.invocation, $.subquery), optional( choice( - seq($.keyword_as, field("alias", $.identifier)), - field("alias", $.identifier) + seq($.keyword_as, field("alias", $.any_identifier)), + field("alias", $.any_identifier) ) ) ), @@ -3162,13 +3161,13 @@ module.exports = grammar({ $.grantable_on_all ) ), - $.identifier + $.any_identifier ), grantable_targets: ($) => choice( - seq($._grantable, comma_list($.identifier, false)), - comma_list($.identifier, true) + seq($._grantable, comma_list($.any_identifier, false)), + comma_list($.any_identifier, true) ), _grantable: ($) => @@ -3225,12 +3224,12 @@ module.exports = grammar({ ), $.keyword_in, $.keyword_schema, - comma_list($.identifier, true) + comma_list($.any_identifier, true) ), role_specification: ($) => choice( - seq(optional($.keyword_group), $.identifier), + seq(optional($.keyword_group), $.any_identifier), $.keyword_public, $.keyword_current_role, $.keyword_current_user, @@ -3376,9 +3375,9 @@ module.exports = grammar({ prec.left( precedence, seq( - field("left", $._expression), - field("operator", operator), - field("right", $._expression) + field("binary_expr_left", $._expression), + field("binary_expr_operator", operator), + field("binary_expr_right", $._expression) ) ) ), @@ -3386,9 +3385,9 @@ module.exports = grammar({ prec.left( precedence, seq( - field("left", $._expression), - field("operator", operator), - field("right", $._expression) + field("binary_expr_left", $._expression), + field("binary_expr_operator", operator), + field("binary_expr_right", $._expression) ) ) ), @@ -3396,9 +3395,9 @@ module.exports = grammar({ prec.left( precedence, seq( - field("left", $._expression), - field("operator", operator), - field("right", choice($.list, $.subquery)) + field("binary_expr_left", $._expression), + field("binary_expr_operator", operator), + field("binary_expr_right", choice($.list, $.subquery)) ) ) ) @@ -3503,11 +3502,14 @@ module.exports = grammar({ ), _bit_string: ($) => seq(/[bBxX]'([^']|'')*'/, repeat(/'([^']|'')*'/)), // The identifier should be followed by a string (no parenthesis allowed) - _string_casting: ($) => seq($.identifier, $._single_quote_string), + _string_casting: ($) => seq($.any_identifier, $._single_quote_string), bang: (_) => "!", - identifier: ($) => + any_identifier: ($) => $._any_identifier, + column_identifier: ($) => $._any_identifier, + + _any_identifier: ($) => choice( $._identifier, $._double_quote_string, From 55bf258bf8ed7b7d369fce9291e69c3e93e85fc9 Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 28 Oct 2025 12:20:42 +0100 Subject: [PATCH 8/8] forgot that one --- crates/pgt_completions/src/relevance/filtering.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/pgt_completions/src/relevance/filtering.rs b/crates/pgt_completions/src/relevance/filtering.rs index e0f6f6a26..42962782a 100644 --- a/crates/pgt_completions/src/relevance/filtering.rs +++ b/crates/pgt_completions/src/relevance/filtering.rs @@ -115,14 +115,8 @@ impl CompletionFilter<'_> { CompletionRelevanceData::Table(_) => match clause { WrappingClause::From | WrappingClause::Update => true, -<<<<<<< HEAD WrappingClause::RevokeStatement | WrappingClause::GrantStatement => ctx .matches_ancestor_history(&["grantable_on_table", "object_reference"]), -======= - WrappingClause::RevokeStatement => { - ctx.matches_ancestor_history(&["revoke_on_table", "object_reference"]) - } ->>>>>>> 01a1456068d4c3fa353cd57b000447fcb13da950 WrappingClause::Join { on_node: None } => true, WrappingClause::Join { on_node: Some(on) } => ctx