📝 Commit Details:
This commit is contained in:
252
backend/utils/database_migration.py
Normal file
252
backend/utils/database_migration.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database Migration Utility für MYP Platform
|
||||
Überprüft und aktualisiert die Datenbankschema automatisch.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from config.settings import DATABASE_PATH
|
||||
from models import init_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_table_columns(table_name: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Ruft die Spalten einer Tabelle ab.
|
||||
|
||||
Args:
|
||||
table_name: Name der Tabelle
|
||||
|
||||
Returns:
|
||||
List[Dict]: Liste der Spalten mit ihren Eigenschaften
|
||||
"""
|
||||
try:
|
||||
conn = sqlite3.connect(DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f'PRAGMA table_info({table_name})')
|
||||
columns = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
return [
|
||||
{
|
||||
'name': col[1],
|
||||
'type': col[2],
|
||||
'not_null': bool(col[3]),
|
||||
'default': col[4],
|
||||
'primary_key': bool(col[5])
|
||||
}
|
||||
for col in columns
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Spalten für Tabelle {table_name}: {e}")
|
||||
return []
|
||||
|
||||
def table_exists(table_name: str) -> bool:
|
||||
"""
|
||||
Prüft, ob eine Tabelle existiert.
|
||||
|
||||
Args:
|
||||
table_name: Name der Tabelle
|
||||
|
||||
Returns:
|
||||
bool: True wenn die Tabelle existiert
|
||||
"""
|
||||
try:
|
||||
conn = sqlite3.connect(DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name=?
|
||||
""", (table_name,))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
return result is not None
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Prüfen der Tabelle {table_name}: {e}")
|
||||
return False
|
||||
|
||||
def column_exists(table_name: str, column_name: str) -> bool:
|
||||
"""
|
||||
Prüft, ob eine Spalte in einer Tabelle existiert.
|
||||
|
||||
Args:
|
||||
table_name: Name der Tabelle
|
||||
column_name: Name der Spalte
|
||||
|
||||
Returns:
|
||||
bool: True wenn die Spalte existiert
|
||||
"""
|
||||
columns = get_table_columns(table_name)
|
||||
return any(col['name'] == column_name for col in columns)
|
||||
|
||||
def add_column_if_missing(table_name: str, column_name: str, column_type: str, default_value: str = None) -> bool:
|
||||
"""
|
||||
Fügt eine Spalte hinzu, falls sie nicht existiert.
|
||||
|
||||
Args:
|
||||
table_name: Name der Tabelle
|
||||
column_name: Name der Spalte
|
||||
column_type: Datentyp der Spalte
|
||||
default_value: Optional - Standardwert
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich
|
||||
"""
|
||||
if column_exists(table_name, column_name):
|
||||
logger.info(f"Spalte {column_name} existiert bereits in Tabelle {table_name}")
|
||||
return True
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"
|
||||
if default_value:
|
||||
sql += f" DEFAULT {default_value}"
|
||||
|
||||
cursor.execute(sql)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Spalte {column_name} erfolgreich zu Tabelle {table_name} hinzugefügt")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Hinzufügen der Spalte {column_name} zu Tabelle {table_name}: {e}")
|
||||
return False
|
||||
|
||||
def migrate_database() -> bool:
|
||||
"""
|
||||
Führt alle notwendigen Datenbankmigrationen durch.
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich
|
||||
"""
|
||||
logger.info("Starte Datenbankmigration...")
|
||||
|
||||
try:
|
||||
# Prüfe, ob grundlegende Tabellen existieren
|
||||
required_tables = ['users', 'printers', 'jobs', 'stats']
|
||||
missing_tables = [table for table in required_tables if not table_exists(table)]
|
||||
|
||||
if missing_tables:
|
||||
logger.warning(f"Fehlende Tabellen gefunden: {missing_tables}")
|
||||
logger.info("Erstelle alle Tabellen neu...")
|
||||
init_db()
|
||||
logger.info("Tabellen erfolgreich erstellt")
|
||||
return True
|
||||
|
||||
# Prüfe spezifische Spalten, die möglicherweise fehlen
|
||||
migrations = [
|
||||
# Printers Tabelle
|
||||
('printers', 'last_checked', 'DATETIME', 'NULL'),
|
||||
('printers', 'active', 'BOOLEAN', '1'),
|
||||
('printers', 'created_at', 'DATETIME', 'CURRENT_TIMESTAMP'),
|
||||
|
||||
# Jobs Tabelle
|
||||
('jobs', 'duration_minutes', 'INTEGER', '60'),
|
||||
('jobs', 'actual_end_time', 'DATETIME', 'NULL'),
|
||||
('jobs', 'owner_id', 'INTEGER', 'NULL'),
|
||||
('jobs', 'file_path', 'VARCHAR(500)', 'NULL'),
|
||||
|
||||
# Users Tabelle
|
||||
('users', 'username', 'VARCHAR(100)', 'NULL'),
|
||||
('users', 'active', 'BOOLEAN', '1'),
|
||||
('users', 'created_at', 'DATETIME', 'CURRENT_TIMESTAMP'),
|
||||
]
|
||||
|
||||
success = True
|
||||
for table_name, column_name, column_type, default_value in migrations:
|
||||
if not add_column_if_missing(table_name, column_name, column_type, default_value):
|
||||
success = False
|
||||
|
||||
if success:
|
||||
logger.info("Datenbankmigration erfolgreich abgeschlossen")
|
||||
else:
|
||||
logger.warning("Datenbankmigration mit Fehlern abgeschlossen")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Datenbankmigration: {e}")
|
||||
return False
|
||||
|
||||
def check_database_integrity() -> bool:
|
||||
"""
|
||||
Überprüft die Integrität der Datenbank.
|
||||
|
||||
Returns:
|
||||
bool: True wenn die Datenbank integer ist
|
||||
"""
|
||||
try:
|
||||
conn = sqlite3.connect(DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('PRAGMA integrity_check')
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if result and result[0] == 'ok':
|
||||
logger.info("Datenbankintegrität: OK")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Datenbankintegrität: FEHLER - {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Integritätsprüfung: {e}")
|
||||
return False
|
||||
|
||||
def backup_database(backup_path: str = None) -> bool:
|
||||
"""
|
||||
Erstellt ein Backup der Datenbank.
|
||||
|
||||
Args:
|
||||
backup_path: Optional - Pfad für das Backup
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich
|
||||
"""
|
||||
if not backup_path:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = f"database/myp_backup_{timestamp}.db"
|
||||
|
||||
try:
|
||||
import shutil
|
||||
shutil.copy2(DATABASE_PATH, backup_path)
|
||||
logger.info(f"Datenbank-Backup erstellt: {backup_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen des Backups: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Logging konfigurieren
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
print("=== MYP Platform - Datenbankmigration ===")
|
||||
|
||||
# Backup erstellen
|
||||
if backup_database():
|
||||
print("✅ Backup erstellt")
|
||||
else:
|
||||
print("⚠️ Backup-Erstellung fehlgeschlagen")
|
||||
|
||||
# Integrität prüfen
|
||||
if check_database_integrity():
|
||||
print("✅ Datenbankintegrität OK")
|
||||
else:
|
||||
print("❌ Datenbankintegrität FEHLER")
|
||||
|
||||
# Migration durchführen
|
||||
if migrate_database():
|
||||
print("✅ Migration erfolgreich")
|
||||
else:
|
||||
print("❌ Migration fehlgeschlagen")
|
||||
|
||||
print("\nMigration abgeschlossen!")
|
Reference in New Issue
Block a user