43

私の現在の仕事では、独自の odbc ドライバーを実装して、さまざまなアプリケーションが独自のアプリにデータソースとして接続できるようにすることを検討しています。現在、大規模な実装仕様に合わせて独自のドライバーを開発するか、プログラマーがデータ固有の部分を「埋め」、より高いレベルの抽象化を可能にする SDK を使用するかのオプションを比較検討しています。

他の誰かがカスタム odbc ドライバーを実装しましたか? どのような落とし穴に陥りましたか? 自分でやってみて、どんなメリットがありましたか? おおよそ何工数かかりましたか?SDK を使用しましたか?もしそうなら、そのアプローチからどのような利点/欠点が見られましたか?

コメントや回答をいただければ幸いです。ありがとう!

編集: C で記述されたコードで移植性を維持しようとしています。

4

5 に答える 5

30

別のオプション: ODBC ドライバーを作成する代わりに、別のデータベース (たとえば、Postgresql または MySQL) が使用するワイヤ プロトコルと対話するバックエンドを実装します。

ユーザーは、Postgresql ODBC ドライバーなどをダウンロードして使用できます。

どのバックエンド データベースをエミュレートするかは、ワイヤ プロトコル フォーマットがどの程度文書化されているかに大きく依存するはずです。

PostgresMySQLの両方に、クライアント サーバー プロトコルに関する適切なドキュメントがあります。

Postgresql ワイヤ プロトコルの一部を理解するサーバー バックエンドの簡単な Python 2.7 の例を以下に示します。サンプル スクリプトは、ポート 9876 をリッスンするサーバーを作成します。コマンドpsql -h localhost -p 9876を使用してサーバーに接続できます。実行されたクエリは、列 abc と def および 2 つの行を含む結果セットを返します。値はすべて 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は、OSプラットフォームに関係なく同じです。FreeTDS for MSSQL / Sybaseには、私が見た中で最も優れたオープンソースのODBCドライバー実装の1つがあります。

アプリケーションを制御する場合は、妥当な時間内に仕様のごく一部にすぎない可能性のあるものを実装することで逃げることができます。汎用環境で使用するには、正しく機能させるためにかなり多くの労力が必要になる場合があります。何十ものラッパー呼び出しを単純に実装することに加えて、次のことも実装する必要があります。

  • メタデータアクセス機能
  • ODBC固有のクエリ構文解析
  • SQLSTATEエラーメッセージのマッピング
  • マルチバイト/文字セットのマーシャリング
  • ODBCバージョン2,3のサポート-エラーメッセージ/関数マッピング
  • カーソル
  • データソースを管理するためのDM構成UI
于 2008-12-02T19:38:21.343 に答える
9

私はしていませんが、これを正確に行った会社に一度インタビューしました。彼らは、MUMPSと同じ種類のアーキテクチャのAMPSと呼ばれる4GL / DBMS製品を作成しました。これは4GLが統合された階層型データベースです(このようなシステムの全ジャンルが1970年代に登場しました)。彼らはかなりのレガシーコードベースを持っていて、顧客はMSAccessを使用してそれに接続したいと思っていました。

私にインタビューしたリード開発者は、これについていくつかの戦争の話を共有しました。どうやらそれを行うのは非常に苦痛であり、軽視すべきではありません。しかし、彼らは実際にそれを実装することに成功しました。

これを行う代わりの方法の1つは、アプリケーションデータを外部データベースに表示し、スタースキーマやスノーフレークスキーマなどのより使いやすい形式にマッサージするデータマート/ BI製品を(SAP BWのラインに沿って)提供することです。

これは、リアルタイムアクセスをサポートしないという問題がありますが、ODBCドライバーよりも実装(およびより重要なことは保守)がかなり簡単な場合があります。リアルタイムアクセス要件が合理的に予測可能で制限されている場合は、それらをサポートするWebサービスAPIを公開する可能性があります。

于 2008-12-02T18:59:04.870 に答える
4

ODBCドライバーは実装していませんが、オープンソースの実装から始めて独自のカスタマイズを追加できるという提案を提供したいと思います。これにより、作業をより早く開始できる場合があります。

少なくとも2つのオプションがあります。

  • unixODBCはLGPLの下でライセンスされています。つまり、コードを変更する場合は、変更をオープンソースにする必要があります。

  • iODBCは、LGPLまたはNewBSDのいずれかでライセンスされています。新しいBSDを使用すると、変更をオープンソースにすることなく変更を加えることができます。

ただし、標準のODBCと整合性のあるクライアントAPIを使用してUNIX / Linuxで実行するのではなく、これらのパッケージがWindowsで実行されるかどうかは明確ではありません。使用しているプラ​​ットフォームがわからないので、これがあなたに関係があるかどうかはわかりません。

于 2008-12-02T18:57:36.090 に答える
2

この投稿は少し古いものですが、ODBC ドライバーが必要な場合は、次のような SDK を使用できることに注意してください: http://www.simba.com/drivers/simba-engine-sdk/他の回答で提起されたほとんどのポイントを説明し、実装するためのはるかに簡素化されたインターフェイスを提供します。

私はたまたま Simba で働いているので、少し偏見がありますが、SDK を使用すると、何をしようとしても、ODBC ドライバーをかなり簡単に作成できます。ある程度のコーディングができれば、5 日で何かを始めることができます。

他の投稿の 1 つは、出発点として unixODBC または iODBC を推奨していますが、これは機能しません。ドライバー マネージャー (unixODBC、iODBC など) とドライバーの違いを理解することが重要です。ドライバー マネージャーは、アプリケーションとドライバーの間の仲介者として機能し、ドライバーに直接リンクする必要がなくなります。

Postgres または MySQL ドライバーを出発点として開始し、それらをフォークして独自のデータベースを使用することもできますが、これは簡単な作業ではありません。ドライバーをゼロから作成することはさらに難しく、継続的な (そして予想よりも高い) メンテナンス コストがかかる可能性があります。このアプローチのコストを認識している限り、実行可能です。

于 2016-12-05T16:58:47.077 に答える