连接 MySQL 3.23 与 pyodbc 3.07

Connect MySQL 3.23 with pyodbc 3.07

我正在尝试使用 UnixODBC 和 pyodbc 3.07 从 Ubuntu 16 客户端连接到旧的 MySQL 3.23 服务器。我已经尝试了 MySQL Connector/ODBC 的三 (3) 个版本和来自 MariaDB 的两 (2) 个版本:

MySQL-ODBC 5.3.9 仅支持新的mysql 身份验证方法。所以连接不上。

MySQL-ODBC 5.1.13 有认证方法的开关,但在 pyodbc.connect(dsn) 上告诉我:[MySQL][ODBC 5.1 Driver]Driver does not support server versions under 4.1.1

MySQL-ODBC 3.51 有两个问题:

  1. [MySQL][ODBC 3.51 Driver]Transactions are not enabled (4000) (SQLSetConnnectAttr(SQL_ATTR_AUTOCOMMIT)) 失败,因为 pyodbc 默认将自动提交设置为 false。
  2. 当我连接 pyodbc.connect(dsn, autocommit=True) 时给我一个连接。连接给了我一个光标,但所有 cursor.execute(sql) 抛出异常 ('HY000', 'The driver did not supply an error!').

通过 isql -v [dsn] 从 shell 测试与 isql 的连接给了我一个会话,但在所有与 [ISQL]ERROR: Could not SQLExecute 的语句上都失败了。所以这似乎是unixodbc的问题。

我安装了mysql-client。但是程序 mysql 连接服务器失败。

mariadb-client可以连接数据库甚至执行语句。这看起来更有希望。

我下载了 MariaDB ODBC-Driver 3.0.2。将该驱动程序与 isql returns 一起使用时出现错误:[S1000][unixODBC][ma-3.0.2]Plugin old_password could not be loaded: lib/mariadb/plugin/old_password.so: cannot open shared object file: No such file or directory。这是一个可以处理的回应。有一个 ODBC 选项 PLUGIN_DIR 但我不知道从哪里获得插件。

MariaDB ODBC-Driver 2.0.13 在连接时给我 ('HY000', "[HY000] [unixODBC][ma-2.0.13]You have an error in your SQL syntax near 'SQL_AUTO_IS_NULL=0' at line 1 (1064) (SQLDriverConnect)")。因为似乎没有改变这一点的选择。死胡同了。

我想知道是否有办法通过 unixodbc/pyodbc 访问这个旧的 MySql?

或者有人知道从哪里获得 MariaDB 的插件 old_password.so 吗?

通过apt-get安装的mariadb-client可以连接所以一定有办法。

我花了一天左右的时间研究这个,我认为如果不对 driver 代码进行重大改动,或者不为旧版本提供极其 [​​=296=] 的构建环境,这是不可能的。

我把这个放在一个答案中,这样其他人就不会像我一样掉进同一个兔子洞(或者,更好的是,这样其他人就可以从我离开的地方继续并真正解决问题!) ...它不适合发表评论。

抱歉,这会有点冗长。

概述

我能够使用一对 Ubuntu 16.04 容器、MySQL 3.23 download 可从 Oracle 获得,以及您提到的所有客户端库和其他一些库。

以下是我在您提到的每个地方尝试寻找其他解决方案时发现的内容,然后是一些 "next steps" 类型的信息和一些关于故事寓意的劝导。

所有这些测试都是使用 Python 2、UnixODBC 和 pyodbc(通过 pip)的最新版本进行的 Ubuntu 16.04 Docker 容器截至 2017 年 11 月 26 日。

所有使用的 URL 都是 linked,但是,如果历史有任何迹象,它们可能会随着时间的推移而消失,考虑到很多这种软件已经有 20 年的历史了。如果您愿意,我也很高兴 post any/all 我的 shellscripts/Dockerfiles/modified driver 来源;只需在评论中 ping 我。

old_password.so 和 MariaDB Connector/ODBC 3.0.2

您说得对,这是最有潜力的故障排除选项。这是我所做的:

首先,我安装了 Connector/ODBC 3.0.2 二进制文件并尝试通过 Python 连接到它。在为名为 "maria" 的数据源配置我的 ODBC .ini 文件后,我遇到了同样的错误,即:

> pyodbc.connect('DRIVER={maria};Server=mysql;Database=mysql;User=admin;Password=admin')
pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Plugin old_password could not be loaded: lib/mariadb/plugin/old_password.so: cannot open shared object file: No such file or directory (2059) (SQLDriverConnect)')

ODBC 代码会尝试在 MySQL 服务器宣布身份验证协议足够旧时加载为 Connector/C MariaDB driver 构建的已编译插件。 strace通过 ODBC 连接尝试的输出确定了这一点。

old_password.so 结果是 Connector/C MariaDB driver 的一个组件,但不是 driver 的二进制版本中包含的库。有意思。

原来Connector/Cdriver的源码中包含了一堆类似old_password的插件模块。我下载了 Connector/C 3.0.2 sources 并打开了那些 "auth" 类型插件的文档、资源和构建系统,这些插件作为 .so 文件分发,看看我能找到什么。

我发现 Connector/C 的各种组件可以作为插件 "statically" link 编译到主 driver 库中,或者作为动态库本身。我在引号中说 "statically",因为 C driver 的构建过程创建了 mariadbclient 的静态 (.a) 和动态 (.so) 版本,但如果在构建系统中将特定插件声明为静态,则该插件的代码将静态包含在两个 mariadbclient 工件中。

old_password.so 文件的源代码似乎位于 plugins/auth/old_password.c 的一个小源文件中。

似乎可以更改构建系统 (CMake) 来为 old_password 插件生成动态库。在 Connector/C 源中有一个 cmake/plugins.cmake 文件,它充当所有插件的 "registry"。它包含一个带有 STATICDYNAMIC 参数的 cmake 宏 REGISTER_PLUGIN。我在该文件中搜索 old_password 并找到以下行:

REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CC_SOURCE_DIR}/plugins/auth/old_password.c" "old_password_client_plugin" "STATIC" "" 0)

看起来很有希望。模仿 did 为他们的插件生成 .so 文件的类似行,我将该行更改为以下行和 运行 构建:

REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CC_SOURCE_DIR}/plugins/auth/old_password.c" "old_password_client_plugin" "DYNAMIC" "old_password" 1)

由于缺少依赖项,构建失败了几次。我必须安装一些 -dev 包和其他工具,但最后我能够干净地构建(对于插件,事实证明你不需要 CURL 或 OpenSSL)。果然,在 plugins/auth 目录中创建了一个名为 mysql_old_password.so 的文件作为构建工件。 - 现在,我需要我的 Python 代码来找到那个插件;它仍然给我关于找不到 lib/mariadb/plugin/old_password.so 的错误。我向 ODBC 连接字符串提供了您在问题中提到的 PLUGIN_DIR 参数,将编译的 mysql_old_password.so 重命名为 old_password.so,并 运行 以下代码。 . .并得到一个新的错误!进步了!

conn = pyodbc.connect('DRIVER={maria};Server=mysql;Database=mysql;User=admin;Password=admin;PLUGIN_DIR=/home/mysql/zclient/mdb-c/plugins/auth')
pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Plugin old_password could not be loaded: /home/mysql/zclient/mdb-c/plugins/auth/old_password.so: undefined symbol: ma_scramble_323 (2059) (SQLDriverConnect)')

看起来编译的工件已损坏,缺少 ma_scramble_323 函数定义。由于插件是在 运行 时动态加载的,程序仍会启动,但是当它尝试 dload 插件时它会崩溃。更糟糕的是,该函数看起来像是 "old" MySQL 协议身份验证机制的主要 password-hashing 入口点,所以我不能放弃它。在 Connector/C 来源中,我找到了该函数的声明和 header (mariadb_com.h),但是 includeold_password.c 来源的不同地方找到了它文件似乎没有办法。我的直觉是,这是两种不幸行为的相互作用。冷杉, 由 Connector/C 构建系统编译的插件被设置为假设它们将 仅由 Connector/C 插件 或类似的东西 link 编辑.这意味着插件本身在编译时不会 link 到 "common" Connector/C 功能,因为这些东西应该已经在加载插件的东西中可用。由于我们使用的是 Connector/ODBC,而不是 Connector/C,因此这些常用函数不存在或不可访问。现在,从源代码构建 Connector/ODBC 需要 Connector/C,因此我可以编译一个新的 Connector/ODBC 库,使其包含正确的函数,但我不想从那个兔子洞开始。其次,即使被告知以独立模式(不要编译任何其他东西)构建 old_password 插件,CMake 的依赖性分析也没有发现或 link 描述 ma_scramble_323 的文件。这可能是 CMake 的问题,但这可能是因为构建系统没有像上面提到的那样配置这个用例。

在这里,我很幸运。 ma_scramble_323 函数在 libmariadb/ma_password.c 中定义,它是一个非常小、简单的源文件,与 Connector/C 项目中尚未 depended-on 的任何其他库没有显着依赖关系通过 old_password 插件。我做了 "poor man's linking"(讨厌),只是将 ma_scramble_323 函数的源代码复制到 old_password.c 文件中。这些函数调用了 ma_password.c 文件中的其他函数,所以我将它们复制到。同样,由于 ma_password.c 文件非常简单,这很容易(或者根本不是一个选项)。如果它本身有依赖关系或更复杂,我将不得不停止、放弃并学习高级 CMake-fu 以 "right" 方式解决问题。我绝对相信有更好的方法来做到这一点。

(旁白) 在这一点上,我不得不在我的数据库服务器上定期 运行 of mysqladmin flush-hosts,因为我的测试导致了如此多的失败尝试,我不得不经常这样做。可能还有更好的解决方法,但我不知道,我知道 cron。

有了新的"inlined"源代码,编译了mysql_old_password.so库,我重命名了它,运行我的测试脚本再次出现。这次,我得到了:

pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Plugin old_password could not be loaded: name mismatch (2059) (SQLDriverConnect)')

我想这与我重命名文件以便 ODBC 可以找到它有关(它正在寻找 old_password.so 而不是 mysql_old_password.so)。我尝试了霰弹枪方法。在 plugins/auth/CMakeLists.txt 构建系统配置中,我将 mysql_old_password 的所有实例替换为 old_password 并编译。编译成功,但是还是不行

事实证明,插件源代码本身(在本例中为 old_password.c)在顶部有一个声明其名称的结构声明,而这个声明其名称为 mysql_old_password。这很可能是一个 pre-existing 问题(即这从未奏效),我开始感到有点寒意:当你构建的代码感觉就像没有人在给定的配置中构建或测试过它以前,你成功的几率并不大。不管怎样,我也对源文件做了同样的 s/mysql_old_password/old_password/,然后编译。这次它生成了一个具有正确 old_password.so 名称的工件。我再次 运行 我的测试脚本并得到:

conn = pyodbc.connect('DRIVER={maria};Server=mysql;Database=mysql;User=admin;Password=admin;PLUGIN_DIR=/home/mysql/zclient/mdb-c/plugins/auth')
pyodbc.Error: ('HY000', u"[HY000] [unixODBC][ma-3.0.2]Access denied for user: 'admin@hostname' (Using password: NO) (1045) (SQLDriverConnect)")

这很奇怪。我的 client-testing 盒子上也安装了 3.23 服务器附带的 mysql 命令行客户端(通过 tarball,而不是在系统库路径中),它可以很好地连接这些凭据(我不能不要用 isql 进行测试,因为我无法让它正确使用 PLUGIN_DIR 并且无法弄清楚它想让我把插件放在哪里;它不在系统中 /usr 目录,也不是相关目录)。我想不出办法解决这个问题。我已经为 MySQL 服务器设置了所有常用的 "ultra-promiscuous, testing only" GRANTs,对于 localhost%,对于每个数据库,对于 admin 用户和同名密码。

我放弃并将密码设置为 empty/null,禁用密码验证,确保我仍然可以通过命令行上的 mysql 登录,最后一次尝试:

pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Error in server handshake (2012) (SQLDriverConnect)')

事实证明这是丧钟。在研究这个错误时,我发现 this GitHub issue,其中人们似乎非常确信这代表了根本的 client/server 协议不兼容。在这一点上,我放弃了 old_password.so 方法。似乎 MariaDB driver 代码(C 或 ODBC)的 3.0.2 版本没有使用足够古老的 MySQL 协议方言来工作,尽管可能有很多可能我在该过程中遗漏的修复程序。

尝试了其他路径

我尝试了您在问题中提到的其他一些事情,我将在此处简要介绍一下:

  • 正如您可能发现的那样,尝试禁用 MariaDB 2.0 ODBC driver 系列中的 SQL_AUTO_IS_NULL 行为效果不佳。 This bug thread and the ODBC Connector parameters list 有一些关于如何禁用该字段设置的建议(Option=8388608 是显而易见的,对吧?),但是 none 这些尝试强制禁用或启用该标志改变了行为,无论它们是在连接字符串中还是在 ODBC .ini 文件中。

  • MySQL archive site has old versions of the ODBC connector available. Unfortunately, all of their compiled versions are for 32-bit Linux, which I don't have. I tried building from source, and it was a massive chore even to get the toolchain configured. At the point where I had to hand-install system identification files from 1999我就知道是prob真是一个失败的原因,但我安装了所有的 deps 和古老的版本并尝试编译它。编译错误的数量和种类之多导致我放弃了这种方法(C 标准不匹配,加上与 UnixODBC 的几乎每个部分都缺乏兼容性)。完全有可能对我错过的这些问题进行简单的修复;我不是 C 编码员或 old-linux-build-system 专家。

  • 我试了一些third party MySQL ODBC connectors,但没用;与 5.* 系列相同的错误。

  • 我编译了 Connector/ODBC 库的 2.50.39 版本(存档中只有源代码可用)。为此,我首先为 3.23 版本的服务器编译了 libmysqlclient.so.10 文件。这需要更改 3.23 服务器的源代码以解决一些 errno 相关问题(删除 my_sys.hextern int errno#define 子句),复制 libtool OS定义文件到源目录中的不同位置(/usr/share/libtool/build-aux/config.{guess,sub} 已复制到 .mit-pthreadsmit-pthreads/config/,如果重要的话)。之后,我能够使用 --with-mit-threads --without-server --without-docs --without-bench 配置开关编译和构建 libmysqlclient 库。在那之后评估 mysql 客户端程序的宏时,编译失败并出现几个难以理解的错误,但是 libmysqlclient.so 文件已经生成,所以我抓住它们并继续前进。编译 libmysqlclient.so.10 库后,我构建了 2.50.39 version of Connector/ODBC from the archive。这需要更改主要 MySQL 包含文件中的一些来源(删除对 asm/atomic.h 的引用),以及与其他库相同的 system-identification libtool hack。它找不到 iodbc 库(通过 libiodbc2-dev 软件包安装在 Ubuntu 上),因为它们现在位于 /usr/include 而不是 /usr/local/include。我终于用开关 --with-mysql-includes=$path_to_3.23_mysql_binary_dir/include --with-mysql-libs=$path_to_compiled_libmysqlclient.so.10_files_from_mysql_server_3.23_sources --with-iodbc-includes=/usr/include/iodbc 配置了它,除了前面提到的 atomic.h 问题之外,它没有遇到任何问题。然而,毕竟,通过我的 newly-compiled libmyodbc.so 连接导致了 Python/UnixODBC 中的段错误。 Valgrind、gdb 和其他工具无法确定原因;也许更精通调试编译库互操作性问题的人可以解决这个问题。

  • MySQL 存档中有 Connector/ODBC 的旧二进制 RPM 版本。它们都是 32 位的,几乎所有现代 Linux 都是 64 位的。我通过安装 i386 体系结构和所需的库来尝试 shimming 这些文件。 64 位 Python/UnixODBC 无法成功加载 myodbc 插件,返回通用 "file not found" 错误,我最终将其追溯到对 dlopen 的失败调用。 Libtool 的 dlopen 包装器(由 UnixODBC 使用)被大多数人认为是 not-very-debuggable,在经历了重大的麻烦之后,我的基本 Valgrind 技巧似乎表明,如我所料,无法动态加载一个architecture-incompatible(i386 对比 x86-64)ODBC 后端。

Solutions/Remaining 选项

一般来说,您自己重写代码可能会更容易。例如,您可以制作一个 Python 模块来包装遗留 Python non-ODBC MySQL driver (正如@FlipperPA 在对该问题的评论中所建议的那样),将 pyodbc 接口的 'enough' 破解到该模块上,您不必重构太多调用它的代码,并在部署前进行彻底测试。我知道这很糟糕而且有风险,但这可能是你最好的选择。在编写这样的模块时,您可以使用 pyodbc 中处理通用 ODBC syntax/et 等的一些内部代码。

你甚至可以为 pyodbc 开发一个 "fake" ODBC 后端,它只是调用了一个 non-ODBC Python MySQL driver,但是我怀疑这会很困难,因为 pyodbc 的 backend-pluggability 似乎主要针对编译库而不是 "dummy" 垫片代码。

我不是这方面的专家,所以我完全可能错过了一些解决方案!

我考虑过并放弃了其他几种可能性:

您可以向 MariaDB 人员提交错误,它可能会被修复。我不太清楚我最终遇到的协议错误是 "this is fundamentally incompatible at every level" 还是 "the auth system just needs a tweak then everything will work"。可能值得一试。

由于 2.50 版 Connector/ODBC 代码有 32 位 RPM(它们不会加载到 64 位 Python/UnixODBC 环境中),您可以想象将整个堆栈(甚至操作系统分发)到 32 位代码。但是,如果您使用任何 non-common 编译的东西,这可能会很麻烦。虽然 Ubuntu/Debian 特别适合在旧架构上使用软件包,但这仍然可能很棘手。即使您转换了所有内容,某些行为也可能会发生变化,并且旧的 32 位特征对于使用您的应用程序的任何人来说都是一个持续的 st运行geness。只有当 2.50 driver 从 32 位 运行 时间访问时才能工作;之后可能还会出现其他问题。如果将来所有客户端代码的维护负担可能非常低(如果项目很小或不太可能改变)。

故事寓意

软件腐烂得血腥。除非一个项目不断致力于保持向后兼容性,否则事情很快就会停止工作,尤其是在网络软件中。

并不是产品本身坏了,而是宇宙从它下面以百万种方式发生了变化。除非有人足够多面手,并且已经足够长的时间知道所有这些小变化以及如何扭转它们,否则很难将 time/revisions 中的所有内容倒退到 "just work" 的地方。 =126=]

如果你得到二进制文件,即使它是 "common" 像 MySQL driver,保留它们。理想情况下,将它们与互联网共享。

如果您有某物的来源,请严格记录他们需要的 dependencies/toolchain 的完整列表,并记录它 供人类使用 。假设需要读取编程依赖列表的工具,例如autotools 本身将过时。没有什么比 "obvious" 更值得记录的了;不是体系结构、内核 ABI、libc 行为——什么都没有。现在我们有了 "in a box on any kernel" 类似 Docker 的东西,您也许能够以编程方式存储更多依赖项,但不要指望它。

正如 Zac B 所展示的,没有真正的方法可以从 Ubuntu 16 客户端与 UnixODBC 和 pyodbc 3.07 连接 MySQL 3.23 服务器。

FlipperPA 建议使用 pypi.python.org/pypi/MySQL-python/1.2.4 作为下一个可能的解决方案。所以我试了一下:

通过 apt 安装的

MySQL-python 无法连接到旧的 MySQL .它使用的系统 mysql-客户端库不适用于 MySQL 3.23.

MySQL-python 通过 pip 安装是另一回事:它从 mysql_config 包含在 libmysqlclient-dev 包中。也不适用于 MySQL 3.23。不过这里有机会修改一下。

当我安装 libmariadb-client-lgpl-dev 时,我得到一个 mariadb_config 看起来很像 mysql_config。 Ubuntu 16 的 mariadb-client 使用 MySQL 3.23(如上所示)。

ln -s /usr/bin/mariadb_config mysql_config
pip install MySQL-python

这样就可以了。我可以从 Python 连接那个旧的 MySQL 服务器。

数据类型有一些问题,但我在这种情况下并不挑剔。