Added migration logic

This commit is contained in:
Sven Heidemann 2021-12-07 16:38:42 +01:00
parent d5de1991eb
commit ca361b63cb
9 changed files with 247 additions and 6 deletions

View File

@ -3,8 +3,10 @@ from cpl_core.configuration import ConfigurationABC
from cpl_core.console import Console from cpl_core.console import Console
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC from cpl_core.logging import LoggerABC
from gismo_core.abc.bot_service_abc import BotServiceABC from gismo_core.abc.bot_service_abc import BotServiceABC
from gismo_core.service.bot_service import BotService from gismo_core.service.bot_service import BotService
from gismo_data.service.migration_service import MigrationService
class Gismo(ApplicationABC): class Gismo(ApplicationABC):
@ -14,12 +16,14 @@ class Gismo(ApplicationABC):
self._bot: BotService = services.get_service(BotServiceABC) self._bot: BotService = services.get_service(BotServiceABC)
self._logger: LoggerABC = services.get_service(LoggerABC) self._logger: LoggerABC = services.get_service(LoggerABC)
self._migrations: MigrationService = services.get_service(MigrationService)
async def configure(self): async def configure(self):
pass pass
async def main(self): async def main(self):
try: try:
self._migrations.migrate()
self._logger.trace(__name__, f'Try to start {BotService}') self._logger.trace(__name__, f'Try to start {BotService}')
await self._bot.start_async() await self._bot.start_async()
except Exception as e: except Exception as e:

View File

@ -15,12 +15,17 @@ from gismo_core.abc.message_service_abc import MessageServiceABC
from gismo_core.service.bot_service import BotService from gismo_core.service.bot_service import BotService
from gismo_core.service.message_service import MessageService from gismo_core.service.message_service import MessageService
from gismo_data.abc.known_user_repository_abc import KnownUserRepositoryABC from gismo_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from gismo_data.abc.migration_abc import MigrationABC
from gismo_data.abc.server_repository_abc import ServerRepositoryABC from gismo_data.abc.server_repository_abc import ServerRepositoryABC
from gismo_data.abc.user_repository_abc import UserRepositoryABC from gismo_data.abc.user_repository_abc import UserRepositoryABC
from gismo_data.db_context import DBContext from gismo_data.db_context import DBContext
from gismo_data.migration.initial_migration import InitialMigration
from gismo_data.migration.migration_0_3 import Migration_0_3
from gismo_data.service.client_repository_service import ( from gismo_data.service.client_repository_service import (
ClientRepositoryABC, ClientRepositoryService) ClientRepositoryABC, ClientRepositoryService)
from gismo_data.service.known_user_repository_service import KnownUserRepositoryService from gismo_data.service.known_user_repository_service import \
KnownUserRepositoryService
from gismo_data.service.migration_service import MigrationService
from gismo_data.service.server_repository_service import \ from gismo_data.service.server_repository_service import \
ServerRepositoryService ServerRepositoryService
from gismo_data.service.user_repository_service import UserRepositoryService from gismo_data.service.user_repository_service import UserRepositoryService
@ -58,19 +63,29 @@ class Startup(StartupABC):
services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings)) services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings))
# modules
services.add_singleton(ModuleServiceABC, ModuleService) services.add_singleton(ModuleServiceABC, ModuleService)
services.add_singleton(BotServiceABC, BotService) services.add_singleton(BotServiceABC, BotService)
services.add_transient(MessageServiceABC, MessageService) services.add_transient(MessageServiceABC, MessageService)
# services
services.add_transient(MigrationService)
# data services
services.add_transient(ServerRepositoryABC, ServerRepositoryService) services.add_transient(ServerRepositoryABC, ServerRepositoryService)
services.add_transient(UserRepositoryABC, UserRepositoryService) services.add_transient(UserRepositoryABC, UserRepositoryService)
services.add_transient(ClientRepositoryABC, ClientRepositoryService) services.add_transient(ClientRepositoryABC, ClientRepositoryService)
services.add_transient(KnownUserRepositoryABC, KnownUserRepositoryService) services.add_transient(KnownUserRepositoryABC, KnownUserRepositoryService)
# modules
services.add_transient(ModuleABC, Database) services.add_transient(ModuleABC, Database)
services.add_transient(ModuleABC, Base) services.add_transient(ModuleABC, Base)
services.add_transient(ModuleABC, BootLog) services.add_transient(ModuleABC, BootLog)
# migrations
services.add_transient(MigrationABC, InitialMigration)
services.add_transient(MigrationABC, Migration_0_3)
provider: ServiceProviderABC = services.build_service_provider() provider: ServiceProviderABC = services.build_service_provider()
startup_init_time = round((datetime.now() - self._config.get_configuration('Startup_StartTime')).total_seconds(), 2) startup_init_time = round((datetime.now() - self._config.get_configuration('Startup_StartTime')).total_seconds(), 2)

View File

@ -0,0 +1,13 @@
from abc import ABC, abstractmethod
class MigrationABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def upgrade(self): pass
@abstractmethod
def downgrade(self): pass

View File

@ -16,10 +16,6 @@ class DBContext(DatabaseContext):
try: try:
self._logger.debug(__name__, "Connecting to database") self._logger.debug(__name__, "Connecting to database")
self._db.connect(database_settings) self._db.connect(database_settings)
for table in self._tables:
self._logger.debug(__name__, f"Create table if not exists: {table}")
self._logger.trace(__name__, f'Send SQL command: {table.get_create_string()}')
self._db.cursor.execute(table.get_create_string())
self.save_changes() self.save_changes()
self._logger.info(__name__, "Connected to database") self._logger.info(__name__, "Connected to database")

View File

@ -0,0 +1 @@
# imports

View File

@ -0,0 +1,57 @@
from cpl_core.logging import LoggerABC
from gismo_data.abc.migration_abc import MigrationABC
from gismo_data.db_context import DBContext
class InitialMigration(MigrationABC):
def __init__(self, logger: LoggerABC, db: DBContext):
self._logger = logger
self._db = db
self._cursor = db.cursor
def upgrade(self):
self._logger.debug(__name__, 'Running upgrade')
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `MigrationHistory` (
`MigrationId` VARCHAR(255),
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`MigrationId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `Servers` (
`ServerId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordServerId` BIGINT NOT NULL,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`ServerId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `Users` (
`UserId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordId` BIGINT NOT NULL,
`XP` BIGINT NOT NULL DEFAULT 0,
`ServerId` BIGINT,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`ServerId`) REFERENCES Servers(`ServerId`),
PRIMARY KEY(`UserId`)
);
""")
)
def downgrade(self):
self._cursor.execute('DROP TABLE `Servers`;')
self._cursor.execute('DROP TABLE `Users`;')

View File

@ -0,0 +1,65 @@
from cpl_core.logging import LoggerABC
from gismo_data.abc.migration_abc import MigrationABC
from gismo_data.db_context import DBContext
class Migration_0_3(MigrationABC):
def __init__(self, logger: LoggerABC, db: DBContext):
self._logger = logger
self._db = db
self._cursor = db.cursor
def upgrade(self):
self._logger.debug(__name__, 'Running upgrade')
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `Clients` (
`ClientId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordClientId` BIGINT NOT NULL,
`SentMessageCount` BIGINT NOT NULL DEFAULT 0,
`ReceivedMessageCount` BIGINT NOT NULL DEFAULT 0,
`DeletedMessageCount` BIGINT NOT NULL DEFAULT 0,
`ReceivedCommandsCount` BIGINT NOT NULL DEFAULT 0,
`MovedUsersCount` BIGINT NOT NULL DEFAULT 0,
`ServerId` BIGINT,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`ServerId`) REFERENCES Servers(`ServerId`),
PRIMARY KEY(`ClientId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `KnownUsers` (
`KnownUserId` BIGINT NOT NULL AUTO_INCREMENT,
`DiscordId` BIGINT NOT NULL,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`KnownUserId`)
);
""")
)
self._cursor.execute(
str(f"""
CREATE TABLE IF NOT EXISTS `UserJoinedServers` (
`JoinId` BIGINT NOT NULL AUTO_INCREMENT,
`UserId` BIGINT NOT NULL,
`JoinedOn` DATETIME(6) NOT NULL,
`LeavedOn` DATETIME(6),
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
FOREIGN KEY (`UserId`) REFERENCES Users(`UserId`),
PRIMARY KEY(`JoinId`)
);
""")
)
def downgrade(self):
self._cursor.execute('DROP TABLE `Clients`;')
self._cursor.execute('DROP TABLE `KnownUsers`;')
self._cursor.execute('DROP TABLE `UserJoinedServers`;')

View File

@ -0,0 +1,47 @@
from cpl_core.database import TableABC
class MigrationHistory(TableABC):
def __init__(self, id: str):
self._id = id
TableABC.__init__(self)
@property
def migration_id(self) -> str:
return self._id
@staticmethod
def get_select_by_id_string(id: str) -> str:
return str(f"""
SELECT * FROM `MigrationHistory`
WHERE `MigrationId` = '{id}';
""")
@property
def insert_string(self) -> str:
return str(f"""
INSERT INTO `MigrationHistory` (
`MigrationId`, `CreatedAt`, `LastModifiedAt`
) VALUES (
'{self._id}',
'{self._created_at}',
'{self._modified_at}'
);
""")
@property
def udpate_string(self) -> str:
return str(f"""
UPDATE `MigrationHistory`
SET LastModifiedAt` = '{self._modified_at}'
WHERE `MigrationId` = '{self._id}';
""")
@property
def delete_string(self) -> str:
return str(f"""
DELETE FROM `MigrationHistory`
WHERE `MigrationId` = '{self._id}';
""")

View File

@ -0,0 +1,43 @@
from cpl_core.database.context import DatabaseContextABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
from gismo_data.abc.migration_abc import MigrationABC
from gismo_data.model.migration_history import MigrationHistory
class MigrationService:
def __init__(self, logger: LoggerABC, services: ServiceProviderABC, db: DatabaseContextABC):
self._logger = logger
self._services = services
self._db = db
self._cursor = db.cursor
self._migrations: list[MigrationABC] = MigrationABC.__subclasses__()
def migrate(self):
self._logger.info(__name__, f"Running Migrations")
for migration in self._migrations:
migration_id = migration.__name__
try:
# check if table exists
self._cursor.execute("SHOW TABLES LIKE 'MigrationHistory'")
result = self._cursor.fetchone()
if result:
# there is a table named "tableName"
self._logger.trace(__name__, f"Running SQL Command: {MigrationHistory.get_select_by_id_string(migration_id)}")
migration_from_db: MigrationHistory = self._db.select(MigrationHistory.get_select_by_id_string(migration_id))
self._logger.trace(__name__, migration_from_db)
if migration_from_db is not None and len(migration_from_db) > 0:
continue
self._logger.debug(__name__, f"Running Migration {migration}")
migration_as_service: MigrationABC = self._services.get_service(migration)
migration_as_service.upgrade()
self._cursor.execute(MigrationHistory(migration_id).insert_string)
self._db.save_changes()
except Exception as e:
self._logger.error(__name__, f'Cannot get migration with id {migration}', e)