From 3054ac9d902f158faad9ed4ec2f1c29e35f721c7 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 28 Sep 2014 00:14:10 -0400 Subject: [PATCH] Introduce new migration procedure. This creates a new migration module that include upgrade logic files, one file for each DB version. It should be noted that this will not support downgrades (the previous method didn't really support them either) --- config.py | 4 ++ eos/db/__init__.py | 1 - eos/db/migration.py | 87 ++++++--------------------------- eos/db/migrations/__init__.py | 9 ++++ eos/db/migrations/upgrade1.py | 92 +++++++++++++++++++++++++++++++++++ pyfa.py | 11 ++++- 6 files changed, 131 insertions(+), 73 deletions(-) create mode 100644 eos/db/migrations/__init__.py create mode 100644 eos/db/migrations/upgrade1.py diff --git a/config.py b/config.py index 8abc4d327..f20142a6c 100644 --- a/config.py +++ b/config.py @@ -19,6 +19,10 @@ expansionName = "Hyperion" expansionVersion = "1.0" evemonMinVersion = "4081" +# Database version (int ONLY) +# Increment every time we need to flag for user database upgrade/modification +dbversion = 1 + pyfaPath = None savePath = None staticPath = None diff --git a/eos/db/__init__.py b/eos/db/__init__.py index a02979112..a66d0fc40 100644 --- a/eos/db/__init__.py +++ b/eos/db/__init__.py @@ -49,7 +49,6 @@ if saveddata_connectionstring is not None: saveddata_meta = MetaData() saveddata_meta.bind = saveddata_engine - migration.update(saveddata_engine) saveddata_session = sessionmaker(bind=saveddata_engine, autoflush=False, expire_on_commit=False)() # Lock controlling any changes introduced to session diff --git a/eos/db/migration.py b/eos/db/migration.py index 81399f4a5..2d91d805a 100644 --- a/eos/db/migration.py +++ b/eos/db/migration.py @@ -1,76 +1,21 @@ -import sqlalchemy +import config + +def getVersion(db): + cursor = db.execute('PRAGMA user_version') + return cursor.fetchone()[0] def update(saveddata_engine): - checkPriceFailures(saveddata_engine) - checkApiDefaultChar(saveddata_engine) - checkFitBooster(saveddata_engine) - checktargetResists(saveddata_engine) + currversion = getVersion(saveddata_engine) -def checkPriceFailures(saveddata_engine): - # Check if we have 'failed' column - try: - saveddata_engine.execute("SELECT failed FROM prices") - except sqlalchemy.exc.DatabaseError: - # As we don't have any important data there, let's just drop - # and recreate whole table - from eos.db.saveddata.price import prices_table - # Attempt to drop/create table only if it's already there - try: - prices_table.drop(saveddata_engine) - prices_table.create(saveddata_engine) - except sqlalchemy.exc.DatabaseError: - pass + if currversion == config.dbversion: + return + if currversion < config.dbversion: + for version in xrange(currversion, config.dbversion): + module = __import__('eos.db.migrations.upgrade%d'%(version+1), fromlist=True) + upgrade = getattr(module, "upgrade", False) + if upgrade: + upgrade(saveddata_engine) -def checkApiDefaultChar(saveddata_engine): - try: - saveddata_engine.execute("SELECT * FROM characters LIMIT 1") - # If table doesn't exist, it means we're doing everything from scratch - # and sqlalchemy will process everything as needed - except sqlalchemy.exc.DatabaseError: - pass - # If not, we're running on top of existing DB - else: - # Check that we have columns - try: - saveddata_engine.execute("SELECT defaultChar, chars FROM characters LIMIT 1") - # If we don't, create them - # This is ugly as hell, but we can't use proper migrate packages as it - # will require us to rebuild skeletons, including mac - except sqlalchemy.exc.DatabaseError: - saveddata_engine.execute("ALTER TABLE characters ADD COLUMN defaultChar INTEGER;") - saveddata_engine.execute("ALTER TABLE characters ADD COLUMN chars VARCHAR;") - -def checkFitBooster(saveddata_engine): - try: - saveddata_engine.execute("SELECT * FROM fits LIMIT 1") - # If table doesn't exist, it means we're doing everything from scratch - # and sqlalchemy will process everything as needed - except sqlalchemy.exc.DatabaseError: - pass - # If not, we're running on top of existing DB - else: - # Check that we have columns - try: - saveddata_engine.execute("SELECT booster FROM fits LIMIT 1") - # If we don't, create them - except sqlalchemy.exc.DatabaseError: - saveddata_engine.execute("ALTER TABLE fits ADD COLUMN booster BOOLEAN;") - # Set NULL data to 0 (needed in case of downgrade, see GH issue #62 - saveddata_engine.execute("UPDATE fits SET booster = 0 WHERE booster IS NULL;") - -def checktargetResists(saveddata_engine): - try: - saveddata_engine.execute("SELECT * FROM fits LIMIT 1") - # If table doesn't exist, it means we're doing everything from scratch - # and sqlalchemy will process everything as needed - except sqlalchemy.exc.DatabaseError: - pass - # If not, we're running on top of existing DB - else: - # Check that we have columns - try: - saveddata_engine.execute("SELECT targetResistsID FROM fits LIMIT 1") - # If we don't, create them - except sqlalchemy.exc.DatabaseError: - saveddata_engine.execute("ALTER TABLE fits ADD COLUMN targetResistsID INTEGER;") + # when all is said and done, set version to current + saveddata_engine.execute('PRAGMA user_version = %d'%config.dbversion) diff --git a/eos/db/migrations/__init__.py b/eos/db/migrations/__init__.py new file mode 100644 index 000000000..16c939868 --- /dev/null +++ b/eos/db/migrations/__init__.py @@ -0,0 +1,9 @@ +""" +The migration module includes migration logic to update database scheme and/or +data for the user database. + +To create a migration, simply create a file upgrade.py and +define an upgrade() function with the logic. Please note that there must be as +many upgrade files as there are database versions (version 5 would include +upgrade files 1-5) +""" diff --git a/eos/db/migrations/upgrade1.py b/eos/db/migrations/upgrade1.py new file mode 100644 index 000000000..f30e39331 --- /dev/null +++ b/eos/db/migrations/upgrade1.py @@ -0,0 +1,92 @@ +""" +Migration 1 + +- Alters fits table to introduce target resist attribute +- Converts modules based on Oceanus Module Tiericide + Some modules have been deleted, which causes pyfa to crash when fits are + loaded as they no longer exist in the database. We therefore replace these + modules with their new replacements + + Based on http://community.eveonline.com/news/patch-notes/patch-notes-for-oceanus/ + and output of itemDiff.py +""" + +CONVERSIONS = { + 6135: [ # Scoped Cargo Scanner + 6133, # Interior Type-E Cargo Identifier + ], + 6527: [ # Compact Ship Scanner + 6525, # Ta3 Perfunctory Vessel Probe + 6529, # Speculative Ship Identifier I + 6531, # Practical Type-E Ship Probe + ], + 6569: [ # Scoped Survey Scanner + 6567, # ML-3 Amphilotite Mining Probe + 6571, # Rock-Scanning Sensor Array I + 6573, # 'Dactyl' Type-E Asteroid Analyzer + ], + 509: [ # 'Basic' Capacitor Flux Coil + 8163, # Partial Power Plant Manager: Capacitor Flux + 8165, # Alpha Reactor Control: Capacitor Flux + 8167, # Type-E Power Core Modification: Capacitor Flux + 8169, # Marked Generator Refitting: Capacitor Flux + ], + 8135: [ # Restrained Capacitor Flux Coil + 8131, # Local Power Plant Manager: Capacitor Flux I + ], + 8133: [ # Compact Capacitor Flux Coil + 8137, # Mark I Generator Refitting: Capacitor Flux + ], + 3469: [ # Basic Co-Processor + 8744, # Nanoelectrical Co-Processor + 8743, # Nanomechanical CPU Enhancer + 8746, # Quantum Co-Processor + 8745, # Photonic CPU Enhancer + 15425, # Naiyon's Modified Co-Processor (never existed but convert + # anyway as some fits may include it) + ], + 8748: [ # Upgraded Co-Processor + 8747, # Nanomechanical CPU Enhancer I + 8750, # Quantum Co-Processor I + 8749, # Photonic CPU Enhancer I + ], + 1351: [ # Basic Reactor Control Unit + 8251, # Partial Power Plant Manager: Reaction Control + 8253, # Alpha Reactor Control: Reaction Control + 8257, # Marked Generator Refitting: Reaction Control + ], + 8263: [ # Compact Reactor Control Unit + 8259, # Local Power Plant Manager: Reaction Control I + 8265, # Mark I Generator Refitting: Reaction Control + 8261, # Beta Reactor Control: Reaction Control I + ], + 16537: [ # Compact Micro Auxiliary Power Core + 16539, # Micro B88 Core Augmentation + 16541, # Micro K-Exhaust Core Augmentation + ], + 31936: [ # Navy Micro Auxiliary Power Core + 16543, # Micro 'Vigor' Core Augmentation + ], + 8089: [ # Compact Light Missile Launcher + 8093, # Prototype 'Arbalest' Light Missile Launcher + ], + 8091: [ # Ample Light Missile Launcher + 7993, # Experimental TE-2100 Light Missile Launcher + ], + # Surface Cargo Scanner I was removed from game, however no mention of + # replacement module in patch notes. Morphing it to meta 0 module to be safe + 442: [ # Cargo Scanner I + 6129, # Surface Cargo Scanner I + ] +} + +def upgrade(saveddata_engine): + # Update fits schema + saveddata_engine.execute("ALTER TABLE fits ADD COLUMN targetResistsID INTEGER;") + + # Convert modules + for replacement_item, list in CONVERSIONS.iteritems(): + for retired_item in list: + saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) + saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) + diff --git a/pyfa.py b/pyfa.py index 76b7ca336..b80ae5d70 100755 --- a/pyfa.py +++ b/pyfa.py @@ -84,6 +84,7 @@ if __name__ == "__main__": import os.path import eos.db + import eos.db.migration as migration import service.prefetch from gui.mainFrame import MainFrame @@ -91,7 +92,15 @@ if __name__ == "__main__": if not os.path.exists(config.savePath): os.mkdir(config.savePath) - eos.db.saveddata_meta.create_all() + if os.path.isfile(config.saveDB): + # If database exists, run migration after init'd database + eos.db.saveddata_meta.create_all() + migration.update(eos.db.saveddata_engine) + else: + # If database does not exist, do not worry about migration. Simply + # create and set version + eos.db.saveddata_meta.create_all() + eos.db.saveddata_engine.execute('PRAGMA user_version = %d'%config.dbversion) pyfa = wx.App(False) MainFrame()