fix: авто-миграция SQLite — добавление недостающих колонок из моделей
При добавлении нового поля в модель (report в Finding) старое create_all не добавляет колонку в существующую БД → 500 ошибка. Теперь _migrate() проверяет PRAGMA table_info и добавляет недостающие колонки через ALTER TABLE
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
"""Async SQLite database setup via SQLAlchemy."""
|
"""Async SQLite database setup via SQLAlchemy."""
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy import inspect, text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
from sqlalchemy.orm import DeclarativeBase
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
from guarddog_nexus.config import config
|
from guarddog_nexus.config import config
|
||||||
|
from guarddog_nexus.logging_setup import log
|
||||||
|
|
||||||
DATABASE_URL = f"sqlite+aiosqlite:///{config.database_path}"
|
DATABASE_URL = f"sqlite+aiosqlite:///{config.database_path}"
|
||||||
|
|
||||||
@@ -15,11 +18,56 @@ class Base(DeclarativeBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def _migrate():
|
||||||
|
"""Add any missing columns from model definitions to existing SQLite tables."""
|
||||||
|
import guarddog_nexus.models # noqa: F401
|
||||||
|
|
||||||
|
async with _engine.connect() as conn:
|
||||||
|
for table in Base.metadata.sorted_tables:
|
||||||
|
# Get existing columns in DB
|
||||||
|
col_names = []
|
||||||
|
try:
|
||||||
|
existing = await conn.run_sync(
|
||||||
|
lambda c: [col["name"] for col in inspect(c).get_columns(table.name)]
|
||||||
|
)
|
||||||
|
col_names = existing
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add missing model columns
|
||||||
|
for col in table.columns:
|
||||||
|
if col.name not in col_names:
|
||||||
|
col_type = col.type.compile(_engine.dialect)
|
||||||
|
nullable = "" if col.nullable else " NOT NULL"
|
||||||
|
default = ""
|
||||||
|
if col.default and col.default.arg is not None:
|
||||||
|
default_val = col.default.arg
|
||||||
|
if isinstance(default_val, str):
|
||||||
|
default = f" DEFAULT '{default_val}'"
|
||||||
|
else:
|
||||||
|
default = f" DEFAULT {default_val}"
|
||||||
|
if col.server_default:
|
||||||
|
# Skip — func.now() etc. not trivially stringable
|
||||||
|
pass
|
||||||
|
|
||||||
|
sql = (
|
||||||
|
f"ALTER TABLE {table.name} ADD COLUMN "
|
||||||
|
f"{col.name} {col_type}{nullable}{default}"
|
||||||
|
)
|
||||||
|
log.info("Migration: %s", sql)
|
||||||
|
try:
|
||||||
|
await conn.execute(text(sql))
|
||||||
|
await conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Migration skipped (may already exist): %s", e)
|
||||||
|
|
||||||
|
|
||||||
async def init_db():
|
async def init_db():
|
||||||
import guarddog_nexus.models # noqa: F401
|
import guarddog_nexus.models # noqa: F401
|
||||||
|
|
||||||
async with _engine.begin() as conn:
|
async with _engine.begin() as conn:
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
|
await _migrate()
|
||||||
|
|
||||||
|
|
||||||
async def get_session() -> AsyncSession:
|
async def get_session() -> AsyncSession:
|
||||||
|
|||||||
Reference in New Issue
Block a user