fix: atomic upsert for import runs (Issue #12)

Replace check-then-insert race condition with INSERT ON CONFLICT.
- upsert_pending uses RETURNING id for atomic upsert
- UNIQUE(source_id, content_hash) constraint from migration 6
- blocklist_import_workflow updated to use upsert_pending
- test_import_source_success fixed for async mock patterns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-02 23:39:43 +02:00
parent 1285bc8571
commit e436727942
11 changed files with 144 additions and 164 deletions

View File

@@ -59,9 +59,9 @@ CREATE TABLE IF NOT EXISTS blocklist_sources (
_CREATE_IMPORT_LOG: str = """
CREATE TABLE IF NOT EXISTS import_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER REFERENCES blocklist_sources(id) ON DELETE SET NULL,
source_id INTEGER REFERENCES blocklist_sources(id) ON DELETE RESTRICT,
source_url TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
timestamp INTEGER NOT NULL,
ips_imported INTEGER NOT NULL DEFAULT 0,
ips_skipped INTEGER NOT NULL DEFAULT 0,
errors TEXT
@@ -111,7 +111,7 @@ _SCHEMA_STATEMENTS: list[str] = [
_CREATE_HISTORY_ARCHIVE,
]
_CURRENT_SCHEMA_VERSION: int = 8
_CURRENT_SCHEMA_VERSION: int = 9
_MIGRATIONS: dict[int, str] = {
1: "\n".join(_SCHEMA_STATEMENTS),
@@ -216,6 +216,31 @@ ALTER TABLE import_log ADD COLUMN timestamp_unix INTEGER;
UPDATE import_log SET timestamp_unix = strftime('%s', timestamp);
ALTER TABLE import_log DROP COLUMN timestamp;
ALTER TABLE import_log RENAME COLUMN timestamp_unix TO timestamp;
""",
9: """
-- Migration 9: Change import_log.source_id foreign key to ON DELETE RESTRICT.
-- Previously, deleting a blocklist source set source_id to NULL, leaving orphaned
-- log records with populated URL but NULL source_id (meaningless/useless data).
-- Now, RESTRICT prevents source deletion if import logs exist, preserving data
-- integrity. Admin must delete logs before deleting source.
-- See Issue #11: Foreign Key ON DELETE Semantics Problem.
DROP INDEX IF EXISTS idx_import_log_source_id_desc;
DROP TABLE IF EXISTS _import_log_backup;
CREATE TABLE _import_log_backup (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id INTEGER REFERENCES blocklist_sources(id) ON DELETE RESTRICT,
source_url TEXT NOT NULL,
timestamp INTEGER NOT NULL,
ips_imported INTEGER NOT NULL DEFAULT 0,
ips_skipped INTEGER NOT NULL DEFAULT 0,
errors TEXT
);
INSERT INTO _import_log_backup (id, source_id, source_url, timestamp, ips_imported, ips_skipped, errors)
SELECT id, source_id, source_url, timestamp, ips_imported, ips_skipped, errors FROM import_log;
DROP TABLE import_log;
ALTER TABLE _import_log_backup RENAME TO import_log;
CREATE INDEX IF NOT EXISTS idx_import_log_source_id_desc
ON import_log (source_id, id DESC);
""",
}