Hello,

With the upcoming 0.12b1 release, we will probably have a lot more people upgrading their 0.11 database to 0.12, i.e. going from database_version 21 to 26. This will likely trigger errors we're not yet aware of. Actually I'm writing this as a follow-up to http://trac.edgewall.org/ticket/9268, which reported such upgrade problem. I'm wondering if we shouldn't make this upgrade a bit more robust than it currently is, and more convenient to debug. In particular, we could consider each upgrade step (i.e. each call to an IEnvironmentSetupParticipant's upgrade_environment method) as being self-contained. They already are in practice, as schema modifications can't be rollbacked for the SQLite and MySQL database backends

This reminded me of a discussion we had in Bitten (http://bitten.edgewall.org/ticket/462) and indeed hodgestar later added an explicit db.commit(), despite the Trac documentation advising to not do it.

Well, I think we should now simply invalidate this advice and say that Trac not only takes care of doing a commit after each step, but that in addition it should also be possible to perform finer grained commits inside an upgrade_environment method if appropriate. It's certainly appropriate for the trac.env.EnvironmentSetup.upgrade_environment method (i.e. the place where we go through the trac/upgrades/db<version>.py steps).

I tested the attached patch with sqlite, postgresql and mysql, for an upgrade of a repository-less 0.11-stable environment upgraded to trunk. Tell me what you think about it, but I think it's a worthy change to do before the beta1.

This was also the occasion to output something in the log about which step is currently being executed.

An interesting exercise for 0.13 would be to add an unit-test which would create an environment with the database_version==1 schema, and make it go through all the updates ;-)

-- Christian

--
You received this message because you are subscribed to the Google Groups "Trac 
Development" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/trac-dev?hl=en.

# HG changeset patch
# Parent 826e2cb931b56a6795ca37f6505d06171a5e7128
Make environment upgrades more robust by committing after each upgrade step.

This was anyway the de facto way to make upgrades more robust (see e.g. [Bitten 
786/trunk/bitten/main.py]).

diff --git a/trac/env.py b/trac/env.py
--- a/trac/env.py
+++ b/trac/env.py
@@ -65,9 +65,13 @@ class IEnvironmentSetupParticipant(Inter
     def upgrade_environment(db):
         """Actually perform an environment upgrade.
         
-        Implementations of this method should not commit any database
-        transactions. This is done implicitly after all participants have
-        performed the upgrades they need without an error being raised.
+        Implementations of this method don't need to commit any database
+        transactions. This is done implicitly for each participant
+        if the upgrade succeeds without an error being raised.
+
+        However, if the `upgrade_environment` consists of small, restartable,
+        steps of upgrade, it can decide to commit on its own after each
+        successful step.
         """
 
 
@@ -509,35 +513,27 @@ class Environment(Component, ComponentMa
     def upgrade(self, backup=False, backup_dest=None):
         """Upgrade database.
         
-        Each db version should have its own upgrade module, named
-        upgrades/dbN.py, where 'N' is the version number (int).
-
         @param backup: whether or not to backup before upgrading
         @param backup_dest: name of the backup file
         @return: whether the upgrade was performed
         """
         upgraders = []
-        
-        @with_transaction(self)
-        def do_upgrade(db):
-            for participant in self.setup_participants:
-                if participant.environment_needs_upgrade(db):
-                    upgraders.append(participant)
-            if not upgraders:
-                return
-    
-            if backup:
-                self.backup(backup_dest)
+        db = self.get_read_db()
+        for participant in self.setup_participants:
+            if participant.environment_needs_upgrade(db):
+                upgraders.append(participant)
+        if not upgraders:
+            return
 
-            for participant in upgraders:
-                participant.upgrade_environment(db)
+        if backup:
+            self.backup(backup_dest)
 
-        if not upgraders:
-            return False
-
-        # Database schema may have changed, so close all connections
-        self.shutdown(except_logging=True)
-
+        for participant in upgraders:
+            self.log.info("%s.%s upgrading...", participant.__module__,
+                          participant.__class__.__name__)
+            with_transaction(self)(participant.upgrade_environment)
+            # Database schema may have changed, so close all connections
+            self.shutdown(except_logging=True)
         return True
 
     def _get_href(self):
@@ -590,6 +586,9 @@ class EnvironmentSetup(Component):
         return True
 
     def upgrade_environment(self, db):
+        """Each db version should have its own upgrade module, named
+        upgrades/dbN.py, where 'N' is the version number (int).
+        """
         cursor = db.cursor()
         dbver = self.env.get_version()
         for i in range(dbver + 1, db_default.db_version + 1):
@@ -601,10 +600,11 @@ class EnvironmentSetup(Component):
                 raise TracError(_('No upgrade module for version %(num)i '
                                   '(%(version)s.py)', num=i, version=name))
             script.do_upgrade(self.env, i, cursor)
-        cursor.execute("UPDATE system SET value=%s WHERE "
-                       "name='database_version'", (db_default.db_version,))
-        self.log.info('Upgraded database version from %d to %d',
-                      dbver, db_default.db_version)
+            cursor.execute("""
+                UPDATE system SET value=%s WHERE name='database_version'
+                """, (i,))
+            self.log.info('Upgraded database version from %d to %d', i - 1, i)
+            db.commit()
         self._update_sample_config()
 
     # Internal methods

Reply via email to