Repository navigation
Releases
5.5.0: DECIMAL fixes for PostgreSQL, and crash and memory-safety fixes
Version 5.5.0 has been released with wheels for Python 3.11 - 3.15
This release follows 5.4.0 closely. It fixes DECIMAL and NUMERIC values that 5.4.0 and earlier
versions read or wrote incorrectly, mostly with PostgreSQL. It also fixes crashes and
memory-safety bugs found in a review of the C++ code prompted by #1515. Most of the
memory-safety bugs need an unusual setting, value, or driver to reach, but we recommend that
everyone upgrade. It is 5.5.0 rather than 5.4.1 because it adds Connection.decimal_mode;
there will be no 5.4.1.
Check your data
- Writing a
DecimalNaN or Infinity stored 0. In 5.4.0 and earlier,Cursor.execute()
with aDecimal('NaN'),Decimal('sNaN'),Decimal('Infinity'), orDecimal('-Infinity')
parameter raisedSystemError, but only after the statement had run with the value 0. If
such a value could have reached an insert or update that was then committed (with autocommit
on, for example), the table may hold zeros where NaN or Infinity was intended. - PostgreSQL: some values read wrongly in 5.4.0.
NaN,Infinity, and-Infinitywere
read asDecimal('0'), and values with more than 38 digits or more than 38 decimal places
were read as a different number. The values in the database are correct, but values read
with 5.4.0 and then stored or used elsewhere may not be. (5.3.0 raised an exception for NaN
and Infinity, and read long values correctly.)
Upgrading from 5.4.0
-
If you followed the 5.4.0 notes and set
Connection.fetch_decimal_as_string = True, it still
works, but it now raises aFutureWarning, and it will be removed in 6.0. Use
Connection.decimal_modeinstead:- PostgreSQL: remove the line. The new default reads every value correctly.
True
selects the old text path, which cannot read NaN or Infinity, andFalseselects
'binary', which reads them as 0. - Other databases:
decimal_mode = 'locale'behaves exactly like
fetch_decimal_as_string = True. If your driver never adds thousands separators,
decimal_mode = 'text'is stricter and is not affected by the locale.
- PostgreSQL: remove the line. The new default reads every value correctly.
-
Reading
fetch_decimal_as_stringalso raisesFutureWarning. To silence it until you
update your code:warnings.filterwarnings("ignore", message=".*fetch_decimal_as_string", category=FutureWarning)
New features
Connection.decimal_modeselects how DECIMAL and NUMERIC values are read:'binary': into a SQL_NUMERIC_STRUCT, which is not affected by the locale. This is the
5.4.0 behavior and the default except for PostgreSQL. (With PostgreSQL it reads NaN and
Infinity as 0.)'text': as text from drivers that never add thousands separators. The decimal point can
be.or,, whatever the locale, andNaN,Infinity, and-Infinityare read.
Anything else raisesDataErrorrather than being guessed at. This is the default for
PostgreSQL.'locale': the text path from before 5.4, which removes thousands separators and currency
symbols usingsetDecimalSeparator().
- PostgreSQL:
DecimalNaN, Infinity, and -Infinity can be written withCursor.execute().
They are sent as text, so databases that can store them do; others raise their own error
(SQL Server raisesDataError).fast_executemanyand table-valued parameters cannot send
them and raiseProgrammingError, as does a signaling NaN.
Behavior changes
- PostgreSQL connections read DECIMAL and NUMERIC as text by default (
decimal_mode == 'text').
Reads are as fast as with the SQL_NUMERIC_STRUCT. Connection.fetch_decimal_as_stringis deprecated; see "Upgrading from 5.4.0".DecimalNaN and Infinity parameters are sent to the database instead of raising
SystemError; see above.- With
decimal_mode = 'binary', a value the driver reports does not fit a
SQL_NUMERIC_STRUCT raisesDataErrorinstead of being returned as a wrong number. With
PostgreSQL this is a value with more than 38 digits, counting the column's decimal places,
so a column such asnumeric(60, 45)cannot be read in binary mode. Use'text', the
PostgreSQL default. setinputsizes()with a(sqltype, size)tuple and no scale keeps the parameter's scale
instead of using the size as the scale, so(SQL_DECIMAL, 18)no longer binds as
decimal(18, 18)and rejects values like 123.fast_executemanyraisesProgrammingErrorfor aDecimalorintwith more than 38
digits bound as a numeric. 5.4.0 sent 39-digit values to the driver.- Binding a
Decimalwith more than 32767 digits after the decimal point raises
ProgrammingError. statistics()andtablePrivileges()raiseValueErrorfor a name longer than 32767
characters, andexecute()raisesValueErrorfor a statement longer than 2147483647
characters. These lengths were passed to the driver truncated.DecimalandUUIDsubclasses whoseas_tuple()orbytes_lereturn invalid values raise
TypeError.- Comparing rows raises any error from comparing their values, instead of ignoring it.
connect()with a connection string that is not astrsays "argument 1 must be a str".
Bug fixes
- PostgreSQL:
fast_executemanyraised "Converting decimal loses precision" for anyDecimal
with more than 6 decimal places, and for fractional values after the same cursor had run the
statement with a whole number. The precision and scale now come from the values, including
whensetinputsizes()gives a size but no scale. - The text path no longer misreads values when the driver's decimal point differs from
pyodbc's separator, for example afterlocale.setlocale()is called after importing pyodbc
(#753). This needsdecimal_mode = 'text', the PostgreSQL default. - After an error while sending long (data-at-execution) parameters, the cursor can be used
again instead of failing with "Function sequence error". - With
setdecoding(pyodbc.SQL_WMETADATA, encoding='utf-8'), catalog functions such as
columns()returned no rows. setDecimalSeparator()with a character that is special in regular expressions, such as],
broke reading numbers withdecimal_mode = 'locale'.- A
Connection.readvar_initsizeof 1 or any odd number made fetching text fail or return
wrong text, and with the Microsoft driver a value of 1 hung. - Text parameters of 2 GB or more were sent with a truncated length.
- An
attrs_beforekey too large for an ODBC attribute ID set a different attribute; for
example,2**40 + 102set autocommit (102). Such keys now raiseOverflowError. - Some errors were reported as
SystemErrorinstead of the real exception:- catalog functions with a name that cannot be encoded (now
UnicodeEncodeError) nextset()on a closed cursor (nowProgrammingError)- a TVP
Decimalcolumn with a value that is not aDecimal, on some Python versions (now
TypeError) - queries after
del pyodbc.lowercaseordel pyodbc.native_uuid(the defaults are now
used)
- catalog functions with a name that cannot be encoded (now
nextset()returned an exception instead of raising it when the driver could not report the
next result's columns.- Fix a memory leak when reading a text or binary column fails.
Crash fixes
These could be triggered by ordinary Python code:
- Reusing a
fast_executemanycursor after an error while sending long parameters fast_executemanywithDecimalNaN or Infinity, aDecimalwith a very large exponent, or
a negative scale from the driver (the last by @ziyziy89 in
#1515)- Table-valued parameters with non-
strtype or schema names, an empty first row, rows that
change size during execution, or rows and values from sequences that create new objects
(such asrange) del row[i]anddel row.name, which now raiseTypeError- Unpickling a
Rowwith an inconsistent column map - Catalog functions such as
tables()andcolumns()on a closed cursor fetchval()withrows_as_dicts- Fetching after a result's column names could not be decoded
connect()with anencodingthat cannot be encoded, or unusualattrs_beforevaluesConnection.set_attr()with values that cannot be encoded or are too largeTimeFromTicks()with a float or an out-of-range value (out-of-range values now raise
OverflowError)connect()afterdel pyodbc.poolingordel pyodbc.odbcversion
Memory-safety fixes
Reads or writes outside a buffer, or use of freed memory. Each needs an unusual setting,
value, driver, or subclass to reach. The review that found these and the crashes above is in
#1521, and what it found but 5.5.0 does not fix is in #1522.
Connection.readvar_initsize = 1, which let the driver write before the start of the buffer- Drivers that report a longer length than the buffer they were given when reading text or
binary columns,getinfo()strings,searchescape, error messages, orCursor.messages,
and, on Windows,dataSources() - Error messages and
Cursor.messageswith a driver manager that uses a 4-byteSQLWCHAR - Catalog function names with
setdecoding(pyodbc.SQL_WMETADATA, encoding='utf-8'), and names
of exactly 65533 characters passed tostatistics()ortablePrivileges(), which the driver
read past the end of attrs_beforestring values, which the driver manager could read past the end of- A
bytearrayparameter resized by another thread while a statement runs;bytearray
values are now copied when bound DecimalandUUIDsubclasses whoseas_tuple()orbytes_lereturn invalid values- A
Decimalwith an extremely large negative exponent sent withexecute() fast_executemanybatches large enough to overflow the buffer size- A locale whose decimal point is not a Latin-1 character
- A driver reporting a
sql_variantnested in asql_variant - Reading GUID columns with
pyodbc.native_uuid = True, which passed a length of the wrong
size and can fail ...
5.4.0 with wheels for Python 3.15
Version 5.4.0 has been released with wheels for Python 3.11 - 3.15
Python support
- Add support for Python 3.15, including wheels, all on PyPI now, by @keitherskine
- Drop support for EOL Python 3.10 by @keitherskine in #1508
- Drop support for EOL Python 3.9 by @mkleehammer in #1499
Behavior changes
- DECIMAL and NUMERIC values are now fetched using SQL_NUMERIC_STRUCT by default, so they are
no longer affected by the locale's decimal separator. SetConnection.fetch_decimal_as_string = True
to restore the previous text-based behavior. By @bkline in #1470
New features
- Support returning rows as dictionaries (
Cursor.rows_as_dicts) by @bkline in #1487 - Add support for SQLTablePrivileges (
Cursor.tablePrivileges()) by @bkline in #1463 - Expose the native ODBC handles (
pyodbc.henv,Connection.hdbc,Cursor.hstmt) by @bkline in #1493 - Make driver_completion configurable (
connect(driver_completion=...)) by @bkline in #1488 - Allow setting of connection string attributes (
Connection.set_attr()) by @bkline in #1465 - Support filtering DSN by scope (
dataSources(scope=...)) by @bkline in #1461 - Make initial ReadVarColumn buffer size configurable (
Connection.readvar_initsize) by @bkline in #1490 - Add flag for buggy SQLGetDiagRecW driver behavior (
Connection.compat_diagrec_byte_length) by @bkline in #1464 - Allow fetch of DB2 BLOB by @bkline in #1462
- Fetch messages when DAE kicks in by @bkline in #1468
Bug fixes
- Fix segfaults when a connect fails by @rad-pat in #1510
- Avoid heap corruption with NULLs in TVPs by @bkline in #1452
- Scan all TVP rows before binding by @bkline in #1481
- Keep SetConnectAttrW buffers alive by @bkline in #1472
- attrs_before: snapshot bytearray into immutable bytes by @mkleehammer in #1500
- Fix fast_executemany rounding Decimal parameters to context precision by @M0d3v1 in #1511
- Size the create_name_map name buffer for UTF-32 metadata by @ziyziy89 in #1512
- Fix reference leak in pyodbc.dataSources() by @apoorvdarshan in #1504
- Fix reference leak and double-DECREF in pyodbc.drivers() by @apoorvdarshan in #1505
- Fix .statistics() issue with Unicode table names by @gordthompson in #1460
- Remove incorrect check when unpickling Row by @bkline in #1466
Build and tests
- Enable assertions for test builds and run SQLite tests in CI by @shramov in #1448
- Prevent tests from failing with FreeTDS by @bkline in #1473
- Remove test dependency coupling for testing UUID retrieval by @bkline in #1475
- Replace custom macros with Py_STRINGIFY() by @bkline in #1485
- Use ODBC Driver 18.6.2.1 for Windows tests now that the TVP crash is fixed by @mkleehammer in #1498
- CI, test, and typing improvements throughout, and SQL Server 2025 in CI, by @keitherskine
Thanks
- @keitherskine for once again keeping the wheel builds and CI current, including Python 3.15.
- @bkline for the large majority of this release's features and fixes.
New Contributors
- @rad-pat made their first contribution in #1510
- @apoorvdarshan made their first contribution in #1504
- @M0d3v1 made their first contribution in #1511
- @ziyziy89 made their first contribution in #1512
Full Changelog: 5.3.0...5.4.0
5.3.0 with wheels for Python 3.14
Version 5.3.0 has been released with wheels for versions 3.9 - 3.14
What's Changed
- Add support for Python 3.14 including wheels, all on PyPI now, by @keitherskine
- Drop support for EOL Python 3.8 by @MatthijsKok in #1445
- Port SQLite tests to pytest by @shramov in #1440
- Use
HOMEBREW_PREFIXto locate Homebrew by @RA80533 in #1212 - Do not perform type check on NULL pointer by @shramov in #1439
New Contributors
- @keitherskine gets all the credit for wheel support and adding 3.14 to the build list.
- @MatthijsKok made their first contribution in #1445
- @shramov made their first contribution in #1440
- @RA80533 made their first contribution in #1212
Full Changelog: 5.2.0...5.3.0
Release 5.2.0 with wheels for Python 3.13
Thanks to @keitherskine there are now wheels for 5.13. There are aiso a few bug fixes that were ready to go.
5.1.0 with Mac ARM (M1 & M2) binaries
Mac M1 & M2 binaries are available going forward.
GitHub added new Mac ARM action runners which is what we use to build each release.
Thanks to @keitherskine who made all this happen. Not only did he write the original release code for all of the other versions, he jumped on the new Mac runner as soon as it was available.
I'd also like to thank the many people working through the Mac binary confusion, testing and providing work arounds until this runner was available: @Carreau @ndmlny-qs @jordantshaw @gabalafou @v-chojas and many others on the Issue.
If you are a Mac user, please let us know how this works for you.
5.0.1 with fix for Azure tokens / binary attrs_before
This is a bug fix for 5.0.0 that restores the ability to pass bytes objects in the attrs_before parameter when connecting. This is often used for Azure with a token. Issue #1289
5.0.0 Release
This is the first 3.x only pyodbc release, supporting Python 3.8-3.12.
The API is backwards compatible, so previous Python 3 products using pyodbc should not need any changes.
Please note that macOS ARM / Apple Silicon binary builds have been problematic due inconsistencies in the way unixODBC libraries can be installed. Please see the issues and discussions.
5.0.0 beta2 - Help test macOS Apple Silicon binary wheels
We're retrying macOS Apple Silicon wheels, for those with M1, M2, etc. chips. If you have one of these, please give this release a try so we know before the final 5.0.0 release in a few days.
This means you would not use Rosetta. Just install normally using pip install pyodbc==5.0.0b2. Don't use --no-binary.
For those wondering, the inability to supply these wheels has not been due to any pyodbc code issues. We use Github Actions to build all the different versions and it still doesn't have complete support for macOS Apple Silicon CI runners. At this time, only Intel is supported. However, we can cross-compile and generate ARM wheels on Intel, but we can't test them. That's why we need your help!
5.0.0 Beta 1
This is a beta release for the upcoming 5.0 that drops Python 2 and adds Python 3.12
Source and wheels for 5.0.0b1 is available on PyPI.
This beta restores fast_executemany, so it should be feature complete. If there are no serious issues reported, it will be released as 5.0.0 this week (before Python 3.12 is officially released).
5.0.0 alpha 2
This is a test release for the upcoming 5.0 that drops Python 2 and supports Python 3.12
Source and wheels for 5.0.0a2 is available on PyPI
Note that fastexecmany is not available in this release, but will be ported to the next.
Please give it a try and join the discussion here.