创建自定义的ODBC驱动程序

49

我现在的工作是希望实现我们自己的ODBC驱动程序,以允许许多不同的应用程序能够将我们的应用程序作为数据源连接。目前,我们正在权衡开发符合实现规范的自己的驱动程序,这需要大量的工作量,或者使用允许程序员“填写”特定数据部分并允许更高级别抽象的SDK。

有其他人实现过自定义ODBC驱动程序吗?你遇到了什么问题?从自行完成中你看到了哪些好处?你估计需要多少人时来完成它?如果使用了SDK,你看到了什么优缺点?

任何评论和答案都将不胜感激。谢谢!

编辑:我们希望保持代码的可移植性,它是用C编写的。


3
我能问一下,为什么你选择构建一个自定义的ODBC驱动程序(15年前的技术),而不是一个OLEDB驱动程序(10年前的技术)或一个托管代码的ADO.Net数据提供程序(8年前和目前的技术)? - Charles Bretana
我只是好奇,如果考虑这个问题,我会在我的决策因素中包括该组件的潜在市场...你们大多数潜在用户使用Access和/或MsQuery吗? - Charles Bretana
3
有趣的是现在微软似乎正在从“旧”的ole db转向“真正古老”的odbc... - Brady Moritz
Boomhauer可能是在提到微软在SQL Server 2010之后放弃OLE DB提供程序的事情。http://blogs.msdn.com/b/sqlnativeclient/archive/2011/08/29/microsoft-is-aligning-with-odbc-for-native-relational-data-access.aspx - Fls'Zen
unixODBC和iODBC是ODBC驱动程序管理器,而不是ODBC驱动程序。 - Tony
显示剩余2条评论
5个回答

30
另一种选择是:不创建ODBC驱动程序,而是实现一个后端,该后端使用另一个数据库(例如Postgresql或MySQL)使用的协议进行通信。
您的用户可以下载并使用例如Postgresql ODBC驱动程序。
选择要模拟的后端数据库应该主要取决于协议格式的文档化程度。 PostgresMySQL都为其客户端 - 服务器协议提供了良好的文档。
下面是一个简单的Python 2.7示例,用于理解Postgresql协议的某些部分的服务器后端。 示例脚本创建一个侦听端口9876的服务器。我可以使用命令psql -h localhost -p 9876连接到服务器。执行的任何查询都将返回一个具有列abc和def以及两行的结果集,所有值均为空。
阅读Postgresql文档并使用类似wireshark的工具检查真实的协议流量将使实现与Postgresql兼容的后端变得非常简单。
import SocketServer
import struct

def char_to_hex(char):
    retval = hex(ord(char))
    if len(retval) == 4:
        return retval[-2:]
    else:
        assert len(retval) == 3
        return "0" + retval[-1]

def str_to_hex(inputstr):
    return " ".join(char_to_hex(char) for char in inputstr)

class Handler(SocketServer.BaseRequestHandler):
    def handle(self):
        print "handle()"
        self.read_SSLRequest()
        self.send_to_socket("N")

        self.read_StartupMessage()
        self.send_AuthenticationClearText()
        self.read_PasswordMessage()
        self.send_AuthenticationOK()
        self.send_ReadyForQuery()
        self.read_Query()
        self.send_queryresult()

    def send_queryresult(self):
        fieldnames = ['abc', 'def']
        HEADERFORMAT = "!cih"
        fields = ''.join(self.fieldname_msg(name) for name in fieldnames)
        rdheader = struct.pack(HEADERFORMAT, 'T', struct.calcsize(HEADERFORMAT) - 1 + len(fields), len(fieldnames))
        self.send_to_socket(rdheader + fields)

        rows = [[1, 2], [3, 4]]
        DRHEADER = "!cih"
        for row in rows:
            dr_data = struct.pack("!ii", -1, -1)
            dr_header = struct.pack(DRHEADER, 'D', struct.calcsize(DRHEADER) - 1 + len(dr_data), 2)
            self.send_to_socket(dr_header + dr_data)

        self.send_CommandComplete()
        self.send_ReadyForQuery()

    def send_CommandComplete(self):
        HFMT = "!ci"
        msg = "SELECT 2\x00"
        self.send_to_socket(struct.pack(HFMT, "C", struct.calcsize(HFMT) - 1 + len(msg)) + msg)

    def fieldname_msg(self, name):
        tableid = 0
        columnid = 0
        datatypeid = 23
        datatypesize = 4
        typemodifier = -1
        format_code = 0 # 0=text 1=binary
        return name + "\x00" + struct.pack("!ihihih", tableid, columnid, datatypeid, datatypesize, typemodifier, format_code)

    def read_socket(self):
        print "Trying recv..."
        data = self.request.recv(1024)
        print "Received {} bytes: {}".format(len(data), repr(data))
        print "Hex: {}".format(str_to_hex(data))
        return data

    def send_to_socket(self, data):
        print "Sending {} bytes: {}".format(len(data), repr(data))
        print "Hex: {}".format(str_to_hex(data))
        return self.request.sendall(data)

    def read_Query(self):
        data = self.read_socket()
        msgident, msglen = struct.unpack("!ci", data[0:5])
        assert msgident == "Q"
        print data[5:]


    def send_ReadyForQuery(self):
        self.send_to_socket(struct.pack("!cic", 'Z', 5, 'I'))

    def read_PasswordMessage(self):
        data = self.read_socket()
        b, msglen = struct.unpack("!ci", data[0:5])
        assert b == "p"
        print "Password: {}".format(data[5:])


    def read_SSLRequest(self):
        data = self.read_socket()
        msglen, sslcode = struct.unpack("!ii", data)
        assert msglen == 8
        assert sslcode == 80877103

    def read_StartupMessage(self):
        data = self.read_socket()
        msglen, protoversion = struct.unpack("!ii", data[0:8])
        print "msglen: {}, protoversion: {}".format(msglen, protoversion)
        assert msglen == len(data)
        parameters_string = data[8:]
        print parameters_string.split('\x00')

    def send_AuthenticationOK(self):
        self.send_to_socket(struct.pack("!cii", 'R', 8, 0))

    def send_AuthenticationClearText(self):
        self.send_to_socket(struct.pack("!cii", 'R', 8, 3))

if __name__ == "__main__":
    server = SocketServer.TCPServer(("localhost", 9876), Handler)
    try:
        server.serve_forever()
    except:
        server.shutdown()

示例命令行psql会话:

[~]
$ psql -h localhost -p 9876
Password:
psql (9.1.6, server 0.0.0)
WARNING: psql version 9.1, server version 0.0.
         Some psql features might not work.
Type "help" for help.

codeape=> Select;
 abc | def
-----+-----
     |
     |
(2 rows)

codeape=>

一个支持PostgreSQL协议的ODBC驱动程序也应该可以使用(但我还没有尝试过)。


1
目前正在尝试使用ODBC驱动程序使其正常工作,看起来很有希望。我只需要删除开头的SSLRequest部分并返回与ODBC驱动程序要求相对应的结果,我可以看到所有请求都在传输中 :) - Gregory
1
很棒的答案!这就是h2数据库所做的事情:http://www.h2database.com/html/advanced.html#odbc_driver - user325117

11

ODBC驱动程序非常复杂,编写一个驱动程序的决定不应该轻率地做出。查看现有的开源驱动程序是一个好方法,但大多数驱动程序都存在缺陷,您可能不想效仿:) 无论操作系统平台如何,API都是相同的。

如果您控制应用程序,可以在合理的时间内实现可能只是规范的非常小的子集。但在通用环境中使用则需要更多的努力来使其正确运行。除了简单地实现几十个包装器函数之外,还需要实现以下功能:

  • 元数据访问函数
  • ODBC特定的查询语法解析
  • SQLSTATE错误消息映射
  • 多字节/字符集编组
  • ODBC版本2、3支持-错误消息/函数映射
  • 游标
  • DM配置UI以管理数据源

对于MSSQL/Sybase,FreeTDS具有我见过的最好的开源ODBC驱动程序实现之一。


2
MySQL ODBC驱动程序和Postgresql ODBC驱动程序怎么样?有人对这些驱动程序的代码质量有什么看法吗? - codeape

10
我没有亲身经历过,但我曾经面试过一家公司,他们正是这样做的。他们开发了一个名为AMPS的4GL/DBMS产品,与MUMPS相同的体系结构 - 一种具有集成4GL的分层数据库(在20世纪70年代出现了整个此类系统的流派)。他们拥有相当大的遗留代码库和希望使用MS Access连接到它的客户。
面试我的首席开发人员分享了一些相关的故事。显然这是非常痛苦的,不能轻率对待。然而,他们确实成功地实现了它。
另一种方法是提供数据仓库/BI产品(类似于SAP BW),将您的应用程序数据呈现在外部数据库中,并将其转换为更友好的格式,如星型或雪花模式。
这种方法不支持实时访问,但可能比ODBC驱动程序更易于实现(更重要的是维护)。如果您的实时访问要求是可以预测和有限的,则可能会公开Web服务API来支持这些要求。

4
我没有实现ODBC驱动程序,但想提供一个建议,您可以从开源实现开始,然后添加自己的定制内容。这可能会让您更快地入手。
至少有两个选择:
- unixODBC在LGPL下授权,这意味着如果您修改代码,则必须使您的修改开源。 - iODBC在LGPL或New BSD下授权,由您选择。New BSD允许您进行修改而无需使您的修改开源。
但是,不清楚这些软件包是否在Windows上运行,而不是在UNIX / Linux上使用与标准ODBC一致的客户端API。您没有说明使用的平台,因此我不知道这是否与您相关。

2

这篇文章有点老,但值得一提的是,如果您需要使用ODBC驱动程序,可以使用像这样的SDK:http://www.simba.com/drivers/simba-engine-sdk/它解决了其他答案中提到的大部分问题,并为您提供了一个更简化的接口来实现。

我恰好在Simba工作,所以有点偏见,但使用SDK确实很容易为您正在尝试完成的任何任务创建ODBC驱动程序。如果您对编码有一定的熟练程度,可以在5天内开始使用。

其中一篇帖子推荐使用unixODBC或iODBC作为起点,但这不起作用。重要的是要意识到驱动程序管理器(unixODBC,iODBC等)和驱动程序之间的区别。驱动程序管理器充当应用程序和驱动程序之间的中间人,消除了直接链接到驱动程序的必要性。

您可以从Postgres或MySQL驱动程序开始,并将其分叉以使用自己的数据库,但这可能不是一项微不足道的任务。从头开始创建驱动程序甚至更加困难,可能会有比预期更高的维护成本。只要您知道这种方法的成本,它也可以是可行的。


1
Simba SDK 的许可证是什么? - Martynas Jusevičius
链接已失效。 - siggemannen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接