detect row-switches for version_id_col

Issue #1692 resolved
Former user created an issue

First, version_id_col doesn't respect row switches. Solution is to fetch the old version from the row_switch state.

Second issue is similiar, but more like an "offline row-switch" where version=1. Consider two concurrent transactions:

# tx1
x = db.query(X).get(1) # version = 1
db.delete(x)
db.commit()
# ...
db.add(X(data='"offline" row-switch'))
db.commit() # version = 1

# tx2 concurrent with above
x = db2.query(X).get(1) # version = 1
x.attr = 'I overwrote row-switch!'
db.commit() # version = 2

You may think that it's just an edge case for version=1, but that's probably the most common version number!

The solution in this case is to use version identifiers that aren't local to the object, e.g. UUIDs.

I have a patch that allows supplying a version_id_generator function, defaulting to an integer increment.

Comments (5)

  1. Former user Account Deleted

    Can you at least get the first part in for the 0.6 release? The patch is trivial and fixes a bug (adding int to None):

    Traceback (most recent call last):
      File "test_occ_rowswitch.py", line 41, in <module>
        db.commit()
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/session.py", line 655, in commit
        self.transaction.commit()
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/session.py", line 368, in commit
        self._prepare_impl()
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/session.py", line 352, in _prepare_impl
        self.session.flush()
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/session.py", line 1325, in flush
        self._flush(objects)
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/session.py", line 1403, in _flush
        flush_context.execute()
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/unitofwork.py", line 261, in execute
        UOWExecutor().execute(self, tasks)
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/unitofwork.py", line 753, in execute
        self.execute_save_steps(trans, task)
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/unitofwork.py", line 768, in execute_save_steps
        self.save_objects(trans, task)
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/unitofwork.py", line 759, in save_objects
        task.mapper._save_obj(task.polymorphic_tosave_objects, trans)
      File "/home/avdd/tmp/src/sqlalchemy.6/lib/sqlalchemy/orm/mapper.py", line 1377, in _save_obj
        params[col.key](col.key) = params[col._label](col._label) + 1
    TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
    
  2. Log in to comment