Skip to content

Commit a76cd12

Browse files
committed
FIX: Decode Raw UTF-16 data from Conn.getinfo()
1 parent da875a7 commit a76cd12

File tree

3 files changed

+69
-33
lines changed

3 files changed

+69
-33
lines changed

mssql_python/connection.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,21 +1198,21 @@ def getinfo(self, info_type: int) -> Union[str, int, bool, None]:
11981198
# Make sure we use the correct amount of data based on length
11991199
actual_data = data[:length]
12001200

1201-
# Now decode the string data
1202-
try:
1203-
return actual_data.decode("utf-8").rstrip("\0")
1204-
except UnicodeDecodeError:
1201+
# SQLGetInfoW returns UTF-16LE encoded strings
1202+
# Try encodings in order: UTF-16LE (Windows), UTF-8, Latin-1
1203+
for encoding in ("utf-16-le", "utf-8", "latin1"):
12051204
try:
1206-
return actual_data.decode("latin1").rstrip("\0")
1207-
except Exception as e:
1208-
logger.debug(
1209-
"error",
1210-
"Failed to decode string in getinfo: %s. "
1211-
"Returning None to avoid silent corruption.",
1212-
e,
1213-
)
1214-
# Explicitly return None to signal decoding failure
1215-
return None
1205+
return actual_data.decode(encoding).rstrip("\0")
1206+
except UnicodeDecodeError:
1207+
continue
1208+
1209+
# All decodings failed
1210+
logger.debug(
1211+
"error",
1212+
"Failed to decode string in getinfo with any supported encoding. "
1213+
"Returning None to avoid silent corruption.",
1214+
)
1215+
return None
12161216
else:
12171217
# If it's not bytes, return as is
12181218
return data

mssql_python/pybind/build.bat

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
@echo off
22
setlocal enabledelayedexpansion
33

4-
REM Usage: build.bat [ARCH], If ARCH is not specified, it defaults to x64.
4+
REM Usage: build.bat [ARCH] [MODE], If ARCH is not specified, it defaults to x64.
5+
REM MODE can be 'profile' or '--profile' to enable profiling instrumentation
56
set ARCH=%1
67
if "%ARCH%"=="" set ARCH=x64
8+
9+
REM Check for profiling mode
10+
set PROFILING_MODE=0
11+
if /i "%2"=="profile" set PROFILING_MODE=1
12+
if /i "%2"=="--profile" set PROFILING_MODE=1
13+
if /i "%1"=="profile" (
14+
set PROFILING_MODE=1
15+
set ARCH=x64
16+
)
17+
if /i "%1"=="--profile" (
18+
set PROFILING_MODE=1
19+
set ARCH=x64
20+
)
21+
722
echo [DIAGNOSTIC] Target Architecture set to: %ARCH%
23+
if %PROFILING_MODE%==1 (
24+
echo [MODE] C++ Profiling: ENABLED
25+
) else (
26+
echo [MODE] C++ Profiling: DISABLED
27+
)
828

929
REM Clean up main build directory if it exists
1030
echo Checking for main build directory...
@@ -109,8 +129,13 @@ if errorlevel 1 (
109129
)
110130

111131
REM Now invoke CMake with correct source path (options first, path last!)
112-
echo [DIAGNOSTIC] Running CMake configure with: cmake -A %PLATFORM_NAME% -DARCHITECTURE=%ARCH% "%SOURCE_DIR:~0,-1%"
113-
cmake -A %PLATFORM_NAME% -DARCHITECTURE=%ARCH% "%SOURCE_DIR:~0,-1%"
132+
if %PROFILING_MODE%==1 (
133+
echo [DIAGNOSTIC] Running CMake configure with profiling: cmake -A %PLATFORM_NAME% -DARCHITECTURE=%ARCH% -DCMAKE_CXX_FLAGS="/DENABLE_PROFILING" -DCMAKE_C_FLAGS="/DENABLE_PROFILING" "%SOURCE_DIR:~0,-1%"
134+
cmake -A %PLATFORM_NAME% -DARCHITECTURE=%ARCH% -DCMAKE_CXX_FLAGS="/DENABLE_PROFILING" -DCMAKE_C_FLAGS="/DENABLE_PROFILING" "%SOURCE_DIR:~0,-1%"
135+
) else (
136+
echo [DIAGNOSTIC] Running CMake configure with: cmake -A %PLATFORM_NAME% -DARCHITECTURE=%ARCH% "%SOURCE_DIR:~0,-1%"
137+
cmake -A %PLATFORM_NAME% -DARCHITECTURE=%ARCH% "%SOURCE_DIR:~0,-1%"
138+
)
114139
echo [DIAGNOSTIC] CMake configure exit code: %errorlevel%
115140
if errorlevel 1 (
116141
echo [ERROR] CMake configuration failed
@@ -157,22 +182,6 @@ if exist "%OUTPUT_DIR%\%PYD_NAME%" (
157182
echo [WARNING] PDB file !PDB_NAME! not found in output directory.
158183
)
159184

160-
setlocal enabledelayedexpansion
161-
for %%I in ("%SOURCE_DIR%..") do (
162-
set PARENT_DIR=%%~fI
163-
)
164-
echo [DIAGNOSTIC] Parent is: !PARENT_DIR!
165-
166-
set VCREDIST_DLL_PATH=!PARENT_DIR!\libs\windows\!ARCH!\vcredist\msvcp140.dll
167-
echo [DIAGNOSTIC] Looking for msvcp140.dll at "!VCREDIST_DLL_PATH!"
168-
169-
if exist "!VCREDIST_DLL_PATH!" (
170-
copy /Y "!VCREDIST_DLL_PATH!" "%SOURCE_DIR%\.."
171-
echo [SUCCESS] Copied msvcp140.dll from !VCREDIST_DLL_PATH! to "%SOURCE_DIR%\.."
172-
) else (
173-
echo [ERROR] Could not find msvcp140.dll at "!VCREDIST_DLL_PATH!"
174-
exit /b 1
175-
)
176185
) else (
177186
echo [ERROR] Could not find built .pyd file: %PYD_NAME%
178187
REM Exit with an error code here if the .pyd file is not found

tests/test_003_connection.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5471,6 +5471,33 @@ def test_getinfo_basic_driver_info(db_connection):
54715471
pytest.fail(f"getinfo failed for basic driver info: {e}")
54725472

54735473

5474+
def test_getinfo_string_encoding_utf16(db_connection):
5475+
"""Test that string values from getinfo are properly decoded from UTF-16."""
5476+
5477+
# Test string info types that should not contain null bytes
5478+
string_info_types = [
5479+
("SQL_DRIVER_VER", sql_const.SQL_DRIVER_VER.value),
5480+
("SQL_DRIVER_NAME", sql_const.SQL_DRIVER_NAME.value),
5481+
("SQL_DRIVER_ODBC_VER", sql_const.SQL_DRIVER_ODBC_VER.value),
5482+
("SQL_SERVER_NAME", sql_const.SQL_SERVER_NAME.value),
5483+
]
5484+
5485+
for name, info_type in string_info_types:
5486+
result = db_connection.getinfo(info_type)
5487+
5488+
if result is not None:
5489+
# Verify it's a string
5490+
assert isinstance(result, str), \
5491+
f"{name}: Expected str, got {type(result).__name__}"
5492+
5493+
# Verify no null bytes (indicates UTF-16 decoded as UTF-8 bug)
5494+
assert '\x00' not in result, \
5495+
f"{name} contains null bytes, likely UTF-16/UTF-8 encoding mismatch: {repr(result)}"
5496+
5497+
# Verify it's not empty (optional, but good sanity check)
5498+
assert len(result) > 0, f"{name} returned empty string"
5499+
5500+
54745501
def test_getinfo_sql_support(db_connection):
54755502
"""Test SQL support and conformance info types."""
54765503

0 commit comments

Comments
 (0)