93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
# custom
|
|
from core.errors.logger import logger
|
|
from core.models.DatabaseOperation import DatabaseOperation, DBErrorType
|
|
|
|
# generic
|
|
from pathlib import Path
|
|
from sqlalchemy import Column, Integer, String, create_engine, inspect, text
|
|
from sqlalchemy.orm import declarative_base, Session
|
|
from sqlalchemy.exc import OperationalError, DBAPIError
|
|
from sqlalchemy import inspect
|
|
|
|
"""
|
|
Purpose:
|
|
Sync SQL database schema to current Python model definitions.
|
|
|
|
Situation Used:
|
|
Python Models have new values missing from the same SQL database
|
|
|
|
Metaphor:
|
|
This replaces something like alembic, by doing it directly
|
|
|
|
Confusion:
|
|
This may be confusing because we're using SQLAlchemy,
|
|
but doing migrations manually.
|
|
|
|
"""
|
|
|
|
def migrate_sql() -> DatabaseOperation:
|
|
from core.models.manage.session_management import engine
|
|
from core.models.orm_models.Base import Base, BaseModel
|
|
|
|
try:
|
|
logger.info("[MIGRATION] Starting Migrations..")
|
|
inspector = inspect(engine)
|
|
existing_tables = inspector.get_table_names()
|
|
changes_made = []
|
|
|
|
for table in Base.metadata.tables.values():
|
|
table_name = table.name
|
|
|
|
if table_name not in existing_tables:
|
|
table.create(engine)
|
|
added_table = f"Created table: {table_name}"
|
|
changes_made.append(added_table)
|
|
logger.info(f"[MIGRATION] {added_table}")
|
|
continue
|
|
|
|
# Check for MISSING columns (add them)
|
|
existing_columns = {col['name'] for col in inspector.get_columns(table_name)}
|
|
model_columns = {col.name for col in table.columns}
|
|
missing_columns = model_columns - existing_columns
|
|
|
|
if missing_columns:
|
|
with engine.begin() as conn:
|
|
for col_name in missing_columns:
|
|
col = table.columns[col_name]
|
|
col_type = str(col.type.compile(engine.dialect))
|
|
alter_sql = f"ALTER TABLE {table_name} ADD COLUMN {col_name} {col_type}"
|
|
conn.execute(text(alter_sql))
|
|
|
|
changes_made.append(f"Added columns to {table_name}: {missing_columns}")
|
|
|
|
# Check for EXTRA columns (drop them)
|
|
extra_columns = existing_columns - model_columns
|
|
if extra_columns:
|
|
with engine.begin() as conn:
|
|
for col_name in extra_columns:
|
|
alter_sql = f"ALTER TABLE {table_name} DROP COLUMN {col_name}"
|
|
logger.info(f"[MIGRATION] Dropping extra column: {alter_sql}")
|
|
conn.execute(text(alter_sql))
|
|
|
|
changes_made.append(f"Dropped extra columns from {table_name}: {extra_columns}")
|
|
|
|
if changes_made:
|
|
logger.info(f"[MIGRATION] Completed! Schema synced successfully")
|
|
return DatabaseOperation(
|
|
valid=True,
|
|
data=changes_made,
|
|
message=f"Schema synced: {'; '.join(changes_made)}"
|
|
)
|
|
else:
|
|
return DatabaseOperation(
|
|
valid=True,
|
|
message="Schema already matches models"
|
|
)
|
|
|
|
except (OperationalError, DBAPIError) as e:
|
|
logger.error(f"[MIGRATION] Failed: {str(e)}")
|
|
return DatabaseOperation(
|
|
valid=False,
|
|
error_type=DBErrorType.UNKNOWN,
|
|
message=f"Schema sync failed: {str(e)}"
|
|
)
|