From 281daf197e4a83ed808f6d9b22a2a2c6c7a6d598 Mon Sep 17 00:00:00 2001 From: blankll Date: Fri, 4 Sep 2026 00:33:25 +0800 Subject: [PATCH] chore(deps): upgrade sqlparser 0.55 to 0.62 0.62 parses statement shapes 0.55 could not: `WITH ... DELETE`, `WITH ... MERGE` and DELETE/MERGE inside CTE bodies are now valid ASTs (Query body carries SetExpr::Delete/SetExpr::Merge), so the MCP classifier no longer fails closed on them. The recursive classifier gains the Delete/Merge arms (both the statement body and nested CTE positions) and routes them to execute_delete / execute_write respectively. Migration for breaking AST changes: - sql_write.rs: the 0.55 SET-family variants (SetVariable, SetNames, SetNamesDefault, SetRole, SetSessionParam, SetTimeZone, SetTransaction) are consolidated into Statement::Set - sql_service.rs: SelectItem gained ExprWithAliases (Spark) - projected names now use its first alias, falling back to the expression name Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src-tauri/Cargo.lock | 26 +++++++------- src-tauri/Cargo.toml | 2 +- src-tauri/src/capabilities/sql_write.rs | 48 ++++++++++++++++++++----- src-tauri/src/database/sql_service.rs | 4 +++ 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 27b49b6..4b6cc47 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1770,7 +1770,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2062,7 +2062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3037,7 +3037,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -3057,7 +3057,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -5083,7 +5083,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls 0.23.40", - "socket2 0.5.10", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -5120,9 +5120,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.4", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5744,7 +5744,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5845,7 +5845,7 @@ dependencies = [ "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6566,9 +6566,9 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.55.0" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4521174166bac1ff04fe16ef4524c70144cd29682a45978978ca3d7f4e0be11" +checksum = "13c6d1b651dc4edf07eead2a0c6c78016ce971bc2c10da5266861b13f25e7cec" dependencies = [ "log", "recursive", @@ -7271,7 +7271,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8361,7 +8361,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0c116fa..11d032a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -98,7 +98,7 @@ zip = { version = "2", features = [ "deflate" ] } # TOML parsing (drivers.toml) toml = "0.8" sha2 = "0.10" -sqlparser = "0.55" +sqlparser = "0.62" russh = "0.60" axum = "0.8" portpicker = "0.1.1" diff --git a/src-tauri/src/capabilities/sql_write.rs b/src-tauri/src/capabilities/sql_write.rs index afc076f..3c13f37 100644 --- a/src-tauri/src/capabilities/sql_write.rs +++ b/src-tauri/src/capabilities/sql_write.rs @@ -72,7 +72,7 @@ fn combine_kind(a: SqlKind, b: SqlKind) -> SqlKind { /// Classify a Query, walking into its body and every CTE. /// -/// sqlparser represents `WITH … INSERT/UPDATE` (PostgreSQL data-modifying +/// sqlparser represents `WITH … INSERT/UPDATE/DELETE/MERGE` (data-modifying /// CTEs) as a `Query` whose body carries the DML statement, so checking only /// the top-level variant would let write statements through as Read. fn classify_query(query: &Query) -> SqlKind { @@ -92,7 +92,10 @@ fn classify_set_expr(body: &SetExpr) -> SqlKind { SetExpr::SetOperation { left, right, .. } => { combine_kind(classify_set_expr(left), classify_set_expr(right)) } - SetExpr::Insert(stmt) | SetExpr::Update(stmt) => classify_statement(stmt), + SetExpr::Insert(stmt) + | SetExpr::Update(stmt) + | SetExpr::Delete(stmt) + | SetExpr::Merge(stmt) => classify_statement(stmt), } } @@ -166,13 +169,7 @@ fn classify_statement(stmt: &Statement) -> SqlKind { | Statement::Comment { .. } | Statement::LockTables { .. } | Statement::UnlockTables { .. } - | Statement::SetVariable { .. } - | Statement::SetNames { .. } - | Statement::SetNamesDefault { .. } - | Statement::SetRole { .. } - | Statement::SetSessionParam(_) - | Statement::SetTimeZone { .. } - | Statement::SetTransaction { .. } + | Statement::Set(_) | Statement::Commit { .. } | Statement::Rollback { .. } | Statement::Savepoint { .. } @@ -495,6 +492,39 @@ mod tests { ); } + #[test] + fn classifies_with_delete_as_delete() { + assert_eq!( + classify_sql( + "postgres", + "WITH x AS (SELECT 1 AS id) DELETE FROM t WHERE id IN (SELECT id FROM x)" + ) + .unwrap(), + SqlKind::Delete + ); + assert_eq!( + classify_sql( + "postgres", + "WITH d AS (DELETE FROM t RETURNING id) SELECT * FROM d" + ) + .unwrap(), + SqlKind::Delete + ); + } + + #[test] + fn classifies_with_merge_as_write() { + assert_eq!( + classify_sql( + "postgres", + "WITH x AS (SELECT 1 AS id) MERGE INTO t USING x ON t.id = x.id \ + WHEN MATCHED THEN UPDATE SET c = 1" + ) + .unwrap(), + SqlKind::Write + ); + } + #[test] fn read_guard_rejects_with_insert() { let err = ensure_read_only( diff --git a/src-tauri/src/database/sql_service.rs b/src-tauri/src/database/sql_service.rs index 671e214..396a4cd 100644 --- a/src-tauri/src/database/sql_service.rs +++ b/src-tauri/src/database/sql_service.rs @@ -485,6 +485,10 @@ fn extract_select_items(items: &[SelectItem]) -> Vec<(String, usize)> { let name = match item { SelectItem::UnnamedExpr(expr) => expr_to_name(expr), SelectItem::ExprWithAlias { alias, .. } => alias.value.clone(), + SelectItem::ExprWithAliases { expr, aliases } => aliases + .first() + .map(|a| a.value.clone()) + .unwrap_or_else(|| expr_to_name(expr)), SelectItem::QualifiedWildcard(kind, _) => match kind { SelectItemQualifiedWildcardKind::ObjectName(obj) => obj.to_string() + ".*", SelectItemQualifiedWildcardKind::Expr(e) => format!("({}).*", expr_to_name(e)),