From 73625928802e5b3b80de08e5f666273c2f28bd69 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 13 May 2026 13:33:35 +0200 Subject: [PATCH] make tasks migration idempotent and fix sqlite index syntax m001 was leaving partial state on first boot: CREATE TABLE succeeded but `CREATE UNIQUE INDEX ... ON tasks.tasks (...)` failed silently in sqlite (schema prefix on the target table, not the index name, is not what sqlite expects for ATTACHed databases). Because LNbits never recorded the migration version, every restart retried m001 and crashed on `CREATE TABLE tasks already exists`. Two fixes: - Use IF NOT EXISTS on every CREATE so a partial run is safe to re-run. - Schema-prefix the index *name*, not the target table: `CREATE INDEX tasks.idx ON tbl (...)` instead of `CREATE INDEX idx ON tasks.tbl (...)`. Co-Authored-By: Claude Opus 4.7 (1M context) --- migrations.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/migrations.py b/migrations.py index 93c27a9..a2f8e61 100644 --- a/migrations.py +++ b/migrations.py @@ -2,10 +2,12 @@ async def m001_initial(db): """ Tasks table (NIP-52 kind 31922 cache) and completions table (kind 31925 cache). The (pubkey, d_tag) pair is the parameterized-replaceable key. + Uses IF NOT EXISTS throughout so a partial first-run leaves the + migration safely re-runnable. """ await db.execute( f""" - CREATE TABLE tasks.tasks ( + CREATE TABLE IF NOT EXISTS tasks.tasks ( id TEXT PRIMARY KEY, wallet TEXT NOT NULL, pubkey TEXT NOT NULL, @@ -26,14 +28,17 @@ async def m001_initial(db): ); """ ) + # Schema prefix goes on the INDEX NAME (not the target table) in sqlite + # when the schema is an ATTACHed database — `CREATE INDEX schema.idx`, + # not `CREATE INDEX idx ON schema.tbl`. await db.execute( - "CREATE UNIQUE INDEX tasks_pubkey_dtag_idx " - "ON tasks.tasks (pubkey, d_tag)" + "CREATE UNIQUE INDEX IF NOT EXISTS tasks.tasks_pubkey_dtag_idx " + "ON tasks (pubkey, d_tag)" ) await db.execute( f""" - CREATE TABLE tasks.completions ( + CREATE TABLE IF NOT EXISTS tasks.completions ( id TEXT PRIMARY KEY, task_address TEXT NOT NULL, pubkey TEXT NOT NULL, @@ -46,16 +51,13 @@ async def m001_initial(db): ); """ ) - # occurrence is NULL for one-shot tasks; the (task_address, pubkey, - # occurrence) tuple identifies "the latest claim by this user for this - # specific occurrence". await db.execute( - "CREATE INDEX tasks_completions_address_idx " - "ON tasks.completions (task_address)" + "CREATE INDEX IF NOT EXISTS tasks.tasks_completions_address_idx " + "ON completions (task_address)" ) await db.execute( - "CREATE INDEX tasks_completions_pubkey_idx " - "ON tasks.completions (pubkey)" + "CREATE INDEX IF NOT EXISTS tasks.tasks_completions_pubkey_idx " + "ON completions (pubkey)" )