Skip to content

Commit

Permalink
Fix editing a locked db
Browse files Browse the repository at this point in the history
In a locked db if existing values are updated, the changes
are stored in the cache and can be committed when the db is
no longer locked. Removing items also works the same way.
Adding items to a locked db is denied and will result in
an error. If trying to commit changes in the db while it
is locked an error telling the commit failed pops up. When
the lock is resolved the changes can again be committed.

Re spine-tools/Spine-Toolbox#2201
  • Loading branch information
Henrik Koski committed Aug 17, 2023
1 parent f50f7ba commit f8babde
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 15 deletions.
16 changes: 12 additions & 4 deletions spinedb_api/db_mapping_add_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from datetime import datetime
from sqlalchemy import func, Table, Column, Integer, String, null, select
from sqlalchemy.exc import DBAPIError
from .exception import SpineDBAPIError
from .exception import SpineDBAPIError, SpineIntegrityError
from .helpers import get_relationship_entity_class_items, get_relationship_entity_items


Expand Down Expand Up @@ -149,15 +149,23 @@ def add_items(
list(SpineIntegrityError): found violations
"""
if readd:
self._readd_items(tablename, *items)
return items if return_items else {x["id"] for x in items}, []
try:
self._readd_items(tablename, *items)
return items if return_items else {x["id"] for x in items}, []
except SpineDBAPIError as e:
return set(), [e]

if check:
checked_items, intgr_error_log = self.check_items(
tablename, *items, for_update=False, strict=strict, cache=cache
)
else:
checked_items, intgr_error_log = list(items), []
ids = self._add_items(tablename, *checked_items)
try:
ids = self._add_items(tablename, *checked_items)
except DBAPIError as e:
intgr_error_log.append(SpineIntegrityError(f"Fail to add items: {e.orig.args}"))
return set(), intgr_error_log
if return_items:
return checked_items, intgr_error_log
if return_dups:
Expand Down
11 changes: 9 additions & 2 deletions spinedb_api/db_mapping_commit_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""

from datetime import datetime, timezone
import sqlalchemy.exc
from .exception import SpineDBAPIError


Expand Down Expand Up @@ -42,7 +43,10 @@ def _get_sqlite_lock(self):
def _make_commit_id(self):
if self._commit_id is None:
if self.committing:
self._get_sqlite_lock()
try:
self._get_sqlite_lock()
except:
raise SpineDBAPIError("Committing failed due to the database being locked")
self._commit_id = self._do_make_commit_id(self.connection)
else:
with self.engine.begin() as connection:
Expand All @@ -66,7 +70,10 @@ def commit_session(self, comment):
user = self.username
date = datetime.now(timezone.utc)
upd = commit.update().where(commit.c.id == self._make_commit_id())
self._checked_execute(upd, dict(user=user, date=date, comment=comment))
try:
self._checked_execute(upd, dict(user=user, date=date, comment=comment))
except sqlalchemy.exc.DBAPIError as e:
raise SpineDBAPIError(f"Fail to commit: {e}")
self.session.commit()
self._commit_id = None
if self._memory:
Expand Down
17 changes: 10 additions & 7 deletions spinedb_api/db_mapping_remove_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@ def cascading_ids(self, cache=None, **kwargs):
cascading_ids (dict): cascading ids keyed by table name
"""
if cache is None:
cache = self.make_cache(
set(kwargs),
include_descendants=True,
force_tablenames={"entity_metadata", "parameter_value_metadata"}
if any(x in kwargs for x in ("entity_metadata", "parameter_value_metadata", "metadata"))
else None,
)
try:
cache = self.make_cache(
set(kwargs),
include_descendants=True,
force_tablenames={"entity_metadata", "parameter_value_metadata"}
if any(x in kwargs for x in ("entity_metadata", "parameter_value_metadata", "metadata"))
else None,
)
except DBAPIError as e:
raise SpineDBAPIError(f"Fail to get cascading ids: {e.orig.args}")
ids = {}
self._merge(ids, self._object_class_cascading_ids(kwargs.get("object_class", set()), cache))
self._merge(ids, self._object_cascading_ids(kwargs.get("object", set()), cache))
Expand Down
7 changes: 5 additions & 2 deletions spinedb_api/db_mapping_update_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from collections import Counter
from sqlalchemy.exc import DBAPIError
from sqlalchemy.sql.expression import bindparam
from .exception import SpineDBAPIError
from .exception import SpineDBAPIError, SpineIntegrityError


class DatabaseMappingUpdateMixin:
Expand Down Expand Up @@ -78,7 +78,10 @@ def update_items(self, tablename, *items, check=True, strict=False, return_items
)
else:
checked_items, intgr_error_log = list(items), []
updated_ids = self._update_items(tablename, *checked_items)
try:
updated_ids = self._update_items(tablename, *checked_items)
except SpineDBAPIError as e:
intgr_error_log.append(f"Fail to update items: {e}")
if return_items:
return checked_items, intgr_error_log
return updated_ids, intgr_error_log
Expand Down

0 comments on commit f8babde

Please sign in to comment.