From e967421290a48d1160015cfc6ba8a99624c00c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Mon, 11 May 2026 16:30:12 +0700 Subject: [PATCH] fix(plugin-mysql): classify BINARY/VARBINARY as blob to prevent accidental data wipe on edit --- CHANGELOG.md | 1 + Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed1fba6e..16a2ffa40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- MySQL/MariaDB: `BINARY(N)` and `VARBINARY(N)` columns are no longer classified as text. The plugin's type switch returned `"CHAR"` for type code 254 and `"VARCHAR"` for type code 253 without consulting the binary flag, so binary columns came through as `.text` instead of `.blob`. The data grid then allowed inline edit on those cells, the editor opened with an empty field (because `PluginCellValue.bytes(_).asText` returns `nil`), and focus-out marked the cell as modified-to-empty. A single accidental click was enough to schedule destruction of the original bytes on Save. Case 253 now returns `"VARBINARY"` and case 254 returns `"BINARY"` when the binary flag is set, so these cells route through the blob editor (chevron-triggered) instead of inline text edit - Data grid: dropdown / boolean / date / JSON / blob cells now show or hide their chevron accessory when the cell's editability changes (refresh, safe-mode toggle, view-to-table switch). The performance pass in #1212 made `configure` skip `needsDisplay = true` when nothing visible changed, but the editability flag was updated unconditionally without flagging a redraw, so chevrons could stick in their stale state (visible in `audit_log.payload` JSON cells after save / refresh / reopen) - MySQL/MariaDB: JSON column detection no longer flickers across refreshes. The plugin reads `mariadb_field_attr(MARIADB_FIELD_ATTR_FORMAT_NAME)` to recognize JSON-stored-as-LONGTEXT. The returned `MARIADB_CONST_STRING` is a length-prefixed buffer (not null-terminated), but we were reading it with `String(cString:)`, which scans bytes until the next `\0` and so reads past the buffer into adjacent memory. When that memory happened to contain a null byte the comparison passed and we tagged the column `JSON`; when it contained garbage the comparison failed and we fell through to `LONGTEXT`. Read exactly `attr.length` bytes via `String(data: Data(bytes:count:), encoding: .utf8)` - Welcome window sometimes failed to open on launch (and Dock-icon clicks did nothing) when the previous session restored only main connection windows. `WindowOpenerBridge` was mounted only in the Welcome scene and used `.onAppear`, so if Welcome got `orderOut`'d before its first appearance the bridge never wired and every `openWelcome()` call queued forever. The bridge now mounts in all four SwiftUI scenes (Welcome, ConnectionForm, IntegrationsActivity, Settings) and uses `.task` so wiring fires reliably whenever any scene materializes diff --git a/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift b/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift index df8bdf97e..04a344991 100644 --- a/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift +++ b/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift @@ -131,8 +131,8 @@ func mysqlTypeToString(_ fieldPtr: UnsafePointer) -> String { } else { return length > 65_535 ? "LONGTEXT" : "TEXT" } - case 253: return "VARCHAR" - case 254: return "CHAR" + case 253: return isBinary ? "VARBINARY" : "VARCHAR" + case 254: return isBinary ? "BINARY" : "CHAR" case 255: return "GEOMETRY" default: return "UNKNOWN" }