43

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

有没有其他人实现了自定义 odbc 驱动程序?你遇到了什么陷阱?你从自己身上看到了什么好处?你估计需要多少工时?您是否使用了 SDK,如果是,您从该方法中看到了哪些好处/缺点?

任何意见和答案将不胜感激。谢谢!

编辑:我们正在尝试使用用 C 编写的代码来保持可移植性。

4

5 回答 5

30

另一种选择:不创建 ODBC 驱动程序,而是实现一个后端,该后端与另一个数据库(例如 Postgresql 或 MySQL)使用的有线协议进行通信。

然后,您的用户可以下载并使用例如 Postgresql ODBC 驱动程序。

您选择模拟的确切后端数据库可能主要取决于线路协议格式的记录情况。

PostgresMySQL都为其客户端-服务器协议提供了不错的文档。

下面是一个简单的 Python 2.7 服务器后端示例,它了解 Postgresql 有线协议的部分内容。示例脚本创建了一个侦听端口 9876 的服务器。我可以使用该命令psql -h localhost -p 9876连接到该服务器。执行的任何查询都将返回一个结果集,其中包含列 abc 和 def 以及两行,所有值均为 NULL。

阅读 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 驱动程序也应该可以工作(但我还没有尝试过)。

于 2012-12-03T14:32:28.350 回答
10

ODBC 驱动程序非常复杂 - 编写驱动程序的决定不应掉以轻心。查看现有的开源驱动程序是一个很好的示例方法,但大多数都有你可能不想模仿的缺点:) 无论操作系统平台如何,API 都是相同的。用于 MSSQL/Sybase 的 FreeTDS 具有我见过的更好的开源 ODBC 驱动程序实现之一。

如果您控制应用程序,您可以在合理的时间内实现可能只是规范的一个非常小的子集。要在通用环境中使用可能需要更多的努力才能做到正确。除了简单地实现几十个包装器调用之外,我还必须实现:

  • 元数据访问函数
  • ODBC 特定查询语法解析
  • SQLSTATE 错误消息映射
  • 多字节/字符集编组
  • ODBC 版本 2,3 支持 - 错误消息/函数映射
  • 光标
  • 用于管理数据源的 DM 配置 UI
于 2008-12-02T19:38:21.343 回答
9

我没有,但我曾经在一家正是这样做的公司面试过。他们制作了一个名为 AMPS 的 4GL/DBMS 产品,其架构与 MUMPS 相同——一种集成了 4GL 的分层数据库(此类系统的整个类型在 1970 年代出现)。他们有相当多的遗留代码库,客户希望使用 MS Access 连接到它。

采访我的首席开发人员分享了一些关于这方面的战争故事。显然这是非常痛苦的,不应该掉以轻心。然而,他们确实成功地实施了它。

这样做的一种替代方法是提供一个数据集市/BI 产品(沿着 SAP BW 的线),它将您的应用程序数据显示在外部数据库中,并将其按摩成更友好的格式,例如星形或雪花模式。

这将受到不支持实时访问的影响,但可能比 ODBC 驱动程序更容易实现(更重要的是维护)。如果您的实时访问要求是可合理预测且有限的,您可能会公开一个 Web 服务 API 来支持这些要求。

于 2008-12-02T18:59:04.870 回答
4

我没有实现 ODBC 驱动程序,只是想提供一个建议,您可以从开源实现开始并添加您自己的定制。这可能会让您更快地开始。

至少有两种选择:

  • unixODBC在 LGPL 下获得许可,这意味着如果您修改代码,您必须使您的修改开源。

  • iODBC根据您的选择在 LGPL 或新 BSD 下获得许可。新的 BSD 允许您进行修改,而无需将您的修改开源。

但是,尚不清楚这些包是否在 Windows 上运行,而不是在 UNIX/Linux 上运行,其客户端 API 与标准 ODBC 一致。您没有说明您使用的是哪个平台,所以我不知道这是否与您有关。

于 2008-12-02T18:57:36.090 回答
2

这篇文章现在有点老了,但值得一提的是,如果你需要一个 ODBC 驱动程序,你可以使用这样的 SDK:http ://www.simba.com/drivers/simba-engine-sdk/它需要注意其他答案中提出的大多数观点,并为您提供了一个非常简化的界面来实现。

我碰巧为 Simba 工作,所以我有点偏见,但是使用 SDK 确实可以很容易地为您尝试做的任何事情创建 ODBC 驱动程序。如果你对编码有点精通,你可以在 5 天内完成一些事情。

其他帖子之一建议将 unixODBC 或 iODBC 作为起点,但这不起作用。了解驱动程序管理器(unixODBC、iODBC 等)和驱动程序之间的区别很重要。Driver Manager 充当应用程序和驱动程序之间的中间人,无需直接链接到驱动程序。

您可以从 Postgres 或 MySQL 驱动程序作为起点,然后将它们分叉以使用您自己的数据库,但这不太可能是一项微不足道的任务。从头开始创建驱动程序更加困难,并且可能会持续(并且高于预期)维护成本。只要您了解这种方法的成本,它也是可行的。

于 2016-12-05T16:58:47.077 回答