Skip to content

Commit 8c0574e

Browse files
committed
fix(db_sqlite): self-heal cached db on integrity and data errors
insert(), replace() and delete() now delete and rebuild the cache database on sqlite3.IntegrityError and sqlite3.DataError (gated by delete_db_on_operational_error), not only on OperationalError. A schema change between releases (for example a NOT NULL column the caller no longer writes) previously raised an IntegrityError that fell through to the generic handler, so the stale database kept failing on every run until it was removed by hand.
1 parent c3124ee commit 8c0574e

2 files changed

Lines changed: 41 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11-
tbd
11+
### Fixed
12+
13+
* db_sqlite.py: cached databases now self-heal after a schema change between releases. A constraint or data error (such as a `NOT NULL` column that a newer or older release no longer writes) deletes and rebuilds the cache on the next run, instead of failing on every run until the stale file is removed by hand.
1214

1315

1416
## [v5.0.0] - 2026-06-12

db_sqlite.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"""
2929

3030
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
31-
__version__ = '2026060201'
31+
__version__ = '2026061701'
3232

3333
import csv
3434
import hashlib
@@ -355,7 +355,8 @@ def create_index(
355355
If `True`, creates a unique index.
356356
If `False`, creates a standard (non-unique) index. Defaults to `False`.
357357
- **delete_db_on_operational_error** (`bool`, optional):
358-
If `True`, deletes the database file when an `OperationalError` occurs.
358+
If `True`, deletes the database file when the on-disk database turns out
359+
to be unusable (e.g. a schema mismatch between releases).
359360
Defaults to `True`.
360361
361362
### Returns
@@ -467,7 +468,8 @@ def cut(conn, table='perfdata', _max=5, delete_db_on_operational_error=True):
467468
- **_max** (`int`, optional):
468469
Number of most recent records to keep. Defaults to `5`.
469470
- **delete_db_on_operational_error** (`bool`, optional):
470-
If `True`, deletes the database file when an `OperationalError` occurs.
471+
If `True`, deletes the database file when the on-disk database turns out
472+
to be unusable (e.g. a schema mismatch between releases).
471473
Defaults to `True`.
472474
473475
### Returns
@@ -530,7 +532,8 @@ def delete(conn, sql, data=None, delete_db_on_operational_error=True):
530532
Dictionary of parameters to bind to the SQL statement.
531533
Defaults to an empty dict (no parameters).
532534
- **delete_db_on_operational_error** (`bool`, optional):
533-
If `True`, deletes the database file when an `OperationalError` occurs.
535+
If `True`, deletes the database file when the on-disk database turns out
536+
to be unusable (e.g. a schema mismatch between releases).
534537
Defaults to `True`.
535538
536539
### Returns
@@ -563,6 +566,14 @@ def delete(conn, sql, data=None, delete_db_on_operational_error=True):
563566
if delete_db_on_operational_error:
564567
rm_db(conn)
565568
return False, f'Operational Error: {e}, Query: {sql}, Data: {data}'
569+
except (sqlite3.IntegrityError, sqlite3.DataError) as e:
570+
# A constraint or data error (e.g. a NOT NULL column that a newer or
571+
# older release no longer writes) means the on-disk schema no
572+
# longer matches the data being written. Deleting the database lets the
573+
# next run rebuild a valid cache from scratch.
574+
if delete_db_on_operational_error:
575+
rm_db(conn)
576+
return False, f'Integrity Error: {e}, Query: {sql}, Data: {data}'
566577
except Exception as e:
567578
return False, f'Query failed: {sql}, Error: {e}, Data: {data}'
568579

@@ -906,7 +917,8 @@ def insert(conn, data, table='perfdata', delete_db_on_operational_error=True):
906917
Name of the table to insert into.
907918
Defaults to `'perfdata'`.
908919
- **delete_db_on_operational_error** (`bool`, optional):
909-
If `True`, deletes the database file when an `OperationalError` occurs.
920+
If `True`, deletes the database file when the on-disk database turns out
921+
to be unusable (e.g. a schema mismatch between releases).
910922
Defaults to `True`.
911923
912924
### Returns
@@ -946,6 +958,14 @@ def insert(conn, data, table='perfdata', delete_db_on_operational_error=True):
946958
if delete_db_on_operational_error:
947959
rm_db(conn)
948960
return False, f'Operational Error: {e}, Query: {sql}, Data: {data}'
961+
except (sqlite3.IntegrityError, sqlite3.DataError) as e:
962+
# A constraint or data error (e.g. a NOT NULL column that a newer or
963+
# older release no longer writes) means the on-disk schema no
964+
# longer matches the data being written. Deleting the database lets the
965+
# next run rebuild a valid cache from scratch.
966+
if delete_db_on_operational_error:
967+
rm_db(conn)
968+
return False, f'Integrity Error: {e}, Query: {sql}, Data: {data}'
949969
except Exception as e:
950970
return False, f'Query failed: {sql}, Error: {e}, Data: {data}'
951971

@@ -1030,7 +1050,7 @@ def per_second_deltas(filename, name, counters):
10301050
# subsequent drop_table() with "Cannot operate on a closed database".
10311051
ok, _ = insert(conn, row, delete_db_on_operational_error=False)
10321052
if not ok:
1033-
# Schema mismatch from a previous plugin version (different counter
1053+
# Schema mismatch from a previous release (different counter
10341054
# columns or NOT NULL constraints). Rebuild the table from the
10351055
# current schema; we lose the previous baseline but auto-recover on
10361056
# the next run.
@@ -1130,7 +1150,8 @@ def replace(conn, data, table='perfdata', delete_db_on_operational_error=True):
11301150
Name of the table to operate on.
11311151
Defaults to `'perfdata'`.
11321152
- **delete_db_on_operational_error** (`bool`, optional):
1133-
If `True`, deletes the database file when an `OperationalError` occurs.
1153+
If `True`, deletes the database file when the on-disk database turns out
1154+
to be unusable (e.g. a schema mismatch between releases).
11341155
Defaults to `True`.
11351156
11361157
### Returns
@@ -1169,6 +1190,14 @@ def replace(conn, data, table='perfdata', delete_db_on_operational_error=True):
11691190
if delete_db_on_operational_error:
11701191
rm_db(conn)
11711192
return False, f'Operational Error: {e}, Query: {sql}, Data: {data}'
1193+
except (sqlite3.IntegrityError, sqlite3.DataError) as e:
1194+
# A constraint or data error (e.g. a NOT NULL column that a newer or
1195+
# older release no longer writes) means the on-disk schema no
1196+
# longer matches the data being written. Deleting the database lets the
1197+
# next run rebuild a valid cache from scratch.
1198+
if delete_db_on_operational_error:
1199+
rm_db(conn)
1200+
return False, f'Integrity Error: {e}, Query: {sql}, Data: {data}'
11721201
except Exception as e:
11731202
return False, f'Query failed: {sql}, Error: {e}, Data: {data}'
11741203

@@ -1237,7 +1266,8 @@ def select(
12371266
If `True`, return results as a list of dictionaries.
12381267
If `False`, return raw SQLite row objects. Defaults to `True`.
12391268
- **delete_db_on_operational_error** (`bool`, optional):
1240-
If `True`, deletes the database file when an `OperationalError` occurs.
1269+
If `True`, deletes the database file when the on-disk database turns out
1270+
to be unusable (e.g. a schema mismatch between releases).
12411271
Defaults to `True`.
12421272
12431273
### Returns

0 commit comments

Comments
 (0)