From f8babde9f3f386bc73371b2170a1e5024738bd7b Mon Sep 17 00:00:00 2001 From: Henrik Koski Date: Thu, 17 Aug 2023 10:12:55 +0300 Subject: [PATCH] Fix editing a locked db 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 --- spinedb_api/db_mapping_add_mixin.py | 16 ++++++++++++---- spinedb_api/db_mapping_commit_mixin.py | 11 +++++++++-- spinedb_api/db_mapping_remove_mixin.py | 17 ++++++++++------- spinedb_api/db_mapping_update_mixin.py | 7 +++++-- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/spinedb_api/db_mapping_add_mixin.py b/spinedb_api/db_mapping_add_mixin.py index abd3b8db..a8447d14 100644 --- a/spinedb_api/db_mapping_add_mixin.py +++ b/spinedb_api/db_mapping_add_mixin.py @@ -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 @@ -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: diff --git a/spinedb_api/db_mapping_commit_mixin.py b/spinedb_api/db_mapping_commit_mixin.py index 025e186d..4bf39256 100644 --- a/spinedb_api/db_mapping_commit_mixin.py +++ b/spinedb_api/db_mapping_commit_mixin.py @@ -15,6 +15,7 @@ """ from datetime import datetime, timezone +import sqlalchemy.exc from .exception import SpineDBAPIError @@ -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: @@ -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: diff --git a/spinedb_api/db_mapping_remove_mixin.py b/spinedb_api/db_mapping_remove_mixin.py index f4718a3f..7ca479f0 100644 --- a/spinedb_api/db_mapping_remove_mixin.py +++ b/spinedb_api/db_mapping_remove_mixin.py @@ -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)) diff --git a/spinedb_api/db_mapping_update_mixin.py b/spinedb_api/db_mapping_update_mixin.py index e2c4fd62..e36fb2f7 100644 --- a/spinedb_api/db_mapping_update_mixin.py +++ b/spinedb_api/db_mapping_update_mixin.py @@ -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: @@ -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