fix(db): graceful fallback when SQLite < 3.35 on MCP bindings migration

SQLite gained ALTER TABLE … DROP COLUMN in 3.35 (Mar 2021). Production
PyInstaller builds bundle Python 3.12 which links to SQLite 3.40+ so
that path is always safe, but a dev running the backend directly on
Ubuntu 20.04 (3.31) or Debian 11 (3.34) would crash on first startup
trying to drop the legacy default_intent column.

Add _supports_drop_column(engine) — returns True on non-SQLite
dialects (Postgres / MySQL have supported DROP COLUMN for decades) and
gates on the runtime sqlite_version for SQLite. When unsupported, log a
warning and leave the unused column in place: SQLAlchemy only maps
declared columns, so a stray default_intent column does no reads or
writes and can't interfere with runtime behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
James Pine
2026-04-23 19:18:41 -07:00
co-authored by Claude Opus 4.7
parent 0081e97ad7
commit c0eba9c628
+31 -4
View File
@@ -252,10 +252,37 @@ def _migrate_mcp_bindings(engine, inspector, tables: set[str]) -> None:
"default_personality",
)
if "default_intent" in columns:
with engine.connect() as conn:
conn.execute(text("ALTER TABLE mcp_client_bindings DROP COLUMN default_intent"))
conn.commit()
logger.info("Dropped legacy default_intent column from mcp_client_bindings")
if _supports_drop_column(engine):
with engine.connect() as conn:
conn.execute(text("ALTER TABLE mcp_client_bindings DROP COLUMN default_intent"))
conn.commit()
logger.info("Dropped legacy default_intent column from mcp_client_bindings")
else:
# ALTER TABLE … DROP COLUMN on SQLite requires 3.35+ (Mar
# 2021). Production PyInstaller builds bundle Python 3.12
# which links to SQLite 3.40+; this branch only fires for
# dev environments running the backend directly against an
# old system SQLite (Ubuntu 20.04 = 3.31, Debian 11 = 3.34).
# Leaving the unused column in place is harmless — the ORM
# only maps declared columns, so a stray one does no work
# and gets no reads or writes.
import sqlite3
logger.warning(
"SQLite %s too old to DROP COLUMN (need 3.35+); leaving unused default_intent column on mcp_client_bindings in place.",
sqlite3.sqlite_version,
)
def _supports_drop_column(engine) -> bool:
"""Whether ``ALTER TABLE … DROP COLUMN`` is supported by the dialect +
runtime. Non-SQLite dialects (Postgres, MySQL) have supported it for
decades; SQLite only gained the feature in 3.35."""
if engine.dialect.name != "sqlite":
return True
import sqlite3
return tuple(int(p) for p in sqlite3.sqlite_version.split(".")[:3]) >= (3, 35, 0)
def _normalize_storage_paths(engine, tables: set[str]) -> None: