0

私は、少なくとも 3 つの異なるデータベース サーバー バックエンド (SQLite3、PostgreSQL、および MySQL) で使用できるユーティリティを作成しています。私は ORM を使用していません (ただし、プロトタイプを完成させた後、SQLAlchemy を使用してフォークを試し、他のツール (将来的には他の言語から) からアクセスできるようにスキーマを維持する方法に十分慣れました)。

私は、自分自身の質問に答え、質問を開いて議論のために回答することに関する StackOverflow のポリシーの精神でこれを投稿しています。

以下の回答に投稿されたコードは、意図したスキーマの非常に単純なサブセット (スクリプトの上部近くのハードコード) で事前構成された、サポートされている 3 つのターゲットすべてに対してテストされています。現在、各テーブルに必要な列がすべて含まれていることを確認し、余分な列が存在する場合にのみ警告します。列の内容に関する型やその他の制約をチェックしていません。

(私のユーティリティでは、NULL 値またはコードが適切なデフォルト値を提供できる限り、エンドユーザーが追加の列を追加できるようにする必要があります --- 私のコードは、指定した列のサブセットのみを挿入または更新するためです。 )。

これを達成するためのより良い方法があるか、またはテストで明らかにされていない重大なエラーを犯した場合はお知らせください。

(この質問を扱いやすくするために共有できるSQLFiddleエントリ/リンク適切に作成する方法を知りたい. . それが機能するかどうか見てみましょう)。

4

1 に答える 1

1

上記のように、私のアプローチは次のとおりです。

#!/usr/bin/env python
import importlib
import sys

schema = (('tags',     set(('id','tag','gen','creation'))),
          ('nodes',    set(('name', 'id', 'gen', 'creation'))),
          ('node_tag', set(('node_id', 'tag_id', 'creation'))),
          ('tgen',     set(('tid', 'comment', 'creation')))
         )

drivers = {
           'mysql':  'MySQLdb',
           'pg':     'psycopg2',
           'sqlite': 'sqlite3',
          }

if __name__ == '__main__':
    args = sys.argv[1:]
    if args[0] in drivers.keys():
        dbtype = args[0]
        db = importlib.import_module(drivers[dbtype])
    else:
        print >> sys.stderr, 'Unrecognized dbtype %s, should be one of these:' % args[0], ' '.join(drivers.keys())
        sys.exit(127)

    if dbtype == 'sqlite':
        required_args = 2
        dbopts = { 'database': args[1] }
    else:
        required_args = 6
        dbopts = { 'database' : args[1],
                   'user'     : args[2],
                   'passwd'   : args[3],
                   'host'     : args[4],
                   'port'     : int(args[5]),
                }

if len(args) < required_args:
    print >> sys.stderr, 'Must supply all arguments:',
    print >> sys.stderr, '[mysql|pg|sqlite] database user passwd host port'
    sys.exit(126)

if dbtype == 'mysql':
    dbopts['db'] = dbopts['database']
    del dbopts['database']

if dbtype == 'pg':
    dbopts['password'] = dbopts['passwd']
    del dbopts['passwd']

try:
    conn = db.connect(**dbopts)
except db.Error, e:
    print 'Database connection failed: %s' % e
    sys.exit(125)

cur  = conn.cursor()

exit_code = 0
for each_table in schema:
    table, columns = each_table
    introspected_columns = None
    try:
        cur.execute("SELECT * FROM %s WHERE 0=1" % table)
        introspected_columns = set((x[0] for x in cur.description))
    except db.Error, e:
        print >> sys.stderr, 'Encountered %s Error on table %s' % (e, table)
    if introspected_columns:
        missing = columns - introspected_columns
        extra   = introspected_columns - columns
    if missing:
        print 'Error: Missing columns in table %s: %s' % (table,' '.join(missing))
        exit_code += 1
    else:
        print 'All columns present for table %s' % table
    if extra:
        print 'Warning: Extra columns in table %s: %s' % (table, ' '.join(extra))

sys.exit(exit_code)

...実際には、これはクラスにリファクタリングされ、私が書いているデーモンのサービス起動中に呼び出されます。

ここでの実際の手法は、すべての列に対してクエリを作成することSELECT * FROM some_tableです...ただし、行を返さないことが保証されていますWHERE 0=1...つまり、事前にテーブル名を知る必要はありません。これは、DBAPI (クライアント側) カーソルでDBAPI cur.descriptionフィールドを一貫して設定しているようです。

(ちなみに、この例では、一般的な DBAPI データベース ドライバー間での接続キーワード引数の厄介な違いも強調しています。それらはいずれも追加のキーを無視していないようです。そのため、3 つのそれぞれに大幅に異なる引数セットが必要です。SQLite3 のファイル名だけで、 'database' キーの名前を MySQLdb の場合は 'db' に、PostgreSQL の場合は 'password' を 'passwd' に(少なくともpsycopg2ドライバーの場合)。

于 2014-07-02T08:49:06.867 に答える