diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e91eda13..037221cab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- SQL Server: Unicode characters (Thai, CJK, etc.) in nvarchar/nchar/ntext columns displaying as question marks - Globe+F (fn+F) fullscreen shortcut not working in SwiftUI lifecycle app ## [0.26.0] - 2026-03-29 diff --git a/Plugins/MSSQLDriverPlugin/CFreeTDS/include/sybdb.h b/Plugins/MSSQLDriverPlugin/CFreeTDS/include/sybdb.h index 21a772d35..e09e109ae 100644 --- a/Plugins/MSSQLDriverPlugin/CFreeTDS/include/sybdb.h +++ b/Plugins/MSSQLDriverPlugin/CFreeTDS/include/sybdb.h @@ -49,6 +49,7 @@ typedef struct loginrec LOGINREC; #define DBSETUSER 2 #define DBSETPWD 3 #define DBSETAPP 5 // 4 is unused; real FreeTDS DBSETAPP = 5 +#define DBSETCHARSET 7 // Client charset for dbsetlname() — controls string encoding // Convenience macros (match FreeTDS sybdb.h) #define DBSETLHOST(x, y) dbsetlname((x), (y), DBSETHOST) diff --git a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift index 4299f181e..d31469650 100644 --- a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift +++ b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift @@ -196,6 +196,7 @@ private final class FreeTDSConnection: @unchecked Sendable { _ = dbsetlname(login, user, Int32(DBSETUSER)) _ = dbsetlname(login, password, Int32(DBSETPWD)) _ = dbsetlname(login, "TablePro", Int32(DBSETAPP)) + _ = dbsetlname(login, "UTF-8", Int32(DBSETCHARSET)) _ = dbsetlversion(login, UInt8(DBVERSION_74)) freetdsLastError = "" @@ -371,10 +372,12 @@ private final class FreeTDSConnection: @unchecked Sendable { return String(bytes: UnsafeBufferPointer(start: ptr, count: Int(srcLen)), encoding: .utf8) ?? String(bytes: UnsafeBufferPointer(start: ptr, count: Int(srcLen)), encoding: .isoLatin1) case Int32(SYBNCHAR), Int32(SYBNVARCHAR), Int32(SYBNTEXT): - let data = Data(bytes: ptr, count: Int(srcLen)) - return String(data: data, encoding: .utf16LittleEndian) + // With client charset UTF-8, FreeTDS converts UTF-16 wire data to UTF-8 + // but may still report the original nvarchar type token + return String(bytes: UnsafeBufferPointer(start: ptr, count: Int(srcLen)), encoding: .utf8) + ?? String(data: Data(bytes: ptr, count: Int(srcLen)), encoding: .utf16LittleEndian) default: - let bufSize: DBINT = 64 + let bufSize: DBINT = 256 var buf = [BYTE](repeating: 0, count: Int(bufSize)) let converted = buf.withUnsafeMutableBufferPointer { bufPtr in dbconvert(proc, srcType, ptr, srcLen, Int32(SYBCHAR), bufPtr.baseAddress, bufSize)