使用pyodbc 3.07连接MySQL 3.23

jhi*_*aus 11 mysql unixodbc pyodbc mariadb

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

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将autocommit设置为false作为默认值而失败.
  2. 当我连接时给我一个连接pyodbc.connect(dsn, autocommit=True).连接给了我一个游标,但所有cursor.execute(sql)抛出异常('HY000', 'The driver did not supply an error!').

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

我安装了mysql-client.但是programm mysql无法连接服务器.

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

我下载了MariaDB ODBC-Driver 3.0.2.使用该驱动程序与isql返回错误:[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-Option 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可以连接,所以必须有一种方法.

Zac*_*c B 5

我花了一天左右的时间研究这个问题,并且认为如果不对驱动程序代码进行重大更改,或者为旧版本创建极其难以创建的构建环境,这是不可能的。

我将其放入答案中,以便其他人不会掉入我所做的同一个兔子洞(或者,更好的是,这样其他人可以从我离开的地方继续并实际解决问题!)...并且它不适合评论。

抱歉,这将是一本大部头书。

概述

我能够使用一对 Ubuntu 16.04 容器、 Oracle 提供的MySQL 3.23 下载以及您提到的所有客户端库来重现您在帖子中提到的每个错误情况(感谢您提出了一个彻底而出色的问题!)和其他一些。

以下是我在尝试在您提到的每个地方寻找其他解决方案时发现的内容,然后是一些“下一步”类型的信息以及一些关于故事道德的劝说。

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

所有使用的 URL 都有链接,但是,如果历史有任何迹象的话,它们可能会随着时间的推移而消亡,考虑到该软件的许多版本已经有二十年的历史了。如果您愿意,我也很乐意发布我的任何/所有 shellscripts/Dockerfiles/修改后的驱动程序源;只需在评论中告诉我即可。

old_password.so 和 MariaDB 连接器/ODBC 3.0.2

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

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

> 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)')
Run Code Online (Sandbox Code Playgroud)

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

old_password.so结果是 Connector/C MariaDB 驱动程序的组件,但不是该驱动程序的二进制版本中包含的库。有趣的。

事实证明,有一堆类似于old_passwordConnector/C 驱动程序源中包含的插件模块。我下载了Connector/C 3.0.2 源代码,并打开了那些以文件形式分发的“auth”类型插件的文档、源代码和构建系统,.so看看我能找到什么。

我发现 Connector/C 的各种组件可以编译为“静态”链接到主驱动程序库的插件,也可以编译为动态库本身。我在引号中说“静态”,因为 C 驱动程序的构建过程会创建静态 ( .a) 和动态 ( .so) 版本的mariadbclient,但如果特定插件在构建系统中声明为静态,则该插件的代码将静态包含在两者都是mariadbclient文物。

old_password.so文件的源似乎位于位于 的单个小源文件中plugins/auth/old_password.c

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

REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CC_SOURCE_DIR}/plugins/auth/old_password.c" "old_password_client_plugin" "STATIC" "" 0)
Run Code Online (Sandbox Code Playgroud)

看起来很有希望。根据为插件生成文件的类似行进行建模.so将该行更改为以下内容并运行构建:

REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CC_SOURCE_DIR}/plugins/auth/old_password.c" "old_password_client_plugin" "DYNAMIC" "old_password" 1)
Run Code Online (Sandbox Code Playgroud)

由于缺少依赖项,构建失败了几次。我必须安装一些-dev软件包和其他工具,但最终我能够干净地构建(对于插件来说,事实证明你不需要 CURL 或 OpenSSL)。果然,mysql_old_password.so在目录中创建了一个名为 的文件plugins/auth作为构建工件。- 现在,我需要我的 Python 代码来找到该插件;它仍然给了我关于未能找到的错误lib/mariadb/plugin/old_password.so。我将PLUGIN_DIR您在问题中提到的参数提供给 ODBC 连接字符串,将我的编译重命名为mysql_old_password.soold_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)')
Run Code Online (Sandbox Code Playgroud)

看起来编译的工件已损坏,缺少ma_scramble_323函数定义。由于插件是在运行时动态加载的,因此程序仍然会启动,但是当它尝试使用dload插件时,它会崩溃。更糟糕的是,该函数看起来像是“旧”MySQL 协议身份验证机制的主要密码哈希入口点,所以我不能放弃它。在 Connector/C 源代码中,我找到了该函数的声明和标头 ( mariadb_com.h),但includeold_password.c源文件中的各个位置进行声明似乎并没有解决问题。我的预感是这是两种不幸行为的相互作用。首先,由 Connector/C 构建系统编译的插件被设置为假设它们仅由 Connector/C 插件或类似的东西链接。这意味着插件本身在编译时不会链接到“通用”Connector/C 功能,因为这些东西应该已经在加载插件的事物中可用。由于我们使用的是 Connector/ODBC,而不是 Connector/C,因此这些常见功能不存在或不可访问。现在,从源代码构建 Connector/ODBC 需要 Connector/C,因此我可以以包含正确函数的方式编译新的 Connector/ODBC 库,但我不想开始这个兔子洞。其次,即使被告知以独立(不编译任何其他内容)模式构建old_password插件,CMake 的依赖关系分析也没有发现或链接描述ma_scramble_323. 这可能是一个 CMake 问题,但这可能是因为构建系统没有按照上面提到的这个用例进行配置。

在这里,我非常幸运。该ma_scramble_323函数在 中定义libmariadb/ma_password.c,这是一个非常小、简单的源文件,对 Connector/C 项目中插件尚未依赖的任何其他库没有显着依赖old_password。我做了“穷人的链接”(恶心),只是将ma_scramble_323函数的源代码复制到old_password.c文件中。这些函数调用ma_password.c文件中的其他函数,所以我将它们复制到。同样,这很简单(或者根本就是一个选项),因为ma_password.c文件非常简单。如果它本身有依赖项或更复杂,我将不得不停止、放弃并学习高级 CMake-fu 以“正确”的方式解决问题。我绝对确信有更好的方法来做到这一点。

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

使用新的“内联”源代码,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)')
Run Code Online (Sandbox Code Playgroud)

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

事实证明,插件源本身(old_password.c在本例中)在顶部有一个结构声明,宣布其名称,而这个声明其名称为mysql_old_password. 这很可能是一个预先存在的问题(也就是说,这从来没有起作用),我开始感到有点寒意:当你构建的代码感觉之前没有人构建过它或在给定的配置中测试过它,你成功的几率不大。无论如何,我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)")
Run Code Online (Sandbox Code Playgroud)

这很奇怪。我也在mysql我的客户端测试盒上安装了 3.23 服务器附带的命令行客户端(通过 tarball,而不是在系统库路径中),并且它可以与这些凭据正常连接(我无法测试,因为我isql无法无法正确使用它PLUGIN_DIR,也无法弄清楚它希望我将插件放在哪里;它不在系统/usr目录中,也不在相关目录中)。我想不出办法解决这个问题。我已经用所有常见的“超混杂,仅测试”设置了我的 MySQL 服务器,GRANT对于localhost%,对于每个数据库,对于admin用户和同名密码。

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

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

事实证明,这就是丧钟。研究这个错误时,我发现了这个 GitHub 问题,其中人们似乎非常确信这代表了基本的客户端/服务器协议不兼容。此时我放弃了这种方法old_password.so。看起来 MariaDB 驱动程序代码(C 或 ODBC)的 3.0.2 版本并没有使用足够古老的 MySQL 协议方言来工作,尽管我在这个过程中可能错过了很多可能的修复。

尝试过其他路径

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

  • 您可能发现,尝试禁用SQL_AUTO_IS_NULLMariaDB 2.0 ODBC 驱动程序系列中的行为效果不佳。此错误线程ODBC 连接器参数列表有一些关于如何禁用该字段的设置的建议(Option=8388608很明显且清晰,对吧?),但是这些强制禁用或启用该标志的尝试都没有改变行为,无论是它们位于连接字符串或 ODBC.ini文件中。

  • MySQL存档站点提供旧版本的 ODBC 连接器。不幸的是,他们所有的编译版本都是针对 32 位 Linux 的,而我没有。我尝试从源代码构建,甚至配置工具链也是一项繁重的工作。当我不得不手动安装 1999 年的系统识别文件时,我知道这可能是一个失败的原因,但我安装了所有 deps 和旧版本并尝试编译它。编译错误的数量和种类使我放弃了这种方法(C 标准不匹配,加上与 UnixODBC 的几乎每个部分缺乏兼容性)。我完全有可能错过了对这些问题的简单修复;我不是 C 程序员或旧的 Linux 构建系统专家。

  • 我尝试了一些第三方 MySQL ODBC 连接器,但不起作用;与 5.* 系列的错误相同。

  • 我编译了 Connector/ODBC 库的 2.50.39 版本(存档中仅提供源代码)。为此,我首先编译了libmysqlclient.so.103.23 版本服务器的文件。这需要更改 3.23 服务器的源来解决一些errno相关问题(删除in#define的子句),将 libtool OS 定义文件复制到源目录中的各个位置(如果重要的话,复制到、 和)。之后,我能够使用配置开关编译和构建库。之后在评估客户端程序的宏时编译失败,出现几个难以理解的错误,但文件已经生成,所以我抓住它们并继续。编译库后,我从存档中构建了 Connector/ODBC 2.50.39 版本。这需要更改主 MySQL 包含文件中的一些源(删除对 的引用),以及与其他库相同的系统识别 libtool hack。它无法找到库(通过软件包安装在 Ubuntu 上),因为它们现在位于而不是. 我最终用开关配置了它,除了上述问题之外,它的构建没有遇到任何问题。然而,毕竟,通过我新编译的连接导致了 Python/UnixODBC 中的段错误。Valgrind、和其他工具无法确定原因;也许更擅长调试编译库互操作性问题的人可以解决这个问题。extern int errnomy_sys.h/usr/share/libtool/build-aux/config.{guess,sub}.mit-pthreadsmit-pthreads/config/libmysqlclient--with-mit-threads --without-server --without-docs --without-benchmysql.solibmysqlclientlibmysqlclient.so.10asm/atomic.hiodbclibiodbc2-dev/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/iodbcatomic.hlibmyodbc.sogdb

  • MySQL 存档具有旧的连接器/ODBC 二进制 RPM 版本。它们都是 32 位的,而几乎所有现代 Linux 都是 64 位的。我尝试通过安装架构和所需的库来调整这些文件。i38664 位 Python/UnixODBC 无法myodbc成功加载插件,返回通用的“文件未找到”错误,我最终追溯到对dlopen. 大多数人认为Libtool 的dlopen包装器(由 UnixODBC 使用)不太容易调试,在经历了一番重大麻烦之后,我的基本 Valgrind 技巧似乎表明,正如我所料,不可能动态加载与架构不兼容的架构(i386x86-64) ODBC 后端。

解决方案/剩余选项

一般来说,重写代码可能会更容易。例如,您可以创建一个 Python 模块来包装旧版 Python 非 ODBC MySQL 驱动程序(如 @FlipperPA 在该问题的评论中建议的那样),将“足够”的接口黑客pyodbc到该模块上,而无需重构太多的代码调用它,并在部署之前进行彻底的测试。我知道这很糟糕而且有风险,但这可能是你最好的选择。在编写此类模块时,您可以利用处理pyodbc通用 ODBC 语法等的一些内部代码。

您甚至可以为其开发一个“假”ODBC 后端,pyodbc称为非 ODBC Python MySQL 驱动程序,但我怀疑这会很困难,因为pyodbc的后端可插拔性似乎主要面向编译库而不是“虚拟”填充代码。

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

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

您可以向 MariaDB 人员提交错误,它可能会被修复。我不太清楚最终出现的协议错误是“这在每个级别上基本上都是不兼容的”还是“身份验证系统只需要进行调整,然后一切都会正常工作”。也许值得一试。

由于 2.50 连接器/ODBC 代码有 32 位 RPM 可用(它们不会加载到 64 位 Python/UnixODBC 环境中),因此您可以将整个堆栈(甚至操作系统发行版)转换为 32 位 RPM。位代码。但是,如果您使用任何非常见的编译内容,这可能会是一个很大的麻烦。虽然 Ubuntu/Debian 特别擅长在旧架构上提供软件包,但这仍然可能很棘手。即使您将所有内容都进行了转换,某些行为也可能会发生变化,并且对于使用您的应用程序的任何人来说,旧的 32 位特性将是一种持续的陌生感。仅当 2.50 驱动程序在从 32 位运行时访问时才能工作时才是如此;之后可能还会出现其他问题。仅当您所有客户端代码的维护负担将来可能非常低(如果项目很小或不太可能更改)时,我才建议尝试此操作。

故事的寓意

软件很快就会腐烂。除非项目持续致力于保持向后兼容性,否则事情很快就会停止工作,尤其是在 Web 软件中。

并不是产品本身损坏了,而是宇宙以一百万种微小的方式发生了变化。除非有人是足够的多面手,并且已经存在了足够长的时间来了解所有这些微小的变化以及如何扭转它们,否则很难将所有内容及时倒退/修改到事情“正常工作”的地方。

如果您获得某些东西的二进制文件,即使它是像 MySQL 驱动程序之类的所谓“常见”的东西,也要保留它们。理想情况下,通过互联网分享它们。

如果您有某些东西的来源,请严格记录他们所需的依赖项/工具链的整个列表,并为人类记录它。假设读取程序依赖项列表(例如自动工具)所需的工具本身将过时。没有什么是太“明显”而无法记录的;不是架构、内核 ABI、libc 行为——什么都不是。现在我们已经有了像 Docker 这样的“在任何内核上的盒子里”的东西,您也许能够以编程方式存储更多依赖项,但不要指望它。