Blog Post

SQL Server Blog
3 MIN READ

mssql-python 1.9.0: Row-friendly Bulk Copy, smarter NULL parameters, and a more portable wheel

DavidLevy's avatar
DavidLevy
Icon for Microsoft rankMicrosoft
Jun 12, 2026

We just shipped mssql-python 1.9.0, the official Microsoft SQL driver for Python. This release focuses on day-to-day ergonomics for data loading, a long-standing correctness gap around NULL parameter binding, and a build change that makes the published wheels work on clean macOS and Linux machines.

pip install --upgrade mssql-python

Highlights

Bulk Copy now accepts Row objects (and lists)

Bulk Copy previously required tuples. In 1.9.0 you can hand it Row objects straight from a SELECT, or plain lists, and the driver converts each row to a tuple internally before passing data to the Rust backend.

rows = source_cursor.execute(
    "SELECT id, display_name, created_at FROM users"
).fetchall()

target_cursor.bulkcopy("staging.users", rows)

The common "fetch from one table, bulk-insert into another" pattern just works, with no manual row reshaping and no type errors on the boundary.

NULL parameters now resolve to the right SQL type

When you bound None to a parameter, the driver used to fall back to SQL_VARCHAR. That was fine for character columns and quietly wrong for everything else, especially VARBINARY and all-NULL columns where the server had no other type signal to lean on.

1.9.0 adds a thread-safe per-statement cache for SQLDescribeParam results, so NULL parameters resolve to the parameter's actual declared type. The cache is invalidated when a new statement is prepared, which also cuts redundant round-trips to the server during repeated executions of the same prepared statement.

simdutf is now statically linked into the extension

The published wheels (especially the macOS universal2 wheel) previously dynamically linked simdutf against a path that only existed on the CI build machine. On a clean install, import mssql_python could fail with missing-symbol or dlopen errors.

The build no longer calls find_package(simdutf). FetchContent is used unconditionally, simdutf is built as a static library, and its symbols are embedded directly in the extension. There is nothing for end users to do; reinstall the wheel and the import works on a clean machine. Thanks to @edgarrmondragon for the contribution.

Bug fixes worth calling out

  • executemany with large Decimal values. Batch inserts of Decimal values larger than the SQL Server MONEY range raised an SQL_C_NUMERIC type-mismatch at runtime. executemany now binds DECIMAL / NUMERIC parameters as SQL_C_CHAR and sizes the column to fit the longest string representation, so large-decimal batches (including NULLs and multi-column inserts) succeed.
  • Exception pickle round-trips. ConnectionStringParseError and the DB-API exception subclasses now implement __reduce__, so driver exceptions survive pickle / copy.deepcopy with every attribute intact. multiprocessing, distributed task queues, and anything that ships exceptions across process boundaries no longer lose context on the way through.
  • nextset() and PRINT output. nextset() now collects diagnostic messages whenever SQL returns SQL_SUCCESS_WITH_INFO. Previously, PRINT output from secondary result sets in multi-statement batches and stored procedures was silently dropped after the first set.
  • executemany data-at-execution path with Row objects. The DAE fallback used by large columns such as varchar(max) only recognized primitive types, so passing Row objects in that path failed. The fallback now converts Row to a tuple before mapping types.
  • Fetch methods now type-check under ty. Catalog and metadata result-set handling no longer monkey-patches fetchone, fetchmany, and fetchall as instance attributes. A cached _column_map is built instead, so the fetch APIs stay proper class methods and static type checkers like ty stop tripping on cursor fetch calls.

Upgrading

For most users, pip install --upgrade mssql-python is all you need. If you had any workarounds for NULL parameter typing (manually setting inputsizes, casting in SQL, or sending sentinel values), you can drop it. If you were converting fetched rows to tuples before handing them to bulkcopy or executemany, that's no longer required either.

Thanks

Thanks to everyone who filed issues, sent repros, and reviewed PRs in this cycle. The NULL parameter fix, the Row handling in Bulk Copy and executemany, and the simdutf linking change all came directly from user-reported scenarios, and the simdutf change was a community contribution from @edgarrmondragon.

Full changelog and PR list: microsoft/mssql-python releases.

Updated Jun 12, 2026
Version 2.0
No CommentsBe the first to comment