Enhancements
• Accept Row objects in bulk copy (#615)
What changed: bulkcopy now accepts Row objects and lists directly, converting each row to a tuple internally before passing data to the Rust backend, instead of requiring manually constructed tuples.
Who benefits: Anyone bulk-loading data that was fetched from a query (which returns Row objects) or assembled as lists.
Impact: Rows from a SELECT can be bulk-inserted straight into a target table without manual tuple conversion or type errors.
Bug Fixes
• Always statically link simdutf (#608)
What changed: Removed the find_package(simdutf) call so FetchContent is always used, building simdutf as a static library and embedding its symbols directly into the extension. Previously the macOS universal2 wheel dynamically linked simdutf against a Homebrew path baked in at CI build time.
Who benefits: macOS and Linux users installing the published wheels on machines without simdutf at the CI build path.
Impact: The driver imports successfully on a clean machine — no more missing-symbol / dlopen failures at runtime.
PR #608 | GitHub Issues #607, #628 - Thanks @edgarrmondragon for the contribution!
• Fix executemany SQL_C_NUMERIC mismatch for large decimals (#611)
What changed: executemany now overrides the C type to SQL_C_CHAR (string binding) for DECIMAL/NUMERIC parameters and adjusts column size to fit the longest string representation.
Who benefits: Users inserting Decimal values that exceed the SQL Server MONEY range via executemany.
Impact: Large decimal batch inserts (including NULLs and multi-column inserts) succeed instead of raising a runtime type-mismatch error.
• Fix incorrect type fallback for NULL parameters (#614)
What changed: Added a thread-safe per-statement cache for SQLDescribeParam results when binding None/NULL parameters, replacing the previous SQL_VARCHAR fallback that produced incorrect types. The cache is invalidated when a new statement is prepared.
Who benefits: Users binding NULL values, especially for all-NULL columns and VARBINARY types.
Impact: NULL parameters resolve to the correct type instead of a wrong SQL_VARCHAR fallback, and redundant server round-trips are eliminated.
• Fix exception pickle/unpickle round-trip (#616)
What changed: Added __reduce__ to ConnectionStringParseError and all DB-API exception subclasses so they serialize/deserialize correctly with all attributes preserved.
Who benefits: Users running across process boundaries — multiprocessing, distributed task queues, or anything that pickles exceptions.
Impact: Driver exceptions survive pickle/unpickle and deep copies without losing data or failing to reconstruct.
• Capture PRINT messages in nextset() (#618)
What changed: nextset() now captures diagnostic messages when SQL returns SQL_SUCCESS_WITH_INFO, so PRINT output from every result set is collected, not just the first.
Who benefits: Users running multi-statement batches or stored procedures that emit PRINT / informational messages across result sets.
Impact: All diagnostic messages are preserved as you advance through result sets with nextset().
• Handle Row objects in executemany DAE fallback path (#630)
What changed: executemany now converts Row objects to tuples before execution in the data-at-execution (DAE) fallback path, where _map_sql_type only recognized primitive types.
Who benefits: Users passing Row objects to executemany when writing to large columns such as varchar(max).
Impact: Writing fetched Row data to varchar(max) columns via executemany works instead of failing with a type error.
• Fix fetchone/fetchmany/fetchall type-checking under ty (#631)
What changed: Refactored catalog/metadata result-set handling to build a cached _column_map instead of dynamically reassigning fetchone, fetchmany, and fetchall as instance attributes.
Who benefits: Users running static type checkers such as ty against code using the driver.
Impact: Fetch methods remain proper class methods, so static type-checking no longer fails on cursor fetch calls.