pyODBC + unixodbc + Db2 for iSeries = UnicodeDecodeError,非法 UTF-16 代理项

pyODBC + unixodbc + Db2 for iSeries = UnicodeDecodeError, illegal UTF-16 surrogate

我能够在我的 docker 容器中成功地使用 pyODBC 和 SQLAlchemy 连接到 DB2 for iSeries(7.2 版)。它有效,但我会间歇性地 运行 查询并返回以下回溯:

>>> Groups.query.get(group_id)

Traceback (most recent call last):
...
  File "/usr/local/lib/python3.7/site-packages/sqlalchemy/util/compat.py", line 154, in reraise
    raise value
  File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 1244, in _execute_context
    cursor, statement, parameters, context
  File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 550, in do_execute
    cursor.execute(statement, parameters)
UnicodeDecodeError: 'utf-16-le' codec can't decode bytes in position 64-65: illegal UTF-16 surrogate

有时这种情况会连续发生很多次然后突然停止。它并不总是发生在同一组查询中,我已经尝试了两个几乎相同的 DB2 服务器并得到了相同的结果。

对于相同的查询,'position 64-65' 总是相同的(即使有时查询 returns 正确的结果)。

版本:

终于找到了。

在堆栈的某处,列名别名有 30 个字符的限制。我猜这是 pyodbc(db2 支持 128 长度的列名和别名),我已经 raised an issue on GitHub 跟踪这个问题。

超过 30 个字符的限制时,pyodbc 仍会尝试使用列名的原始长度解码字符串,因此它会尝试解码垃圾数据,有时会导致 UnicodeDecodeError(其他所有时间都会返回垃圾数据。

这是特定于列名的(因此 cursor.keys() 将显示垃圾列名)。

我的解决方法是使用自定义方言强制 SQLAlchemy 截断列别名。

customdb2.py:

from ibm_db_sa.pyodbc import AS400Dialect_pyodbc

class CustomAS400Dialect(AS400Dialect_pyodbc):
    max_identifier_length = 30
registry.register('db2.pyodbc400_custom', 'customdb2', 'CustomAS400Dialect')