sqlalchemy审计提供了一种为数据设置修订跟踪的简单方法。
sqlalchemy_audit的Python项目详细描述
sqlalchemy审计提供了一种为数据设置修订跟踪的简单方法。它的灵感来自sqlalchemy的versioned_history示例,但使用映射器事件而不是会话事件。
示例
与Versioned共享DBSession:
DBSession=...Versioned.versioned_session(DBSession)
然后像往常一样声明类并让它继承Versioned:
classReservation(Versioned,Base):__tablename__='reservation'id=Column(Integer,primary_key=True)name=Column(String(50))date=Column(Date)time=Column(Time)party=Column(Integer)last_modified=Column(DateTime)Reservation.broadcast_crud()# todo: handle this automagically
注意
您还可以从声明性基类中的子类Versioned。
正常使用保持不变:
# make new reservationsteve_reservation=Reservation(name='Steve',date=datetime.date(2015,04,15),time=datetime.time(19,00),party=6)session.add(steve_reservation)session.commit()# change reservation to party of 4steve_reservation.party=4session.commit()# cancel the reservationsession.delete(steve_reservation)session.commit()
另外,您可以访问它的修订历史。
>>>DBSession.query(ReservationRev).all()[ReservationRev(rev_id='c74d5bce...',rev_created=1427995346.0,rev_isdelete=False,id=1,name='Steve',date='2015-04-15',time='19:00',party=6,last_modified='2015-04-02 13:22:26.291670'),ReservationRev(rev_id='f3f5091d...',rev_created=1428068391.0,rev_isdelete=False,id=1,name='Steve',date='2015-04-15',time='19:00',party=4,last_modified='2015-04-03 09:39:51.098798'),ReservationRev(rev_id='3cf1394b...',rev_created=1428534191.0,rev_isdelete=True,id=1,name=None,date=None,time=None,party=None,last_modified=None)]
工作原理
假设您有一个reservations表。
id | name | date | time | party | last_modified |
---|---|---|---|---|---|
1 | Steve | 2015-04-15 | 19:00 | 4 | 2015-04-08 13:22:26.291670 |
2 | Phil | 2015-05-01 | 18:30 | 3 | 2015-04-13 09:38:01.060898 |
在幕后,我们创建一个映射到表reservations_rev的修订类ReservationRev。它具有相同的模式,并具有三个附加列:
- rev_id :string (uuid)
- Surrogate key for the revision table.
- rev_created :timestamp
- Timestamp (seconds since the epoch as a floating point number) of when the revision was created. (See Use of rev_created.)
- rev_isdelete :boolean
- Whether the entry was deleted. (See Use of rev_isdelete.)
每当您写入reservations表时,我们将在reservations_rev表中插入新行。这允许您使用reservations保持不变。如果需要,可以引用reservations_rev来获取修订时间。
示例
对于以下时间线:
- 2015年4月2日,史蒂夫在19:30预订了2015年4月15日的6人派对。
- 2015年4月3日,史蒂夫将预订改为4人。
- 2015年4月8日,史蒂夫取消了预订。
reservations_rev将具有以下内容
rev_id | rev_created | rev_isdelete | id | name | date | time | party | last_modified |
---|---|---|---|---|---|---|---|---|
c74d5bce… | 1427995346.0 | False | 1 | Steve | 2015-04-15 | 19:00 | 6 | 2015-04-02 13:22:26.291670 |
f3f5091d… | 1428068391.0 | False | 1 | Steve | 2015-04-15 | 19:00 | 4 | 2015-04-03 09:39:51.098798 |
3cf1394b… | 1428534191.0 | True | 1 | (null) | (null) | (null) | (null) | (null) |
设计决策
为所有写入操作写入修订表
对于所有写入,写入修订表有几个优点:
- complete transaction history in the revision table for easy reads (no joins required)
- complete timeline even if the original table doesn’t have a last modified column
但是,这种方法对于具有动态默认值(如序列或自动日期时间)的INSERT语句有一个特殊的缺点。插入时,修订表没有动态值。我们建议采取以下解决方法:
- generate dynamic defaults during object instantiation instead using database defaults
- strictly use client-side defaults in the ORM
- create server-side database triggers to copy values to revision table for inserts
- perform a write-read-write transaction for inserts, which is sub-optimal due to the performance hit
创建的rev_的使用
要重新创建修订时间线,我们依赖于时间戳的使用。虽然我们认识到在不同的服务器之间可能存在时钟漂移或不同步,但有解决这些问题的方法。因此,我们选择继续使用timestamp的简单性。
使用rev廑isdelete
rev_isdelete是一种快速方便的方法,可以在不检查条目的情况下确定行已被删除。它还允许所有为空的条目。
主键/复合键的要求
待定
关联对象对多对多关系的要求
待定