fix(kanban): also hoist idx_events_run + drop redundant inner create

Extends the previous commit to cover the remaining additive-column index
that sits on the same migration trap:

- ``task_events.run_id`` -> ``idx_events_run`` was still in SCHEMA_SQL.
  A legacy ``task_events`` table predating #17805 (no ``run_id``) would
  still abort ``executescript`` before ``_migrate_add_optional_columns``
  could add the column. Hoisted out of SCHEMA_SQL and made unconditional
  in the migration alongside the other three indexes.

- Removed the now-redundant ``CREATE INDEX idx_tasks_idempotency`` that
  was nested inside the ``if "idempotency_key" not in cols`` branch.
  The unconditional create lower in the function makes it idempotent
  on both fresh and legacy DBs.

- Strengthened the regression test to cover all four indexes
  (``idx_tasks_session_id``, ``idx_tasks_tenant``, ``idx_tasks_idempotency``,
  ``idx_events_run``) and to seed a pre-#17805 ``task_events`` shape that
  exercises the ``run_id`` migration path.

The result: every ``CREATE INDEX`` that depends on an additive column now
runs after the migration ensures the column exists. Verified against a
realistic pre-#16081 board fixture (tasks + task_events both legacy
shape) — origin/main reproduces ``no such column: session_id``; this
branch migrates cleanly and creates all four indexes.
This commit is contained in:
kshitijk4poor 2026-05-19 20:01:59 +05:30 committed by kshitij
parent 7c622b6c74
commit 7552e0f3c0
2 changed files with 54 additions and 12 deletions

View file

@ -939,7 +939,6 @@ CREATE INDEX IF NOT EXISTS idx_links_child ON task_links(child_id);
CREATE INDEX IF NOT EXISTS idx_links_parent ON task_links(parent_id);
CREATE INDEX IF NOT EXISTS idx_comments_task ON task_comments(task_id, created_at);
CREATE INDEX IF NOT EXISTS idx_events_task ON task_events(task_id, created_at);
CREATE INDEX IF NOT EXISTS idx_events_run ON task_events(run_id, id);
CREATE INDEX IF NOT EXISTS idx_runs_task ON task_runs(task_id, started_at);
CREATE INDEX IF NOT EXISTS idx_runs_status ON task_runs(status);
CREATE INDEX IF NOT EXISTS idx_notify_task ON kanban_notify_subs(task_id);
@ -1079,10 +1078,9 @@ def _migrate_add_optional_columns(conn: sqlite3.Connection) -> None:
_add_column_if_missing(
conn, "tasks", "idempotency_key", "idempotency_key TEXT"
)
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_tasks_idempotency "
"ON tasks(idempotency_key)"
)
# ``idx_tasks_idempotency`` is created unconditionally below alongside
# the other additive-column indexes — see the block after the
# legacy-column migration. Creating it here too would be redundant.
# Refresh after early additive migrations above. Some existing DBs were
# partially migrated in older releases and can already contain the later
@ -1170,9 +1168,13 @@ def _migrate_add_optional_columns(conn: sqlite3.Connection) -> None:
conn, "tasks", "session_id", "session_id TEXT"
)
# Indexes over additive task columns must be created after the columns
# exist. Keeping them in SCHEMA_SQL breaks legacy boards because
# CREATE TABLE IF NOT EXISTS does not add new columns to existing tables.
# Indexes over additive ``tasks`` columns must be created after the
# columns exist. Keeping them in SCHEMA_SQL breaks legacy boards: SQLite
# parses each statement in ``executescript`` against the live schema, so a
# ``CREATE INDEX`` over a missing column aborts initialization before the
# additive ``ALTER TABLE`` migrations below can run. Re-running them here
# is cheap thanks to ``IF NOT EXISTS`` and stays correct on fresh DBs
# (where the columns already exist from SCHEMA_SQL).
conn.execute("CREATE INDEX IF NOT EXISTS idx_tasks_tenant ON tasks(tenant)")
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_tasks_idempotency ON tasks(idempotency_key)"
@ -1186,10 +1188,14 @@ def _migrate_add_optional_columns(conn: sqlite3.Connection) -> None:
ev_cols = {row["name"] for row in conn.execute("PRAGMA table_info(task_events)")}
if "run_id" not in ev_cols:
_add_column_if_missing(conn, "task_events", "run_id", "run_id INTEGER")
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_events_run "
"ON task_events(run_id, id)"
)
# Same ordering rule as the additive ``tasks`` indexes above: create the
# index after the additive column migration so legacy ``task_events``
# tables don't fail during SCHEMA_SQL execution before ``run_id`` exists.
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_events_run "
"ON task_events(run_id, id)"
)
notify_table_exists = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='kanban_notify_subs'"