web-dev-qa-db-ja.com

カスタムODBCドライバを作成する

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

他の誰かがカスタムODBCドライバーを実装しましたか?どのような落とし穴に遭遇しましたか?自分で行うことからどのようなメリットがありましたか?どれだけの工数を要しましたか? SDKを使用しましたか?使用している場合、そのアプローチからどのようなメリット/デメリットがありましたか?

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

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

37

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

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

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

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

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

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

正確にどのバックエンドデータベースをエミュレートするように選択するかは、おそらく、ワイヤープロトコルのフォーマットがどれだけ適切に文書化されているかに最も依存するはずです。

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

簡単なPython 2.7 Postgresqlワイヤプロトコルの一部を理解するサーバーバックエンドの例を以下に示します。サンプルスクリプトは、ポート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=>

ODBC Postgresqlプロトコルを話すドライバも動作するはずです(しかし、まだ試していません)。

21
codeape

ODBCドライバーは非常に複雑です。ODBCドライバーを作成するという決定は、軽視してはいけません。既存のオープンソースドライバーを確認することは、例としては良いアプローチですが、ほとんどの場合、エミュレートしたくない欠点があります:) APIはOSプラットフォームに関係なく同じです。 MSSQL/Sybase用のFreeTDSには、私が見てきた優れたオープンソースODBCドライバの実装の1つがあります。

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

  • メタデータアクセス関数
  • ODBC固有のクエリ構文解析
  • SQLSTATEエラーメッセージのマッピング
  • マルチバイト/文字セットのマーシャリング
  • ODBCバージョン2、3のサポート-エラーメッセージ/関数のマッピング
  • カーソル
  • データソースを管理するためのDM構成UI
9
Einstein

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

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

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

  • iODBC は、お客様の選択により、LGPLまたはNew BSDのいずれかでライセンスされます。新しいBSDでは、変更をせずに変更をオープンソースにすることができます。

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

4
Bill Karwin

この投稿は少し古いですが、ODBCドライバーが必要な場合は、次のようなSDKを使用できます: http://www.simba。 com/drivers/simba-engine-sdk / 他の回答で指摘されたほとんどの点を処理し、実装するためのはるかに簡略化されたインターフェイスを提供します。

私はたまたまSimbaで働いているので、少し偏っていますが、SDKを使用すると、ODBCドライバーを簡単に作成できます。コーディングにある程度熟練している場合、5日で何かが起こります。

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

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

2
KylePorter