Allow PDB.rollback to take a savepoint; Warn early commits.

If a @transaction method makes calls to other @transaction
methods, and one of those nested calls makes a commit, and then
the outer call raises an exception, then the outer call will
not rollback properly because its savepoint is no longer in
the savepoint stack. So let's warn the user if that happens.
Should this raise an exception instead of just warn? Not sure,
I mean the data is already committed.
master
voussoir 2018-03-05 21:35:36 -08:00
parent 26141f8198
commit d88db08693
3 changed files with 18 additions and 10 deletions

View File

@ -66,6 +66,7 @@ If you are interested in helping, please raise an issue before making any pull r
- Fix album size cache when photo reload metadata and generally improve that validation.
- Better bookmark url validation.
- Create a textbox which gives autocomplete tag names.
- Consider if the "did you commit too early" warning should actually be an exception.
### To do list: User permissions
Here are some thoughts about the kinds of features that need to exist within the permission system. I don't know how I'll actually manage it just yet. Possibly a `permissions` table in the database with `user_id | permission` where `permission` is some reliably-formatted string.

View File

@ -72,11 +72,11 @@ def transaction(method):
@functools.wraps(method)
def wrapped_transaction(self, *args, **kwargs):
photodb = _get_relevant_photodb(self)
photodb.savepoint()
savepoint_id = photodb.savepoint()
try:
result = method(self, *args, **kwargs)
except Exception as e:
photodb.rollback()
photodb.rollback(savepoint=savepoint_id)
raise
else:
return result

View File

@ -703,20 +703,27 @@ class PDBSQLMixin:
self.savepoints.clear()
self.sql.commit()
def rollback(self):
def rollback(self, savepoint=None):
if savepoint is not None:
valid_savepoint = savepoint in self.savepoints
else:
valid_savepoint = None
if valid_savepoint is False:
self.log.warn('Tried to restore to a nonexistent savepoint. Did you commit too early?')
if len(self.savepoints) == 0:
self.log.debug('Nothing to rollback.')
return
if len(self.savepoints) == 1:
self.log.debug('Final rollback.')
self.sql.rollback()
self.savepoints.clear()
self.on_commit_queue.clear()
return
if valid_savepoint:
restore_to = savepoint
while self.savepoints.pop(-1) != restore_to:
pass
else:
restore_to = self.savepoints.pop(-1)
cur = self.sql.cursor()
restore_to = self.savepoints.pop(-1)
self.log.debug('Rolling back to %s', restore_to)
query = 'ROLLBACK TO "%s"' % restore_to
cur.execute(query)