SQLAlchemey中的复合外键允许引用不存在的记录

时间:2013-08-01 20:48:23

标签: python sql sqlalchemy

我有一个包含点和对象概念的数据库。对象与一对一关系中的单个点相关联。一次只能有一个对象与一个点关联,我已通过唯一性约束强制执行该操作。

但是,我总是想说对象只能与存在的点相关联,但我很难看到如何实现它。

在我进一步解释之前,这里有一些测试代码:

import sqlalchemy as sql
import sqlalchemy.orm as sqlorm
import sqlalchemy.ext.declarative
import os

debug = True
db_file = "/".join([os.getcwd() if __name__ == "__main__" else os.path.dirname(__file__), 'sqlite.db'])

engine_opts = {
    'echo': debug
}

engine = sql.create_engine('sqlite:///{}'.format(db_file), **engine_opts)
Session = sqlorm.sessionmaker(bind=engine)

Base = sqlalchemy.ext.declarative.declarative_base()


class Point(Base):
    __tablename__ = 'point'
    x = sql.Column(sql.Integer, primary_key=True, nullable=False)
    y = sql.Column(sql.Integer, primary_key=True, nullable=False)

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "({x},{y})".format(x=self.x, y=self.y)

    def __unicode__(self):
        return self.__str__()


class Obj(Base):
    __tablename__ = 'obj'
    id = sql.Column(sql.Integer, primary_key=True)
    x = sql.Column(sql.Integer)
    y = sql.Column(sql.Integer)

    __table_args__ = (
        sql.ForeignKeyConstraint([x, y], [Point.x, Point.y]),
        sql.UniqueConstraint(x, y, name='obj_coords'),
    )
    point = sqlorm.relationship("Point", backref=sqlorm.backref('obj', uselist=False))


def init_db():
    Base.metadata.create_all(engine)
    session = Session()

    min_val = 0
    max_val = 20

    for x in range(min_val, max_val):
        for y in range(min_val, max_val):
            session.add(Point(x=x, y=y))
    session.commit()

    session.add(Obj(x=0, y=0))
    session.add(Obj(x=10, y= 10))
    session.add(Obj(x=-1, y=-1))

    session.commit()
    session.close()


def reinit_db():
    Base.metadata.drop_all(engine)
    init_db()


if __name__ == "__main__":
    reinit_db()

我有Point的复合主键,我想在x上使用yObj作为外键。

backref有效且唯一性约束意味着没有2个obj可以占据相同的点,但我的测试代码允许我在不存在的Obj中创建Point( - 1,-1)当我想要阻止它时。

这有可能实现,我该怎么做?

1 个答案:

答案 0 :(得分:1)

如果您确实在使用SQLite,不仅仅是出于演示目的,问题在于它默认情况下它当前不强制执行外键约束(并且在3.6.19之前根本没有强制执行它们)。在开始任何交易之前,您必须执行PRAGMA foreign_keys=ON。在SQLAlchemy中,这可以使用事件系统实现:

import sqlalchemy.event as sqlevent

# ...

engine = sql.create_engine('sqlite:///{0}'.format(db_file), **engine_opts)
sqlevent.listen(engine, 'connect',
    lambda conn, rec: conn.execute('PRAGMA foreign_keys=ON;'))

然后执行程序会根据需要产生完整性错误:

sqlalchemy.exc.IntegrityError: (IntegrityError)
foreign key constraint failed u'INSERT INTO obj (x, y) VALUES (?, ?)' (-1, -1)

请注意,此错误仅在刷新/提交时引发:它由数据库引擎报告,并且SQLAlchemy无法知道您创建了无效对象:它不会在Python端跟踪FK。 / p>