diff --git a/CHANGELOG.md b/CHANGELOG.md index ba31cb08b..8ed1fba6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - 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 - Plugin upgrades for built-in drivers (MySQL, PostgreSQL, SQLite, ClickHouse, Redis, etc.) no longer revert to the bundled version after restart. Discovery now scans both built-in and user plugin directories, picks the newest version by `CFBundleShortVersionString`, and only prunes user copies that are older than or equal to the built-in (#1192) - Concurrent plugin installs from the registry can no longer race past the `isInstalling` guard; the lock now wraps `installFromRegistry` and `updateFromRegistry` end-to-end diff --git a/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift b/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift index 8ec6668be..df8bdf97e 100644 --- a/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift +++ b/Plugins/MySQLDriverPlugin/MariaDBPluginConnection.swift @@ -78,11 +78,15 @@ func mysqlTypeToString(_ fieldPtr: UnsafePointer) -> String { let flags = UInt(field.flags) let length = field.length - // MariaDB extended metadata: detect JSON stored as LONGTEXT (best-effort) + // MariaDB extended metadata: detect JSON stored as LONGTEXT. + // `MARIADB_CONST_STRING` is length-prefixed (not null-terminated), so we must read + // exactly `attr.length` bytes. `String(cString:)` would scan past the buffer into + // adjacent memory and intermittently fail the comparison when that memory is non-zero. var attr = MARIADB_CONST_STRING() if mariadb_field_attr(&attr, fieldPtr, MARIADB_FIELD_ATTR_FORMAT_NAME) == 0, let str = attr.str, attr.length > 0, - String(cString: str) == "json" { + let value = String(data: Data(bytes: str, count: Int(attr.length)), encoding: .utf8), + value == "json" { return "JSON" }