Я хотел бы сохранить инвариант при вставке или обновлении отношения «многие-ко-многим» с использованием SQLAlchemy. Цель состоит в том, чтобы в таблице не было перекрывающихся интервалов, которые представляют интервалы для одного и того же объекта (A).Вставка SQLAlchemy, предварительное условие обновления (проверка согласованности)
Например:
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
intervals = relationship('Interval', backref='a', cascade='all, delete-orphan')
class B(Base):
__tablename__ = 'b'
id = Column(Integer, primary_key=True)
intervals = relationship('Interval', backref='b', cascade='all, delete-orphan')
class Interval(Base):
__tablename__ = 'interval'
id = Column(Integer, primary_key=True)
a_id = Column(Integer, ForeignKey('a.id', ondelete='cascade'), nullable=False)
b_id = Column(Integer, ForeignKey('b.id', ondelete='cascade'), nullable=False)
start = Column(DateTime, nullable=False)
end = Column(DateTime, nullable=False)
обновляется на основе нот фургона (спасибо):
Так что я хочу, чтобы обеспечить до/во время вставки и обновления, что не существует никаких записей в «интервал» таблица, где
((interval.start >= start and interval.start <= end) or
(interval.end >= start and interval.end <= end) or
(start >= interval.start and start <= interval.end) or
(end >= interval.start and end <= interval.end)) and
a_id = interval.a_id
Так что вопрос будет то, что лучший способ добиться этого с помощью SQLAlchemy бэкэнд MySQL. Я хочу, чтобы эта проверка была как можно более атомной, и нет возможности нарушить ее с одновременными операциями. Надеюсь, вопрос ясен. Спасибо за помощь заранее.
Update 1:
Ответы на вопросы ван «s:
Что ваш рабочий процесс в случае сбоя проверки?
- Пользователь должен получить сообщение об ошибке, что интервал, который пользователь попытался добавить перекрывается уже существующий интервал.
- Пользователь может также получить список интервалов, которые сталкиваются с тем, который пользователь пытался добавить.
Что вы будете делать с теми интервалами, которые прошли проверку и не были добавлены к?
- См. Ответ на вопрос рабочего процесса.
- Так что, если проверка не выполняется, если кто-то пытается добавить перекрывающий интервал, должно произойти исключение, и пользователь должен получить сообщение об ошибке, что это недопустимо.
- Интервалы, которые не были добавлены, не должны быть привязаны к базе данных.
отмечает, что они будут по-прежнему стремятся к БД, потому что они принадлежат к b1, но a_id = NULL (вы разрешите NULLs в текущей модели)
- Они не должны быть и не должно быть значений NULL для a_id или b_id. Я обновил модель, чтобы отразить это.
- То есть это может быть NULL, но для меня это не имеет смысла, поскольку записи с a_id = NULL должны были быть очищены каким-то образом.
Что должен видеть конечный пользователь? Должна ли быть совершена сделка и т. Д.? Вы счастливы обернуть любой a.intervals.add (...) в try/except?
- Пользователь должен видеть сообщение об ошибке, когда это ограничение нарушается.
- Сделку ни в коем случае нельзя совершать.
- Попробуйте, кроме упаковки, хорошо.
Это, как я полагаю, добавив интервал:
1.) Проверка формы происходит и мы знаем, что в других областях уже не прошли проверку (интервал не будет добавлен к сессии), и мы хотим проверить, если интервал также не проходит проверку:
### Start request
# Prevalidation
Interval.check_valid(a, start, end)
...
#### End request
2.) Все поля формы прошли проверки, мы хотим проверить, если интервал будет действительным, если оно совершено (это, возможно, не потребуется), а затем совершить интервал:
# Start request
# Basic validation at the time of addition
try:
interval = Interval(a, b, start, end)
except SomeException:
return("This interval overlaps another, please fix this!")
...
# Main validation when the interval relation is committed to database
try:
session.flush() # commit the session to the database
except AnotherException: # maybe IntegrityError or something similar
return("This interval overlaps another, please fix this!")
### End request
Я не уверен, действительно ли требуется базовая проверка. Предварительное подтверждение необходимо, потому что, если кто-то выставляет форму, и мы видим некоторые ошибки для других полей, мы хотели бы получить все недопустимые поля, перечисленные в ответе. Причина этого заключается в том, что я не хочу, чтобы пользователь несколько раз публиковал форму, возвращаясь к нему снова и снова с более новыми ошибками, но я хотел бы сразу указать все ошибки, которые я могу проверить ,
Глядя на решение van при условии, что он выполняет основную проверку, но могут быть случаи с краем, где это может вызвать проблемы. Это веб-приложение, поэтому возможно, что два разных пользователя используют разные сеансы, поэтому при выполнении i1 = Interval (a1, b1, start1, end1) i1 не отображается в других сеансах, пока реестр не будет обновлен из базы данных в этой сессии. (По крайней мере, это то, что я думаю, как это работает.)
- Пользователи пытается добавить интервал (i1), что это не пересекается с другими интервалами в базе данных. Интервал (a1, b1, start1, end1)
- Пользователи пытаются добавить интервал (i2), который не перекрывает любые интервалы в базе данных, но перекрывается с i1. Интервал (a1, b2, start2, end2)
- Обе проверки могут быть успешными, поскольку оба пользователя могут использовать разные сеансы.
- Session i1 принадлежит к flush() - ed и привязан к базе данных без ошибок.
- Session i2 принадлежит к flush() - ed и привязан к базе данных без ошибок.
Возможно ли, что здесь ситуация, или я что-то не понимаю?
Я также думал о добавлении триггеров UPDATE и INSERT в таблицу «interval», чтобы выполнить эту проверку. Я не уверен, что это правильный путь, и если это обеспечивает атомарность, которая гарантирует, что параллельные попытки, перечисленные выше, не вызовут проблемы. Если это правильный способ, мой вопрос будет правильным способом для создания этих триггеров при вызове Base.metedata.create_all(). Это единственный способ сделать это, или есть возможность как-то прикрепить это к модели, и пусть create_all() создайте его: DELIMITER/Creating a trigger in SQLAlchemy
Update 2:
Правильный алгоритм для проверки, если интервалы сталкиваются, кажется:
interval.start <= end and start <= interval.end
Я обнаружил, что правильный подход для обеспечения надлежащей атомарности является просто использовать select for update в способе, в котором проверяется перекрытие. Это работает с MySQL как минимум.
Спасибо за исчерпывающий ответ. Я ответил на ваши вопросы в исходном вопросе. – ZergRush
Привет, ZergRush. Я рад, что мой ответ о помощи, и я хочу, чтобы я мог помочь вам больше - у меня просто нет времени, так как объем вашего вопроса внезапно увеличивается в 10 раз. Я поставил этот вопрос в конце своего ответа, чтобы вы думаете о своем рабочем процессе, а не как дополнительный вход для меня, чтобы дать вам «окончательное» решение. Пожалуйста, поделитесь своими решениями окончательного решения, так как у вас есть интересная проблема. Приветствия. – van