「foobar」および「foobar_data_cache」テーブルで表されるfoobarの概念がデータベースであり、「Foobar」クラスはpythonです。「foobar」テーブルはコンセプトに関するデータを表し、「foobar_data_cache」テーブルはコンセプトに関するその他のデータを表します。これらのデータは、データベース内の他の多くの情報から派生し、データベース トリガーによって計算されます。一貫性を保つために、INSERT、UPDATE、および DELETE 権限は「foobar_data_cache」テーブルから取り消されます。
SQLAlchemy を使用して、'Foobar' クラスを 2 つのテーブル 'foobar' と 'foobar_data_cache' に結合してマップしたいと考えています。'foobar_data_cache' テーブルからのデータを表すために別のクラスを使用し、これら 2 つのクラス間の関係を構築する理由はありません。これは、両方のテーブルからのデータが強く関連しているためです。実際、データベースの観点からは、2 つのテーブル間に 1 対 1 の関係があり、以下によって保証され
ます
。 「foobar」の各行に対応する行が「foobar_data_cache」にあることを確認するトリガー
私の問題は、SQLAlchemy ORM を使用して新しい Foobar オブジェクトを永続化しようとすると、「foobar_data_cache」テーブルに行を挿入しようとすることです。これを防止したいと考えています。
では、「foobar_data_cache」テーブルを読み取り専用と見なすように SQLAlchemy を構成することは可能ですか? はいの場合、どのように?
私の問題を説明するコードは次のとおりです。
from sqlalchemy import (
Table,
Column,
Integer,
String,
ForeignKeyConstraint,
join,
create_engine,
)
from sqlalchemy.orm import (
column_property,
sessionmaker,
)
from sqlalchemy.schema import (
FetchedValue,
)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
_foobar_table = Table('foobar', Base.metadata,
Column('id', Integer, primary_key=True),
Column('some_data', String),
)
_foobar_data_cache_table = Table('foobar_data_cache', Base.metadata,
Column('foobar_id', Integer, primary_key=True),
Column('computed_data', String, server_default=FetchedValue()),
ForeignKeyConstraint(['foobar_id'], ['foobar.id']),
)
class Foobar(Base):
__table__ = _foobar_table.join(_foobar_data_cache_table)
_id = column_property(_foobar_table.c.id, _foobar_data_cache_table.c.foobar_id)
def __init__(self, some_data):
self.some_data = some_data
if __name__ == '__main__':
engine = create_engine('postgresql://tester@localhost:5432/mytestdb')
Session = sessionmaker(bind=engine)
session = Session()
my_foobar = Foobar('Dummy data')
session.add(my_foobar)
session.commit()
2 つのテーブルを作成する SQL コマンドは次のとおりです。
CREATE TABLE foobar (
id int NOT NULL DEFAULT -2147483648,
some_data varchar NOT NULL,
CONSTRAINT pk_foobar PRIMARY KEY (id)
);
CREATE TABLE foobar_data_cache (
foobar_id int NOT NULL,
computed_data varchar NOT NULL,
CONSTRAINT pk_foobar_data_cache PRIMARY KEY (foobar_id),
CONSTRAINT fk_foobar_data_cache_foobar_1 FOREIGN KEY (foobar_id)
REFERENCES foobar (id) MATCH FULL
ON UPDATE CASCADE ON DELETE CASCADE
);
注:
1 対 1 の関係であるという事実を考慮して、データを 2 つの異なるテーブルに分割する理由を疑問に思う人もいるかもしれません。この問題は、単一のテーブルと FetchedValue コンストラクト ( SQLAlchemy でマップされた列のサブセットのみを永続化する方法を参照) を計算列に使用することで簡単に解決できます。うーん、少し複雑ですが、説明しようと思います。まず、上記で説明されていないその他の事項は次のとおりです。
- PostgreSQL 8.4 を使用していますが、とりわけ、遅延可能な UNIQUE 制約を持つことができません
- 私の列はどれも NULL 値を受け入れません
- 'foobar_data_cache' の一部の列には一意の制約があります (遅延可能ではありません)
- 「foobar_data_cache」のデータを計算するトリガーは、トランザクションの終了まで延期されます。実際、外部キーの制約により、「foobar」への挿入後にのみ挿入できる他のテーブルから情報を取得するためです。
つまり、単一のテーブルを使用する場合、NOT-NULL 制約のために、計算列に一時的なダミー値を使用する必要があります。私のトリガーは、トランザクションの最後に最終的にそれをオーバーライドします。そして問題は同時実行性です。実際、別のトランザクション T1 の実行中に新しい「foobar」を挿入しようとする新しいトランザクション Tx は、実行中のトランザクション T1 に対応する行に一意の列のデフォルトのダミー値が既に存在するため、失敗します。一意の制約を持つ列に対してランダムなダミー値を生成できますが、そのスタイルは好きではありません。