0

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

但是,我总是想说对象只能与存在的点相关联,但我正在努力了解如何实现这一点。

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

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 个 objs 可以占据同一点,但是我的测试代码允许我Obj在不存在的Point(-1, -1) 中创建一个,当我想要阻止它时。

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

4

1 回答 1

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。

于 2013-08-02T04:07:44.467 回答