Commits

Anonymous committed 6a74248

Refactor transaction handling, using a with_transaction function decorator (paves the way for using `with transaction(env, db) as db` in future versions of Trac).

Many thanks to Jan Schukat for this exhaustive patch.

Main part of #8751.

(contains the fix for the usage of `with_transaction` in `RepositoryCache.sync_changeset`)

Comments (0)

Files changed (17)

  * Olliver Rutherfurd             ollie
  * Andres Salomon                 dilinger@athenacr.com
  * Michael Scherer                misc@mandrake.org
+ * Andreas Schrattenecker         vittorio
+ * Jan Schukat                    shookie@email.de
  * Felix Schwarz                  felix.schwarz@oss.schwarz.eu
- * Andreas Schrattenecker         vittorio
  * Emmeran Seehuber               rototor@rototor.de
  * Odd Simon Simonsen             simon-code@bvnetwork.no
  * Noah Slater                    nslater@gmail.com

trac/attachment.py

                        console_datetime_format, get_dir_list
 from trac.config import BoolOption, IntOption
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.env import IEnvironmentSetupParticipant
 from trac.mimeview import *
 from trac.perm import PermissionError, IPermissionPolicy
 
     def delete(self, db=None):
         assert self.filename, 'Cannot delete non-existent attachment'
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        cursor.execute("DELETE FROM attachment WHERE type=%s AND id=%s "
-                       "AND filename=%s", (self.parent_realm, self.parent_id,
-                       self.filename))
-        if os.path.isfile(self.path):
-            try:
-                os.unlink(self.path)
-            except OSError, e:
-                self.env.log.error('Failed to delete attachment file %s: %s',
-                           self.path, exception_to_unicode(e, traceback=True))
-                if handle_ta:
-                    db.rollback()
-                raise TracError(_('Could not delete attachment'))
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            cursor = db.cursor()
+            cursor.execute("DELETE FROM attachment WHERE type=%s AND id=%s "
+                           "AND filename=%s",
+                           (self.parent_realm, self.parent_id, self.filename))
+            if os.path.isfile(self.path):
+                try:
+                    os.unlink(self.path)
+                except OSError, e:
+                    self.env.log.error('Failed to delete attachment '
+                                       'file %s: %s',
+                                       self.path,
+                                       exception_to_unicode(e, traceback=True))
+                    raise TracError(_('Could not delete attachment'))
 
         self.env.log.info('Attachment removed: %s' % self.title)
-        if handle_ta:
-            db.commit()
 
         for listener in AttachmentModule(self.env).change_listeners:
             listener.attachment_deleted(self)
 
     def insert(self, filename, fileobj, size, t=None, db=None):
         # FIXME: `t` should probably be switched to `datetime` too
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
         self.size = size and int(size) or 0
         timestamp = int(t or time.time())
             basename = os.path.basename(path).encode('ascii')
             filename = unicode_unquote(basename)
 
-            cursor = db.cursor()
-            cursor.execute("INSERT INTO attachment "
-                           "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
-                           (self.parent_realm, self.parent_id, filename,
-                            self.size, timestamp, self.description,
-                            self.author, self.ipnr))
-            shutil.copyfileobj(fileobj, targetfile)
-            self.resource.id = self.filename = filename
+            @with_transaction(self.env, db)
+            def do_insert(db):
+                cursor = db.cursor()
+                cursor.execute("INSERT INTO attachment "
+                               "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
+                               (self.parent_realm, self.parent_id, filename,
+                                self.size, timestamp, self.description,
+                                self.author, self.ipnr))
+                shutil.copyfileobj(fileobj, targetfile)
+                self.resource.id = self.filename = filename
 
-            self.env.log.info('New attachment: %s by %s', self.title,
-                              self.author)
-
-            if handle_ta:
-                db.commit()
-
+                self.env.log.info('New attachment: %s by %s', self.title,
+                                  self.author)
+        finally:
             targetfile.close()
 
-            for listener in AttachmentModule(self.env).change_listeners:
-                listener.attachment_added(self)
+        for listener in AttachmentModule(self.env).change_listeners:
+            listener.attachment_added(self)
 
-        finally:
-            if not targetfile.closed:
-                targetfile.close()
 
     @classmethod
     def select(cls, env, parent_realm, parent_id, db=None):
     import dummy_threading as threading
 
 from trac.core import Component
+from trac.db.util import with_transaction
 from trac.util.compat import partial
 
 __all__ = ["CacheManager", "cached", "cached_value"]
         self._lock.acquire()
         try:
             # Invalidate in other processes
-            handle_ta = db is None
-            if handle_ta:
-                db = self.env.get_db_cnx()
-            cursor = db.cursor()
-            
+
             # The row corresponding to the cache may not exist in the table
             # yet.
             #  - If the row exists, the UPDATE increments the generation, the
             #  - If the row doesn't exist, the UPDATE does nothing, but starts
             #    a transaction. The SELECT then returns nothing, and we can
             #    safely INSERT a new row.
-            cursor.execute("UPDATE cache SET generation=generation+1 "
-                           "WHERE id=%s", (id,))
-            cursor.execute("SELECT generation FROM cache WHERE id=%s", (id,))
-            if not cursor.fetchone():
-                cursor.execute("INSERT INTO cache VALUES (%s, %s)", (id, 0))
-            if handle_ta:
-                db.commit()
+            @with_transaction(self.env, db)
+            def do_invalidate(db):
+                cursor = db.cursor()
+                cursor.execute("UPDATE cache SET generation=generation+1 "
+                               "WHERE id=%s", (id,))
+                cursor.execute("SELECT generation FROM cache WHERE id=%s",
+                               (id,))
+                if not cursor.fetchone():
+                    cursor.execute("INSERT INTO cache VALUES (%s, %s)", (id, 0))
             
             # Invalidate in this process
             self._cache.pop(id, None)
 #
 # Author: Christopher Lenz <cmlenz@gmx.de>
 
+def with_transaction(env, db=None):
+    """Transaction decorator for simple use-once transactions.
+    Will be replaced by a context manager once python 2.4 support is dropped.
+
+    >>> def api_method(p1, p2):
+    >>>     result[0] = value1
+    >>>     @with_transaction(env, db)
+    >>>     def implementation_method(db):
+    >>>         # implementation
+    >>>         result[0] = value2
+    """
+    def transaction_wrapper(fn):
+        if db:
+            fn(db)
+        else:
+            dbtmp = env.get_db_cnx()
+            try:
+                fn(dbtmp)
+                dbtmp.commit()
+            except:
+                dbtmp.rollback()
+                raise
+    return transaction_wrapper
+
+
 def sql_escape_percent(sql):
     import re
     return re.sub("'((?:[^']|(?:''))*)'",
 from trac.core import Component, ComponentManager, implements, Interface, \
                       ExtensionPoint, TracError
 from trac.db import DatabaseManager
+from trac.db.util import with_transaction
 from trac.util import copytree, create_file, get_pkginfo, makedirs
 from trac.util.compat import any
 from trac.util.text import exception_to_unicode, printerr, printout
 
         if backup:
             self.backup(backup_dest)
-        for participant in upgraders:
-            participant.upgrade_environment(db)
-        db.commit()
+
+        @with_transaction(self.env)
+        def do_upgrade(db):
+            for participant in upgraders:
+                participant.upgrade_environment(db)
 
         # Database schema may have changed, so close all connections
         self.shutdown(except_logging=True)
 
     def environment_created(self):
         """Insert default data into the database."""
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        for table, cols, vals in db_default.get_data(db):
-            cursor.executemany("INSERT INTO %s (%s) VALUES (%s)" % (table,
-                               ','.join(cols), ','.join(['%s' for c in cols])),
-                               vals)
-        db.commit()
+        @with_transaction(self.env)
+        def do_db_populate(db):
+            cursor = db.cursor()
+            for table, cols, vals in db_default.get_data(db):
+                cursor.executemany("INSERT INTO %s (%s) VALUES (%s)"
+                                   % (table, ','.join(cols),
+                                      ','.join(['%s' for c in cols])),
+                                   vals)
         self._update_sample_config()
 
     def environment_needs_upgrade(self, db):
 from trac.admin import AdminCommandError, IAdminCommandProvider
 from trac.config import ExtensionOption, OrderedExtensionsOption
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.resource import Resource, get_resource_name
 from trac.util.text import print_table, printout, wrap
 from trac.util.translation import _
 
     def grant_permission(self, username, action):
         """Grants a user the permission to perform the specified action."""
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        cursor.execute("INSERT INTO permission VALUES (%s, %s)",
-                       (username, action))
-        self.log.info('Granted permission for %s to %s' % (action, username))
-        db.commit()
+        @with_transaction(self.env)
+        def do_grant(db):
+            cursor = db.cursor()
+            cursor.execute("INSERT INTO permission VALUES (%s, %s)",
+                           (username, action))
+            self.log.info('Granted permission for %s to %s'
+                          % (action, username))
 
     def revoke_permission(self, username, action):
         """Revokes a users' permission to perform the specified action."""
         db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        cursor.execute("DELETE FROM permission WHERE username=%s AND action=%s",
-                       (username, action))
-        self.log.info('Revoked permission for %s to %s' % (action, username))
-        db.commit()
+        @with_transaction(self.env)
+        def do_revoke(db):
+            cursor = db.cursor()
+            cursor.execute("DELETE FROM permission WHERE username=%s "
+                           "AND action=%s",
+                           (username, action))
+            self.log.info('Revoked permission for %s to %s'
+                          % (action, username))
 
 
 class DefaultPermissionGroupProvider(Component):

trac/ticket/admin.py

 
 from trac.admin import *
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.perm import PermissionSystem
 from trac.resource import ResourceNotFound
 from trac.ticket import model
                         raise TracError(_('No component selected'))
                     if not isinstance(sel, list):
                         sel = [sel]
-                    db = self.env.get_db_cnx()
-                    for name in sel:
-                        comp = model.Component(self.env, name, db=db)
-                        comp.delete(db=db)
-                    db.commit()
+                    @with_transaction(self.env)
+                    def do_remove(db):
+                        for name in sel:
+                            comp = model.Component(self.env, name, db=db)
+                            comp.delete(db=db)
                     add_notice(req, _('The selected components have been '
                                       'removed.'))
                     req.redirect(req.href.admin(cat, page))
         component.insert()
     
     def _do_rename(self, name, newname):
-        db = self.env.get_db_cnx()
-        component = model.Component(self.env, name, db=db)
-        component.name = newname
-        component.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_rename(db):
+            component = model.Component(self.env, name, db=db)
+            component.name = newname
+            component.update(db=db)
     
     def _do_remove(self, name):
-        db = self.env.get_db_cnx()
-        component = model.Component(self.env, name, db=db)
-        component.delete(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_remove(db):
+            component = model.Component(self.env, name, db=db)
+            component.delete(db=db)
     
     def _do_chown(self, name, owner):
-        db = self.env.get_db_cnx()
-        component = model.Component(self.env, name, db=db)
-        component.owner = owner
-        component.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_chown(db):
+            component = model.Component(self.env, name, db=db)
+            component.owner = owner
+            component.update(db=db)
 
 
 class MilestoneAdminPanel(TicketAdminPanel):
                         raise TracError(_('No milestone selected'))
                     if not isinstance(sel, list):
                         sel = [sel]
-                    db = self.env.get_db_cnx()
-                    for name in sel:
-                        mil = model.Milestone(self.env, name, db=db)
-                        mil.delete(db=db, author=req.authname)
-                    db.commit()
+                    @with_transaction(self.env)
+                    def do_remove(db):
+                        for name in sel:
+                            mil = model.Milestone(self.env, name, db=db)
+                            mil.delete(db=db, author=req.authname)
                     add_notice(req, _('The selected milestones have been '
                                       'removed.'))
                     req.redirect(req.href.admin(cat, page))
         milestone.insert()
     
     def _do_rename(self, name, newname):
-        db = self.env.get_db_cnx()
-        milestone = model.Milestone(self.env, name, db=db)
-        milestone.name = newname
-        milestone.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_rename(db):
+            milestone = model.Milestone(self.env, name, db=db)
+            milestone.name = newname
+            milestone.update(db=db)
     
     def _do_due(self, name, due):
-        db = self.env.get_db_cnx()
-        milestone = model.Milestone(self.env, name, db=db)
-        milestone.due = due and parse_date(due)
-        milestone.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_due(db):
+            milestone = model.Milestone(self.env, name, db=db)
+            milestone.due = due and parse_date(due)
+            milestone.update(db=db)
     
     def _do_completed(self, name, completed):
-        db = self.env.get_db_cnx()
-        milestone = model.Milestone(self.env, name, db=db)
-        milestone.completed = completed and parse_date(completed)
-        milestone.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_completed(db):
+            milestone = model.Milestone(self.env, name, db=db)
+            milestone.completed = completed and parse_date(completed)
+            milestone.update(db=db)
     
     def _do_remove(self, name):
-        db = self.env.get_db_cnx()
-        milestone = model.Milestone(self.env, name, db=db)
-        milestone.delete(author=getuser(), db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_remove(db):
+            milestone = model.Milestone(self.env, name, db=db)
+            milestone.delete(author=getuser(), db=db)
 
 
 class VersionAdminPanel(TicketAdminPanel):
                         raise TracError(_('No version selected'))
                     if not isinstance(sel, list):
                         sel = [sel]
-                    db = self.env.get_db_cnx()
-                    for name in sel:
-                        ver = model.Version(self.env, name, db=db)
-                        ver.delete(db=db)
-                    db.commit()
+                    @with_transaction(self.env)
+                    def do_remove(db):
+                        for name in sel:
+                            ver = model.Version(self.env, name, db=db)
+                            ver.delete(db=db)
                     add_notice(req, _('The selected versions have been '
                                       'removed.'))
                     req.redirect(req.href.admin(cat, page))
         version.insert()
     
     def _do_rename(self, name, newname):
-        db = self.env.get_db_cnx()
-        version = model.Version(self.env, name, db=db)
-        version.name = newname
-        version.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_rename(db):
+            version = model.Version(self.env, name, db=db)
+            version.name = newname
+            version.update(db=db)
     
     def _do_time(self, name, time):
-        db = self.env.get_db_cnx()
-        version = model.Version(self.env, name, db=db)
-        version.time = time and parse_date(time)
-        version.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_time(db):
+            version = model.Version(self.env, name, db=db)
+            version.time = time and parse_date(time)
+            version.update(db=db)
     
     def _do_remove(self, name):
-        db = self.env.get_db_cnx()
-        version = model.Version(self.env, name, db=db)
-        version.delete(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_remove(db):
+            version = model.Version(self.env, name, db=db)
+            version.delete(db=db)
 
 
 class AbstractEnumAdminPanel(TicketAdminPanel):
                         raise TracError(_('No %s selected') % self._type)
                     if not isinstance(sel, list):
                         sel = [sel]
-                    db = self.env.get_db_cnx()
-                    for name in sel:
-                        enum = self._enum_cls(self.env, name, db=db)
-                        enum.delete(db=db)
-                    db.commit()
+                    @with_transaction(self.env)
+                    def do_remove(db):
+                        for name in sel:
+                            enum = self._enum_cls(self.env, name, db=db)
+                            enum.delete(db=db)
                     add_notice(req, _('The selected %(fields)s have been '
                                       'removed.',
                                       fields=self._label[1].lower()))
                     req.redirect(req.href.admin(cat, page))
 
-                # Appy changes
+                # Apply changes
                 elif req.args.get('apply'):
-                    changed = False
+                    changed = [False]
                     
                     # Set default value
                     name = req.args.get('default')
                                         name)
                         try:
                             self.config.save()
-                            changed = True
+                            changed[0] = True
                         except Exception, e:
                             self.log.error('Error writing to trac.ini: %s',
                                            exception_to_unicode(e))
                     values = dict([(val, True) for val in order.values()])
                     if len(order) != len(values):
                         raise TracError(_('Order numbers must be unique'))
-                    db = self.env.get_db_cnx()
-                    for enum in self._enum_cls.select(self.env, db=db):
-                        new_value = order[enum.value]
-                        if new_value != enum.value:
-                            enum.value = new_value
-                            enum.update(db=db)
-                            changed = True
-                    db.commit()
+                    @with_transaction(self.env)
+                    def do_change(db):
+                        for enum in self._enum_cls.select(self.env, db=db):
+                            new_value = order[enum.value]
+                            if new_value != enum.value:
+                                enum.value = new_value
+                                enum.update(db=db)
+                                changed[0] = True
 
-                    if changed:
+                    if changed[0]:
                         add_notice(req, _('Your changes have been saved.'))
                     req.redirect(req.href.admin(cat, page))
 
         enum.insert()
     
     def _do_change(self, name, newname):
-        db = self.env.get_db_cnx()
-        enum = self._enum_cls(self.env, name, db=db)
-        enum.name = newname
-        enum.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_change(db):
+            enum = self._enum_cls(self.env, name, db=db)
+            enum.name = newname
+            enum.update(db=db)
     
     def _do_remove(self, value):
-        db = self.env.get_db_cnx()
-        enum = self._enum_cls(self.env, value, db=db)
-        enum.delete(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_remove(db):
+            enum = self._enum_cls(self.env, value, db=db)
+            enum.delete(db=db)
     
     def _do_order(self, name, up_down):
         if up_down not in ('up', 'down'):
                 break
         else:
             return
-        enum1.update(db=db)
-        enum2.update(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_order(db):
+            enum1.update(db=db)
+            enum2.update(db=db)
+
 
 
 class PriorityAdminPanel(AbstractEnumAdminPanel):
             number = int(number)
         except ValueError:
             raise AdminCommandError(_('<number> must be a number'))
-        db = self.env.get_db_cnx()
-        ticket = model.Ticket(self.env, number, db=db)
-        ticket.delete(db=db)
-        db.commit()
+        @with_transaction(self.env)
+        def do_remove(db):
+            ticket = model.Ticket(self.env, number, db=db)
+            ticket.delete(db=db)
         printout(_('Ticket %(num)s and all associated data removed.',
                    num=number))

trac/ticket/model.py

 
 from trac.attachment import Attachment
 from trac.core import TracError
+from trac.db.util import with_transaction
 from trac.resource import Resource, ResourceNotFound
 from trac.ticket.api import TicketSystem
 from trac.util import embedded_numbers, partition
     def _get_db(self, db):
         return db or self.env.get_db_cnx()
 
-    def _get_db_for_write(self, db):
-        if db:
-            return (db, False)
-        else:
-            return (self.env.get_db_cnx(), True)
-
     exists = property(fget=lambda self: self.id is not None)
 
     def _init_defaults(self, db=None):
     def insert(self, when=None, db=None):
         """Add ticket to database"""
         assert not self.exists, 'Cannot insert an existing ticket'
-        db, handle_ta = self._get_db_for_write(db)
 
         # Add a timestamp
         if when is None:
             when = datetime.now(utc)
         self.values['time'] = self.values['changetime'] = when
 
-        cursor = db.cursor()
-
         # The owner field defaults to the component owner
         if self.values.get('component') and not self.values.get('owner'):
             try:
                     custom_fields.append(fname)
                 else:
                     std_fields.append(fname)
-        cursor.execute("INSERT INTO ticket (%s) VALUES (%s)"
-                       % (','.join(std_fields),
-                          ','.join(['%s'] * len(std_fields))),
-                       [values[name] for name in std_fields])
-        tkt_id = db.get_last_id(cursor, 'ticket')
 
-        # Insert custom fields
-        if custom_fields:
-            cursor.executemany("INSERT INTO ticket_custom (ticket,name,value) "
-                               "VALUES (%s,%s,%s)", [(tkt_id, name, self[name])
-                                                     for name in custom_fields])
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_insert(db):
+            cursor = db.cursor()
+            cursor.execute("INSERT INTO ticket (%s) VALUES (%s)"
+                           % (','.join(std_fields),
+                              ','.join(['%s'] * len(std_fields))),
+                           [values[name] for name in std_fields])
+            self.id = db.get_last_id(cursor, 'ticket')
 
-        self.id = tkt_id
-        self.resource = self.resource(id=tkt_id)
+            # Insert custom fields
+            if custom_fields:
+                cursor.executemany("INSERT INTO ticket_custom "
+                                   "(ticket,name,value) VALUES (%s,%s,%s)",
+                                   [(self.id, name, self[name])
+                                    for name in custom_fields])
+
+        self.resource = self.resource(id=self.id)
         self._old = {}
 
         for listener in TicketSystem(self.env).change_listeners:
         if not self._old and not comment:
             return False # Not modified
 
-        db, handle_ta = self._get_db_for_write(db)
-        cursor = db.cursor()
         if when is None:
             when = datetime.now(utc)
         when_ts = to_timestamp(when)
 
+        rdb = self._get_db(db)
+        cursor = rdb.cursor()
+        
         if 'component' in self.values:
-            # If the component is changed on a 'new' ticket then owner field
-            # is updated accordingly. (#623).
+            # If the component is changed on a 'new' ticket
+            # then owner field is updated accordingly. (#623).
             if self.values.get('status') == 'new' \
                     and 'component' in self._old \
                     and 'owner' not in self._old:
                 try:
-                    old_comp = Component(self.env, self._old['component'], db)
+                    old_comp = Component(self.env, self._old['component'], rdb)
                     old_owner = old_comp.owner or ''
                     current_owner = self.values.get('owner') or ''
                     if old_owner == current_owner:
-                        new_comp = Component(self.env, self['component'], db)
+                        new_comp = Component(self.env, self['component'], rdb)
                         if new_comp.owner:
                             self['owner'] = new_comp.owner
                 except TracError:
 
         # store fields
         custom_fields = [f['name'] for f in self.fields if f.get('custom')]
-        for name in self._old.keys():
-            if name in custom_fields:
-                cursor.execute("SELECT * FROM ticket_custom " 
-                               "WHERE ticket=%s and name=%s", (self.id, name))
-                if cursor.fetchone():
-                    cursor.execute("UPDATE ticket_custom SET value=%s "
-                                   "WHERE ticket=%s AND name=%s",
-                                   (self[name], self.id, name))
+
+        @with_transaction(self.env, db)
+        def do_save(db):
+            cursor = db.cursor()
+            for name in self._old.keys():
+                if name in custom_fields:
+                    cursor.execute("SELECT * FROM ticket_custom " 
+                                   "WHERE ticket=%s and name=%s", (self.id, name))
+                    if cursor.fetchone():
+                        cursor.execute("UPDATE ticket_custom SET value=%s "
+                                       "WHERE ticket=%s AND name=%s",
+                                       (self[name], self.id, name))
+                    else:
+                        cursor.execute("INSERT INTO ticket_custom (ticket,name,"
+                                       "value) VALUES(%s,%s,%s)",
+                                       (self.id, name, self[name]))
                 else:
-                    cursor.execute("INSERT INTO ticket_custom (ticket,name,"
-                                   "value) VALUES(%s,%s,%s)",
-                                   (self.id, name, self[name]))
-            else:
-                cursor.execute("UPDATE ticket SET %s=%%s WHERE id=%%s" % name,
-                               (self[name], self.id))
+                    cursor.execute("UPDATE ticket SET %s=%%s WHERE id=%%s" % name,
+                                   (self[name], self.id))
+                cursor.execute("INSERT INTO ticket_change "
+                               "(ticket,time,author,field,oldvalue,newvalue) "
+                               "VALUES (%s, %s, %s, %s, %s, %s)",
+                               (self.id, when_ts, author, name, self._old[name],
+                                self[name]))
+            
+            # always save comment, even if empty (numbering support for timeline)
             cursor.execute("INSERT INTO ticket_change "
                            "(ticket,time,author,field,oldvalue,newvalue) "
-                           "VALUES (%s, %s, %s, %s, %s, %s)",
-                           (self.id, when_ts, author, name, self._old[name],
-                            self[name]))
-        
-        # always save comment, even if empty (numbering support for timeline)
-        cursor.execute("INSERT INTO ticket_change "
-                       "(ticket,time,author,field,oldvalue,newvalue) "
-                       "VALUES (%s,%s,%s,'comment',%s,%s)",
-                       (self.id, when_ts, author, cnum, comment))
+                           "VALUES (%s,%s,%s,'comment',%s,%s)",
+                           (self.id, when_ts, author, cnum, comment))
+    
+            cursor.execute("UPDATE ticket SET changetime=%s WHERE id=%s",
+                           (when_ts, self.id))
 
-        cursor.execute("UPDATE ticket SET changetime=%s WHERE id=%s",
-                       (when_ts, self.id))
-
-        if handle_ta:
-            db.commit()
         old_values = self._old
         self._old = {}
         self.values['changetime'] = when
         return log
 
     def delete(self, db=None):
-        db, handle_ta = self._get_db_for_write(db)
-        Attachment.delete_all(self.env, 'ticket', self.id, db)
-        cursor = db.cursor()
-        cursor.execute("DELETE FROM ticket WHERE id=%s", (self.id,))
-        cursor.execute("DELETE FROM ticket_change WHERE ticket=%s", (self.id,))
-        cursor.execute("DELETE FROM ticket_custom WHERE ticket=%s", (self.id,))
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            Attachment.delete_all(self.env, 'ticket', self.id, db)
+            cursor = db.cursor()
+            cursor.execute("DELETE FROM ticket WHERE id=%s", (self.id,))
+            cursor.execute("DELETE FROM ticket_change WHERE ticket=%s",
+                           (self.id,))
+            cursor.execute("DELETE FROM ticket_custom WHERE ticket=%s",
+                           (self.id,))
 
         for listener in TicketSystem(self.env).change_listeners:
             listener.ticket_deleted(self)
         if when is None:
             when = datetime.now(utc)
         when_ts = to_timestamp(when)
+
+        @with_transaction(self.env, db)
+        def do_save(db):
+            cursor = db.cursor()
+            # Find the current value of the comment
+            cursor.execute("SELECT newvalue FROM ticket_change "
+                           "WHERE ticket=%s AND time=%s AND field='comment'",
+                           (self.id, ts))
+            old_comment = False
+            for old_comment, in cursor:
+                break
+            if comment == (old_comment or ''):
+                return
         
-        db, handle_ta = self._get_db_for_write(db)
-        cursor = db.cursor()
-        
-        # Find the current value of the comment
-        cursor.execute("SELECT newvalue FROM ticket_change "
-                       "WHERE ticket=%s AND time=%s AND field='comment'",
-                       (self.id, ts))
-        old_comment = False
-        for old_comment, in cursor:
-            break
-        if comment == (old_comment or ''):
-            return
-        
-        # Comment history is stored in fields named "_comment%d"
-        # Find the next edit number
-        cursor.execute("SELECT field FROM ticket_change "
-                       "WHERE ticket=%%s AND time=%%s AND field %s"
-                       % db.like(),
-                       (self.id, ts, db.like_escape('_comment') + '%'))
-        fields = list(cursor)
-        rev = fields and max(int(field[8:]) for field, in fields) + 1 or 0
-        cursor.execute("INSERT INTO ticket_change "
-                       "(ticket,time,author,field,oldvalue,newvalue) "
-                       "VALUES (%s,%s,%s,%s,%s,%s)",
-                       (self.id, ts, author, '_comment%d' % rev,
-                        old_comment or '', str(when_ts)))
-        if old_comment is False:
-            # There was no comment field, add one and find the original author
-            # in one of the other changed fields
-            cursor.execute("SELECT author FROM ticket_change "
-                           "WHERE ticket=%%s AND time=%%s AND NOT field %s "
-                           "LIMIT 1" % db.like(),
-                           (self.id, ts, db.like_escape('_') + '%'))
-            old_author = None
-            for old_author, in cursor:
-                break
+            # Comment history is stored in fields named "_comment%d"
+            # Find the next edit number
+            cursor.execute("SELECT field FROM ticket_change "
+                           "WHERE ticket=%%s AND time=%%s AND field %s"
+                           % db.like(),
+                           (self.id, ts, db.like_escape('_comment') + '%'))
+            fields = list(cursor)
+            rev = fields and max(int(field[8:]) for field, in fields) + 1 or 0
             cursor.execute("INSERT INTO ticket_change "
-                           "  (ticket,time,author,field,oldvalue,newvalue) "
-                           "VALUES (%s,%s,%s,'comment','',%s)",
-                           (self.id, ts, old_author, comment))
-        else:
-            cursor.execute("UPDATE ticket_change SET newvalue=%s "
-                           "WHERE ticket=%s AND time=%s AND field='comment'",
-                           (comment, self.id, ts))
-        if handle_ta:
-            db.commit()
+                           "(ticket,time,author,field,oldvalue,newvalue) "
+                           "VALUES (%s,%s,%s,%s,%s,%s)",
+                           (self.id, ts, author, '_comment%d' % rev,
+                            old_comment or '', str(when_ts)))
+            if old_comment is False:
+                # There was no comment field, add one, find the original author
+                # in one of the other changed fields
+                cursor.execute("SELECT author FROM ticket_change "
+                               "WHERE ticket=%%s AND time=%%s AND NOT field %s"
+                               " LIMIT 1" % db.like(),
+                               (self.id, ts, db.like_escape('_') + '%'))
+                old_author = None
+                for old_author, in cursor:
+                    break
+                cursor.execute("INSERT INTO ticket_change "
+                               " (ticket,time,author,field,oldvalue,newvalue) "
+                               "VALUES (%s,%s,%s,'comment','',%s)",
+                               (self.id, ts, old_author, comment))
+            else:
+                cursor.execute("UPDATE ticket_change SET newvalue=%s "
+                               "WHERE ticket=%s AND time=%s AND "
+                               "field='comment'",
+                               (comment, self.id, ts))
 
     def get_comment_history(self, cnum, db=None):
         db = self._get_db(db)
 
     def delete(self, db=None):
         assert self.exists, 'Cannot delete non-existent %s' % self.type
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Deleting %s %s' % (self.type, self.name))
-        cursor.execute("DELETE FROM enum WHERE type=%s AND value=%s",
-                       (self.type, self._old_value))
-        # Re-order any enums that have higher value than deleted (close gap)
-        for enum in list(self.select(self.env)):
-            try:
-                if int(enum.value) > int(self._old_value):
-                    enum.value = unicode(int(enum.value) - 1)
-                    enum.update(db=db)
-            except ValueError:
-                pass # Ignore cast error for this non-essential operation
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            cursor = db.cursor()
+            self.env.log.info('Deleting %s %s' % (self.type, self.name))
+            cursor.execute("DELETE FROM enum WHERE type=%s AND value=%s",
+                           (self.type, self._old_value))
+            # Re-order any enums that have higher value than deleted (close gap)
+            for enum in list(self.select(self.env, db)):
+                try:
+                    if int(enum.value) > int(self._old_value):
+                        enum.value = unicode(int(enum.value) - 1)
+                        enum.update(db=db)
+                except ValueError:
+                    pass # Ignore cast error for this non-essential operation
+            TicketSystem(self.env).reset_ticket_fields(db)
         self.value = self._old_value = None
         self.name = self._old_name = None
 
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid %(type)s name.', type=self.type))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.debug("Creating new %s '%s'" % (self.type, self.name))
-        if not self.value:
-            cursor.execute(("SELECT COALESCE(MAX(%s),0) FROM enum "
-                            "WHERE type=%%s") % db.cast('value', 'int'),
-                           (self.type,))
-            self.value = int(float(cursor.fetchone()[0])) + 1
-        cursor.execute("INSERT INTO enum (type,name,value) VALUES (%s,%s,%s)",
-                       (self.type, self.name, self.value))
-        TicketSystem(self.env).reset_ticket_fields(db)
+        @with_transaction(self.env, db)
+        def do_insert(db):
+            cursor = db.cursor()
+            self.env.log.debug("Creating new %s '%s'" % (self.type, self.name))
+            if not self.value:
+                cursor.execute(("SELECT COALESCE(MAX(%s),0) FROM enum "
+                                "WHERE type=%%s") % db.cast('value', 'int'),
+                               (self.type,))
+                self.value = int(float(cursor.fetchone()[0])) + 1
+            cursor.execute("INSERT INTO enum (type,name,value) "
+                           "VALUES (%s,%s,%s)",
+                           (self.type, self.name, self.value))
+            TicketSystem(self.env).reset_ticket_fields(db)
 
-        if handle_ta:
-            db.commit()
         self._old_name = self.name
         self._old_value = self.value
 
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid %(type)s name.', type=self.type))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Updating %s "%s"' % (self.type, self.name))
-        cursor.execute("UPDATE enum SET name=%s,value=%s "
-                       "WHERE type=%s AND name=%s",
-                       (self.name, self.value, self.type, self._old_name))
-        if self.name != self._old_name:
-            # Update tickets
-            cursor.execute("UPDATE ticket SET %s=%%s WHERE %s=%%s" %
-                           (self.ticket_col, self.ticket_col),
-                           (self.name, self._old_name))
-        TicketSystem(self.env).reset_ticket_fields(db)
+        @with_transaction(self.env, db)
+        def do_update(db):
+            cursor = db.cursor()
+            self.env.log.info('Updating %s "%s"' % (self.type, self.name))
+            cursor.execute("UPDATE enum SET name=%s,value=%s "
+                           "WHERE type=%s AND name=%s",
+                           (self.name, self.value, self.type, self._old_name))
+            if self.name != self._old_name:
+                # Update tickets
+                cursor.execute("UPDATE ticket SET %s=%%s WHERE %s=%%s" %
+                               (self.ticket_col, self.ticket_col),
+                               (self.name, self._old_name))
+            TicketSystem(self.env).reset_ticket_fields(db)
 
-        if handle_ta:
-            db.commit()
         self._old_name = self.name
         self._old_value = self.value
 
 
     def delete(self, db=None):
         assert self.exists, 'Cannot delete non-existent component'
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Deleting component %s' % self.name)
-        cursor.execute("DELETE FROM component WHERE name=%s", (self.name,))
-        self.name = self._old_name = None
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            cursor = db.cursor()
+            self.env.log.info('Deleting component %s' % self.name)
+            cursor.execute("DELETE FROM component WHERE name=%s", (self.name,))
+            self.name = self._old_name = None
+            TicketSystem(self.env).reset_ticket_fields(db)
 
     def insert(self, db=None):
         assert not self.exists, 'Cannot insert existing component'
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid component name.'))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.debug("Creating new component '%s'" % self.name)
-        cursor.execute("INSERT INTO component (name,owner,description) "
-                       "VALUES (%s,%s,%s)",
-                       (self.name, self.owner, self.description))
-        self._old_name = self.name
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_insert(db):
+            cursor = db.cursor()
+            self.env.log.debug("Creating new component '%s'" % self.name)
+            cursor.execute("INSERT INTO component (name,owner,description) "
+                           "VALUES (%s,%s,%s)",
+                           (self.name, self.owner, self.description))
+            self._old_name = self.name
+            TicketSystem(self.env).reset_ticket_fields(db)
 
     def update(self, db=None):
         assert self.exists, 'Cannot update non-existent component'
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid component name.'))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Updating component "%s"' % self.name)
-        cursor.execute("UPDATE component SET name=%s,owner=%s,description=%s "
-                       "WHERE name=%s",
-                       (self.name, self.owner, self.description,
-                        self._old_name))
-        if self.name != self._old_name:
-            # Update tickets
-            cursor.execute("UPDATE ticket SET component=%s WHERE component=%s",
-                           (self.name, self._old_name))
-            self._old_name = self.name
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_update(db):
+            cursor = db.cursor()
+            self.env.log.info('Updating component "%s"' % self.name)
+            cursor.execute("UPDATE component SET name=%s,owner=%s,"
+                           "description=%s WHERE name=%s",
+                           (self.name, self.owner, self.description,
+                            self._old_name))
+            if self.name != self._old_name:
+                # Update tickets
+                cursor.execute("UPDATE ticket SET component=%s "
+                               "WHERE component=%s",
+                               (self.name, self._old_name))
+                self._old_name = self.name
+            TicketSystem(self.env).reset_ticket_fields(db)
 
     @classmethod
     def select(cls, env, db=None):
                      'description': self.description}
 
     def delete(self, retarget_to=None, author=None, db=None):
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            cursor = db.cursor()
+            self.env.log.info('Deleting milestone %s' % self.name)
+            cursor.execute("DELETE FROM milestone WHERE name=%s", (self.name,))
 
-        cursor = db.cursor()
-        self.env.log.info('Deleting milestone %s' % self.name)
-        cursor.execute("DELETE FROM milestone WHERE name=%s", (self.name,))
-
-        # Retarget/reset tickets associated with this milestone
-        now = datetime.now(utc)
-        cursor.execute("SELECT id FROM ticket WHERE milestone=%s", (self.name,))
-        tkt_ids = [int(row[0]) for row in cursor]
-        for tkt_id in tkt_ids:
-            ticket = Ticket(self.env, tkt_id, db)
-            ticket['milestone'] = retarget_to
-            ticket.save_changes(author, 'Milestone %s deleted' % self.name,
-                                now, db=db)
-        self._old['name'] = None
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+            # Retarget/reset tickets associated with this milestone
+            now = datetime.now(utc)
+            cursor.execute("SELECT id FROM ticket WHERE milestone=%s",
+                           (self.name,))
+            tkt_ids = [int(row[0]) for row in cursor]
+            for tkt_id in tkt_ids:
+                ticket = Ticket(self.env, tkt_id, db)
+                ticket['milestone'] = retarget_to
+                ticket.save_changes(author, 'Milestone %s deleted' % self.name,
+                                    now, db=db)
+            self._old['name'] = None
+            TicketSystem(self.env).reset_ticket_fields(db)
 
         for listener in TicketSystem(self.env).milestone_change_listeners:
             listener.milestone_deleted(self)
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid milestone name.'))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.debug("Creating new milestone '%s'" % self.name)
-        cursor.execute("INSERT INTO milestone (name,due,completed,description) "
-                       "VALUES (%s,%s,%s,%s)",
-                       (self.name, to_timestamp(self.due), to_timestamp(self.completed),
-                        self.description))
-        self._to_old()
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_insert(db):
+            cursor = db.cursor()
+            self.env.log.debug("Creating new milestone '%s'" % self.name)
+            cursor.execute("INSERT INTO milestone "
+                           "(name,due,completed,description) "
+                           "VALUES (%s,%s,%s,%s)",
+                           (self.name, to_timestamp(self.due),
+                            to_timestamp(self.completed), self.description))
+            self._to_old()
+            TicketSystem(self.env).reset_ticket_fields(db)
 
         for listener in TicketSystem(self.env).milestone_change_listeners:
             listener.milestone_created(self)
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid milestone name.'))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Updating milestone "%s"' % self.name)
-        cursor.execute("UPDATE milestone SET name=%s,due=%s,"
-                       "completed=%s,description=%s WHERE name=%s",
-                       (self.name, to_timestamp(self.due), to_timestamp(self.completed),
-                        self.description, self._old['name']))
-        self.env.log.info('Updating milestone field of all tickets '
-                          'associated with milestone "%s"' % self.name)
-        cursor.execute("UPDATE ticket SET milestone=%s WHERE milestone=%s",
-                       (self.name, self._old['name']))
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_insert(db):
+            cursor = db.cursor()
+            self.env.log.info('Updating milestone "%s"' % self.name)
+            cursor.execute("UPDATE milestone SET name=%s,due=%s,"
+                           "completed=%s,description=%s WHERE name=%s",
+                           (self.name, to_timestamp(self.due),
+                            to_timestamp(self.completed),
+                            self.description, self._old['name']))
+            self.env.log.info('Updating milestone field of all tickets '
+                              'associated with milestone "%s"' % self.name)
+            cursor.execute("UPDATE ticket SET milestone=%s WHERE milestone=%s",
+                           (self.name, self._old['name']))
+            TicketSystem(self.env).reset_ticket_fields(db)
 
         old_values = dict((k, v) for k, v in self._old.iteritems()
                           if getattr(self, k) != v)
                 raise ResourceNotFound(_('Version %(name)s does not exist.',
                                          name=name))
             self.name = self._old_name = name
-            self.time = row[0] and datetime.fromtimestamp(int(row[0]), utc) or None
+            self.time = row[0] and datetime.fromtimestamp(int(row[0]), utc) \
+                            or None
             self.description = row[1] or ''
         else:
             self.name = self._old_name = None
 
     def delete(self, db=None):
         assert self.exists, 'Cannot delete non-existent version'
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Deleting version %s' % self.name)
-        cursor.execute("DELETE FROM version WHERE name=%s", (self.name,))
-        self.name = self._old_name = None
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_delete(db):
+            cursor = db.cursor()
+            self.env.log.info('Deleting version %s' % self.name)
+            cursor.execute("DELETE FROM version WHERE name=%s", (self.name,))
+            self.name = self._old_name = None
+            TicketSystem(self.env).reset_ticket_fields(db)
 
     def insert(self, db=None):
         assert not self.exists, 'Cannot insert existing version'
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid version name.'))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.debug("Creating new version '%s'" % self.name)
-        cursor.execute("INSERT INTO version (name,time,description) "
-                       "VALUES (%s,%s,%s)",
-                       (self.name, to_timestamp(self.time), self.description))
-        self._old_name = self.name
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_insert(db):
+            cursor = db.cursor()
+            self.env.log.debug("Creating new version '%s'" % self.name)
+            cursor.execute("INSERT INTO version (name,time,description) "
+                           "VALUES (%s,%s,%s)",
+                           (self.name, to_timestamp(self.time),
+                            self.description))
+            self._old_name = self.name
+            TicketSystem(self.env).reset_ticket_fields(db)
 
     def update(self, db=None):
         assert self.exists, 'Cannot update non-existent version'
         self.name = simplify_whitespace(self.name)
         if not self.name:
             raise TracError(_('Invalid version name.'))
-        if not db:
-            db = self.env.get_db_cnx()
-            handle_ta = True
-        else:
-            handle_ta = False
 
-        cursor = db.cursor()
-        self.env.log.info('Updating version "%s"' % self.name)
-        cursor.execute("UPDATE version SET name=%s,time=%s,description=%s "
-                       "WHERE name=%s",
-                       (self.name, to_timestamp(self.time), self.description,
-                        self._old_name))
-        if self.name != self._old_name:
-            # Update tickets
-            cursor.execute("UPDATE ticket SET version=%s WHERE version=%s",
-                           (self.name, self._old_name))
-            self._old_name = self.name
-        TicketSystem(self.env).reset_ticket_fields(db)
-
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_update(db):
+            cursor = db.cursor()
+            self.env.log.info('Updating version "%s"' % self.name)
+            cursor.execute("UPDATE version SET name=%s,time=%s,description=%s "
+                           "WHERE name=%s",
+                           (self.name, to_timestamp(self.time),
+                            self.description, self._old_name))
+            if self.name != self._old_name:
+                # Update tickets
+                cursor.execute("UPDATE ticket SET version=%s WHERE version=%s",
+                               (self.name, self._old_name))
+                self._old_name = self.name
+            TicketSystem(self.env).reset_ticket_fields(db)
 
     @classmethod
     def select(cls, env, db=None):
         for name, time, description in cursor:
             version = cls(env)
             version.name = version._old_name = name
-            version.time = time and datetime.fromtimestamp(int(time), utc) or None
+            version.time = time and datetime.fromtimestamp(int(time), utc) \
+                                or None
             version.description = description or ''
             versions.append(version)
         def version_order(v):

trac/ticket/report.py

 from trac.config import IntOption
 from trac.core import *
 from trac.db import get_column_names
+from trac.db.util import with_transaction
 from trac.mimeview import Context
 from trac.perm import IPermissionRequestor
 from trac.resource import Resource, ResourceNotFound
         data = {}
         if req.method == 'POST':
             if action == 'new':
-                self._do_create(req, db)
+                self._do_create(req)
             elif action == 'delete':
-                self._do_delete(req, db, id)
+                self._do_delete(req, id)
             elif action == 'edit':
-                self._do_save(req, db, id)
+                self._do_save(req, id)
         elif action in ('copy', 'edit', 'new'):
             template = 'report_edit.html'
             data = self._render_editor(req, db, id, action=='copy')
 
     # Internal methods
 
-    def _do_create(self, req, db):
+    def _do_create(self, req):
         req.perm.require('REPORT_CREATE')
 
         if 'cancel' in req.args:
         title = req.args.get('title', '')
         query = req.args.get('query', '')
         description = req.args.get('description', '')
-        cursor = db.cursor()
-        cursor.execute("INSERT INTO report (title,query,description) "
-                       "VALUES (%s,%s,%s)", (title, query, description))
-        id = db.get_last_id(cursor, 'report')
-        db.commit()
+        report_id = [ None ]
+        @with_transaction(self.env)
+        def do_create(db):
+            cursor = db.cursor()
+            cursor.execute("INSERT INTO report (title,query,description) "
+                           "VALUES (%s,%s,%s)", (title, query, description))
+            report_id[0] = db.get_last_id(cursor, 'report')
         add_notice(req, _('The report has been created.'))
-        req.redirect(req.href.report(id))
+        req.redirect(req.href.report(report_id[0]))
 
-    def _do_delete(self, req, db, id):
+    def _do_delete(self, req, id):
         req.perm.require('REPORT_DELETE')
 
         if 'cancel' in req.args:
             req.redirect(req.href.report(id))
 
-        cursor = db.cursor()
-        cursor.execute("DELETE FROM report WHERE id=%s", (id,))
-        db.commit()
+        @with_transaction(self.env)
+        def do_delete(db):
+            cursor = db.cursor()
+            cursor.execute("DELETE FROM report WHERE id=%s", (id,))
         add_notice(req, _('The report {%(id)d} has been deleted.', id=id))
         req.redirect(req.href.report())
 
-    def _do_save(self, req, db, id):
+    def _do_save(self, req, id):
         """Save report changes to the database"""
         req.perm.require('REPORT_MODIFY')
 
             title = req.args.get('title', '')
             query = req.args.get('query', '')
             description = req.args.get('description', '')
-            cursor = db.cursor()
-            cursor.execute("UPDATE report SET title=%s,query=%s,description=%s "
-                           "WHERE id=%s", (title, query, description, id))
-            db.commit()
+            @with_transaction(self.env)
+            def do_save(db):
+                cursor = db.cursor()
+                cursor.execute("UPDATE report "
+                               "SET title=%s,query=%s,description=%s "
+                               "WHERE id=%s", (title, query, description, id))
             add_notice(req, _('Your changes have been saved.'))
         req.redirect(req.href.report(id))
 

trac/ticket/roadmap.py

 from trac.attachment import AttachmentModule
 from trac.config import ExtensionOption
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.mimeview import Context
 from trac.perm import IPermissionRequestor
 from trac.resource import *
             elif action == 'edit':
                 return self._do_save(req, db, milestone)
             elif action == 'delete':
-                self._do_delete(req, db, milestone)
+                self._do_delete(req, milestone)
         elif action in ('new', 'edit'):
             return self._render_editor(req, db, milestone)
         elif action == 'delete':
 
     # Internal methods
 
-    def _do_delete(self, req, db, milestone):
+    def _do_delete(self, req, milestone):
         req.perm(milestone.resource).require('MILESTONE_DELETE')
 
         retarget_to = None
         if req.args.has_key('retarget'):
             retarget_to = req.args.get('target') or None
         milestone.delete(retarget_to, req.authname)
-        db.commit()
         add_notice(req, _('The milestone "%(name)s" has been deleted.',
                           name=milestone.name))
         req.redirect(req.href.roadmap())
             milestone.update()
             # eventually retarget opened tickets associated with the milestone
             if 'retarget' in req.args and completed:
-                cursor = db.cursor()
-                cursor.execute("UPDATE ticket SET milestone=%s WHERE "
-                               "milestone=%s and status != 'closed'",
-                                (retarget_to, old_name))
-                self.env.log.info('Tickets associated with milestone %s '
-                                  'retargeted to %s' % (old_name, retarget_to))
+                @with_transaction(self.env)
+                def retarget(db):
+                    cursor = db.cursor()
+                    cursor.execute("UPDATE ticket SET milestone=%s WHERE "
+                                   "milestone=%s and status != 'closed'",
+                                   (retarget_to, old_name))
+                    self.env.log.info('Tickets associated with milestone %s '
+                                      'retargeted to %s'
+                                      % (old_name, retarget_to))
         else:
             milestone.insert()
-        db.commit()
 
         add_notice(req, _('Your changes have been saved.'))
         req.redirect(req.href.milestone(milestone.name))

trac/versioncontrol/api.py

 from trac.admin import AdminCommandError, IAdminCommandProvider
 from trac.config import ListOption, Option
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.resource import IResourceManager, Resource, ResourceNotFound
 from trac.util.text import printout, to_unicode
 from trac.util.translation import _
         if type_ and type_ not in rm.get_supported_types():
             raise TracError(_("The repository type '%(type)s' is not "
                               "supported", type=type_))
-        db = self.env.get_db_cnx()
-        id = rm.get_repository_id(reponame, db)
-        cursor = db.cursor()
-        cursor.executemany("INSERT INTO repository (id, name, value) "
-                           "VALUES (%s, %s, %s)",
-                           [(id, 'dir', dir),
-                            (id, 'type', type_ or '')])
-        db.commit()
+        @with_transaction(self.env)
+        def do_add(db):
+            id = rm.get_repository_id(reponame, db)
+            cursor = db.cursor()
+            cursor.executemany("INSERT INTO repository (id, name, value) "
+                               "VALUES (%s, %s, %s)",
+                               [(id, 'dir', dir),
+                                (id, 'type', type_ or '')])
         rm.reload_repositories()
     
     def add_alias(self, reponame, target):
         if is_default(target):
             target = ''
         rm = RepositoryManager(self.env)
-        db = self.env.get_db_cnx()
-        id = rm.get_repository_id(reponame, db)
-        cursor = db.cursor()
-        cursor.executemany("INSERT INTO repository (id, name, value) "
-                           "VALUES (%s, %s, %s)",
-                           [(id, 'dir', None),
-                            (id, 'alias', target)])
-        db.commit()
+        @with_transaction(self.env)
+        def do_add(db):
+            id = rm.get_repository_id(reponame, db)
+            cursor = db.cursor()
+            cursor.executemany("INSERT INTO repository (id, name, value) "
+                               "VALUES (%s, %s, %s)",
+                               [(id, 'dir', None),
+                                (id, 'alias', target)])
         rm.reload_repositories()
     
     def remove_repository(self, reponame):
         if is_default(reponame):
             reponame = ''
         rm = RepositoryManager(self.env)
-        db = self.env.get_db_cnx()
-        id = rm.get_repository_id(reponame, db)
-        cursor = db.cursor()
-        cursor.execute("DELETE FROM repository WHERE id=%s", (id,))
-        cursor.execute("DELETE FROM revision WHERE repos=%s", (id,))
-        cursor.execute("DELETE FROM node_change WHERE repos=%s", (id,))
-        db.commit()
+        @with_transaction(self.env)
+        def do_remove(db):
+            id = rm.get_repository_id(reponame, db)
+            cursor = db.cursor()
+            cursor.execute("DELETE FROM repository WHERE id=%s", (id,))
+            cursor.execute("DELETE FROM revision WHERE repos=%s", (id,))
+            cursor.execute("DELETE FROM node_change WHERE repos=%s", (id,))
         rm.reload_repositories()
     
     def modify_repository(self, reponame, changes):
         if is_default(reponame):
             reponame = ''
         rm = RepositoryManager(self.env)
-        db = self.env.get_db_cnx()
-        id = rm.get_repository_id(reponame, db)
-        cursor = db.cursor()
-        for (k, v) in changes.iteritems():
-            if k not in self.repository_attrs:
-                continue
-            if k in('alias', 'name') and is_default(v):
-                v = ''
-            cursor.execute("UPDATE repository SET value=%s "
-                           "WHERE id=%s AND name=%s", (v, id, k))
-            cursor.execute("SELECT value FROM repository "
-                           "WHERE id=%s AND name=%s", (id, k))
-            if not cursor.fetchone():
-                cursor.execute("INSERT INTO repository (id, name, value) "
-                               "VALUES (%s, %s, %s)", (id, k, v))
-        db.commit()
+        @with_transaction(self.env)
+        def do_modify(db):
+            cursor = db.cursor()
+            id = rm.get_repository_id(reponame, db)
+            for (k, v) in changes.iteritems():
+                if k not in self.repository_attrs:
+                    continue
+                if k in('alias', 'name') and is_default(v):
+                    v = ''
+                cursor.execute("UPDATE repository SET value=%s "
+                               "WHERE id=%s AND name=%s", (v, id, k))
+                cursor.execute("SELECT value FROM repository "
+                               "WHERE id=%s AND name=%s", (id, k))
+                if not cursor.fetchone():
+                    cursor.execute("INSERT INTO repository (id, name, value) "
+                                   "VALUES (%s, %s, %s)", (id, k, v))
         rm.reload_repositories()
 
 
         id = cursor.fetchone()[0] + 1
         cursor.execute("INSERT INTO repository (id, name, value) "
                        "VALUES (%s,%s,%s)", (id, 'name', reponame))
-        if handle_ta:
-            db.commit()
         return id
     
     def get_repository(self, reponame):

trac/versioncontrol/cache.py

 
 from trac.cache import CacheProxy
 from trac.core import TracError
+from trac.db.util import with_transaction
 from trac.util.datefmt import utc, to_timestamp
 from trac.util.translation import _
 from trac.versioncontrol import Changeset, Node, Repository, NoSuchChangeset
 
     def sync_changeset(self, rev):
         cset = self.repos.get_changeset(rev)
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        cursor.execute("SELECT time,author,message FROM revision "
-                       "WHERE repos=%s AND rev=%s",
-                       (self.id, str(cset.rev)))
-        old_cset = None
-        for time, author, message in cursor:
-            date = datetime.fromtimestamp(time, utc)
-            old_cset = Changeset(self.repos, cset.rev, message, author, date)
-        
-        cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s "
-                       "WHERE repos=%s AND rev=%s",
-                       (to_timestamp(cset.date), cset.author, cset.message,
-                        self.id, str(cset.rev)))
-        db.commit()
-        return old_cset
+        old_cset = [None]
+
+        @with_transaction(self.env)
+        def do_sync(db):
+            cursor = db.cursor()
+            cursor.execute("""
+                SELECT time,author,message FROM revision
+                WHERE repos=%s AND rev=%s
+                """, (self.id, str(cset.rev)))
+            for time, author, message in cursor:
+                date = datetime.fromtimestamp(time, utc)
+                old_cset[0] = Changeset(self.repos, cset.rev, message, author,
+                                        date)
+                cursor.execute("""
+                    UPDATE revision SET time=%s, author=%s, message=%s
+                    WHERE repos=%s AND rev=%s
+                    """, (to_timestamp(cset.date), cset.author,
+                          cset.message, self.id, str(cset.rev)))
+        return old_cset[0]
         
     def _metadata(self, db):
         """Retrieve data for the cached `metadata` attribute."""
 
 from trac.config import BoolOption, IntOption
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.web.api import IAuthenticator, IRequestHandler
 from trac.web.chrome import INavigationContributor
 from trac.util import hex_entropy, md5, md5crypt
                _('Already logged in as %(user)s.', user=req.authname)
 
         cookie = hex_entropy()
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) "
-                       "VALUES (%s, %s, %s, %s)", (cookie, remote_user,
-                       req.remote_addr, int(time.time())))
-        db.commit()
-
+        @with_transaction(self.env)
+        def store_session_cookie(db):
+            cursor = db.cursor()
+            cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) "
+                           "VALUES (%s, %s, %s, %s)",
+                           (cookie, remote_user,
+                            req.remote_addr, int(time.time())))
         req.authname = remote_user
         req.outcookie['trac_auth'] = cookie
         req.outcookie['trac_auth']['path'] = req.base_path or '/'
 
         # While deleting this cookie we also take the opportunity to delete
         # cookies older than 10 days
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        cursor.execute("DELETE FROM auth_cookie WHERE name=%s OR time < %s",
-                       (req.authname, int(time.time()) - 86400 * 10))
-        db.commit()
+        @with_transaction(self.env)
+        def delete_session_cookie(db):
+            cursor = db.cursor()
+            cursor.execute("DELETE FROM auth_cookie "
+                           "WHERE name=%s OR time < %s",
+                           (req.authname, int(time.time()) - 86400 * 10))
         self._expire_cookie(req)
         custom_redirect = self.config['metanav'].get('logout.redirect')
         if custom_redirect:

trac/web/session.py

 import time
 
 from trac.core import TracError
+from trac.db.util import with_transaction
 from trac.util import hex_entropy
 from trac.util.html import Markup
 
 
         authenticated = int(self.authenticated)
         now = int(time.time())
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
 
-        if self._new:
-            self.last_visit = now
-            self._new = False
-            # The session might already exist even if _new is True since
-            # it could have been created by a concurrent request (#3563).
-            try:
-                cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"
-                               " VALUES(%s,%s,%s)",
-                               (self.sid, self.last_visit, authenticated))
-            except Exception, e:
-                db.rollback()
-                self.env.log.warning('Session %s already exists: %s' % 
-                                     (self.sid, e))
-        if self._old != self:
-            attrs = [(self.sid, authenticated, k, v) for k, v in self.items()]
-            cursor.execute("DELETE FROM session_attribute WHERE sid=%s",
-                           (self.sid,))
-            self._old = dict(self.items())
-            if attrs:
-                # The session variables might already have been updated by a 
-                # concurrent request.
+        @with_transaction(self.env)
+        def delete_session_cookie(db):
+            cursor = db.cursor()
+            if self._new:
+                self.last_visit = now
+                self._new = False
+                # The session might already exist even if _new is True since
+                # it could have been created by a concurrent request (#3563).
                 try:
-                    cursor.executemany("INSERT INTO session_attribute "
-                                       "(sid,authenticated,name,value) "
-                                       "VALUES(%s,%s,%s,%s)", attrs)
-                except Exception, e:
+                    cursor.execute("INSERT INTO session "
+                                   " (sid,last_visit,authenticated)"
+                                   " VALUES (%s,%s,%s)",
+                                   (self.sid, self.last_visit, authenticated))
+                except Exception:
                     db.rollback()
-                    self.env.log.warning('Attributes for session %s already '
-                                         'updated: %s' % (self.sid, e))
-            elif not authenticated:
-                # No need to keep around empty unauthenticated sessions
-                cursor.execute("DELETE FROM session "
-                               "WHERE sid=%s AND authenticated=0", (self.sid,))
-                db.commit()
-                return
-
-        # Update the session last visit time if it is over an hour old,
-        # so that session doesn't get purged
-        if now - self.last_visit > UPDATE_INTERVAL:
-            self.last_visit = now
-            self.env.log.info("Refreshing session %s" % self.sid)
-            cursor.execute('UPDATE session SET last_visit=%s '
-                           'WHERE sid=%s AND authenticated=%s',
-                           (self.last_visit, self.sid, authenticated))
-            # Purge expired sessions. We do this only when the session was
-            # changed as to minimize the purging.
-            mintime = now - PURGE_AGE
-            self.env.log.debug('Purging old, expired, sessions.')
-            cursor.execute("DELETE FROM session_attribute "
-                           "WHERE authenticated=0 AND sid "
-                           "IN (SELECT sid FROM session WHERE "
-                           "authenticated=0 AND last_visit < %s)",
-                           (mintime,))
-            cursor.execute("DELETE FROM session WHERE "
-                           "authenticated=0 AND last_visit < %s",
-                           (mintime,))
-        db.commit()
+                    self.env.log.warning('Session %s already exists', self.sid)
+            if self._old != self:
+                attrs = [(self.sid, authenticated, k, v) 
+                         for k, v in self.items()]
+                cursor.execute("DELETE FROM session_attribute WHERE sid=%s",
+                               (self.sid,))
+                self._old = dict(self.items())
+                if attrs:
+                    # The session variables might already have been updated
+                    # by a concurrent request.
+                    try:
+                        cursor.executemany("INSERT INTO session_attribute "
+                                           " (sid,authenticated,name,value) "
+                                           " VALUES (%s,%s,%s,%s)", attrs)
+                    except Exception:
+                        db.rollback()
+                        self.env.log.warning('Attributes for session %s '
+                                             'already updated', self.sid)
+                elif not authenticated:
+                    # No need to keep around empty unauthenticated sessions
+                    cursor.execute("DELETE FROM session "
+                                   "WHERE sid=%s AND authenticated=0",
+                                   (self.sid,))
+                    return
+            # Update the session last visit time if it is over an hour old,
+            # so that session doesn't get purged
+            if now - self.last_visit > UPDATE_INTERVAL:
+                self.last_visit = now
+                self.env.log.info("Refreshing session %s", self.sid)
+                cursor.execute('UPDATE session SET last_visit=%s '
+                               'WHERE sid=%s AND authenticated=%s',
+                               (self.last_visit, self.sid, authenticated))
+                # Purge expired sessions. We do this only when the session was
+                # changed as to minimize the purging.
+                mintime = now - PURGE_AGE
+                self.env.log.debug('Purging old, expired, sessions.')
+                cursor.execute("DELETE FROM session_attribute "
+                               "WHERE authenticated=0 AND sid "
+                               "IN (SELECT sid FROM session WHERE "
+                               "authenticated=0 AND last_visit < %s)",
+                               (mintime,))
+                cursor.execute("DELETE FROM session WHERE "
+                               "authenticated=0 AND last_visit < %s",
+                               (mintime,))
 
 
 class Session(DetachedSession):
         assert new_sid, 'Session ID cannot be empty'
         if new_sid == self.sid:
             return
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
+        cursor = self.env.get_db_cnx().cursor()
         cursor.execute("SELECT sid FROM session WHERE sid=%s", (new_sid,))
         if cursor.fetchone():
             raise TracError(Markup('Session "%s" already exists.<br />'
                                    'Please choose a different session ID.')
                             % new_sid, 'Error renaming session')
-        self.env.log.debug('Changing session ID %s to %s' % (self.sid, new_sid))
-        cursor.execute("UPDATE session SET sid=%s WHERE sid=%s "
-                       "AND authenticated=0", (new_sid, self.sid))
-        cursor.execute("UPDATE session_attribute SET sid=%s "
-                       "WHERE sid=%s and authenticated=0",
-                       (new_sid, self.sid))
-        db.commit()
+        self.env.log.debug('Changing session ID %s to %s', self.sid, new_sid)
+
+        @with_transaction(self.env)
+        def update_session_id(db):
+            cursor = db.cursor()
+            cursor.execute("UPDATE session SET sid=%s WHERE sid=%s "
+                           "AND authenticated=0", (new_sid, self.sid))
+            cursor.execute("UPDATE session_attribute SET sid=%s "
+                           "WHERE sid=%s and authenticated=0",
+                           (new_sid, self.sid))
         self.sid = new_sid
         self.bake_cookie()
 
         assert self.req.authname != 'anonymous', \
                'Cannot promote session of anonymous user'
 
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-        cursor.execute("SELECT authenticated FROM session "
-                       "WHERE sid=%s OR sid=%s ", (sid, self.req.authname))
-        authenticated_flags = [row[0] for row in cursor.fetchall()]
-        
-        if len(authenticated_flags) == 2:
-            # There's already an authenticated session for the user, we
-            # simply delete the anonymous session
-            cursor.execute("DELETE FROM session WHERE sid=%s "
-                           "AND authenticated=0", (sid,))
-            cursor.execute("DELETE FROM session_attribute WHERE sid=%s "
-                           "AND authenticated=0", (sid,))
-        elif len(authenticated_flags) == 1:
-            if not authenticated_flags[0]:
-                # Update the anomymous session records so that the session ID
-                # becomes the user name, and set the authenticated flag.
-                self.env.log.debug('Promoting anonymous session %s to '
-                                   'authenticated session for user %s',
-                                   sid, self.req.authname)
-                cursor.execute("UPDATE session SET sid=%s,authenticated=1 "
-                               "WHERE sid=%s AND authenticated=0",
-                               (self.req.authname, sid))
-                cursor.execute("UPDATE session_attribute "
-                               "SET sid=%s,authenticated=1 WHERE sid=%s",
-                               (self.req.authname, sid))
-        else:
-            # we didn't have an anonymous session for this sid
-            cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"
-                           " VALUES(%s,%s,1)",
-                           (self.req.authname, int(time.time())))
+        @with_transaction(self.env)
+        def update_session_id(db):
+            cursor = db.cursor()
+            cursor.execute("SELECT authenticated FROM session "
+                           "WHERE sid=%s OR sid=%s ", (sid, self.req.authname))
+            authenticated_flags = [row[0] for row in cursor.fetchall()]
+            
+            if len(authenticated_flags) == 2:
+                # There's already an authenticated session for the user,
+                # we simply delete the anonymous session
+                cursor.execute("DELETE FROM session WHERE sid=%s "
+                               "AND authenticated=0", (sid,))
+                cursor.execute("DELETE FROM session_attribute WHERE sid=%s "
+                               "AND authenticated=0", (sid,))
+            elif len(authenticated_flags) == 1:
+                if not authenticated_flags[0]:
+                    # Update the anomymous session records so the session ID
+                    # becomes the user name, and set the authenticated flag.
+                    self.env.log.debug('Promoting anonymous session %s to '
+                                       'authenticated session for user %s',
+                                       sid, self.req.authname)
+                    cursor.execute("UPDATE session SET sid=%s,authenticated=1 "
+                                   "WHERE sid=%s AND authenticated=0",
+                                   (self.req.authname, sid))
+                    cursor.execute("UPDATE session_attribute "
+                                   "SET sid=%s,authenticated=1 WHERE sid=%s",
+                                   (self.req.authname, sid))
+            else:
+                # we didn't have an anonymous session for this sid
+                cursor.execute("INSERT INTO session "
+                               "(sid,last_visit,authenticated)"
+                               " VALUES(%s,%s,1)",
+                               (self.req.authname, int(time.time())))
         self._new = False
-        db.commit()
 
         self.sid = sid
         self.bake_cookie(0) # expire the cookie

trac/wiki/admin.py

 
 from trac.admin import *
 from trac.core import *
+from trac.db.util import with_transaction
 from trac.wiki import model
 from trac.wiki.api import WikiSystem
 from trac.util import read_file
         data = to_unicode(data, 'utf-8')
         
         # Make sure we don't insert the exact same page twice
-        handle_ta = not db
-        if handle_ta:
-            db = self.env.get_db_cnx()
-        cursor = db.cursor()
+        cursor = self.env.get_db_cnx().cursor()
+
         cursor.execute("SELECT text FROM wiki WHERE name=%s "
                        "ORDER BY version DESC LIMIT 1",
                        (title,))
             printout('  %s already up to date.' % title)
             return False
         
-        if replace and old:
-            cursor.execute("UPDATE wiki SET text=%s WHERE name=%s "
-                           "  AND version=(SELECT max(version) FROM wiki "
-                           "               WHERE name=%s)",
-                           (data, title, title))
-        else:
-            cursor.execute("INSERT INTO wiki(version,name,time,author,ipnr,"
-                           "                 text) "
-                           "SELECT 1+COALESCE(max(version),0),%s,%s,"
-                           "       'trac','127.0.0.1',%s FROM wiki "
-                           "WHERE name=%s",
-                           (title, int(time.time()), data, title))
-        if not old:
-            WikiSystem(self.env).pages.invalidate(db)
-        if handle_ta:
-            db.commit()
+        @with_transaction(self.env, db)
+        def do_import(db):
+            cursor = db.cursor()
+            if replace and old:
+                cursor.execute("UPDATE wiki SET text=%s WHERE name=%s "
+                               "  AND version=(SELECT max(version) FROM wiki "
+                               "               WHERE name=%s)",
+                               (data, title, title))
+            else:
+                cursor.execute("INSERT INTO wiki(version,name,time,author,"
+                               "                 ipnr,text) "
+                               "SELECT 1+COALESCE(max(version),0),%s,%s,"
+                               "       'trac','127.0.0.1',%s FROM wiki "
+                               "WHERE name=%s",
+                               (title, int(time.time()), data, title))
+            if not old:
+                WikiSystem(self.env).pages.invalidate(db)
         return True
 
     def load_pages(self, dir, db=None, ignore=[], create_only=[],
                     [_('Title'), _('Edits'), _('Modified')])
     
     def _do_remove(self, name):
-        db = self.env.get_db_cnx()
-        if name.endswith('*'):
-            pages = list(WikiSystem(self.env).get_pages(name.rstrip('*')
-                                                        or None))
-            for p in pages:
-                page = model.WikiPage(self.env, p, db=db)
+        @with_transaction(self.env)        
+        def do_transaction(db):
+            if name.endswith('*'):
+                pages = list(WikiSystem(self.env).get_pages(name.rstrip('*')
+                                                            or None))
+                for p in pages:
+                    page = model.WikiPage(self.env, p, db=db)
+                    page.delete(db=db)
+                print_table(((p,) for p in pages), [_('Deleted pages')])
+            else:
+                page = model.WikiPage(self.env, name, db=db)
                 page.delete(db=db)
-            print_table(((p,) for p in pages), [_('Deleted pages')])
-        else:
-            page = model.WikiPage(self.env, name, db=db)
-            page.delete(db=db)
-        db.commit()
     
     def _do_export(self, page, filename=None):
         self.export_page(page, filename)
                 self.export_page(p, dst, cursor)
     
     def _load_or_replace(self, paths, replace):
-        db = self.env.get_db_cnx()
-        for path in paths:
-            if os.path.isdir(path):
-                self.load_pages(path, db, replace=replace)
-            else:
-                page = os.path.basename(path)
-                page = unicode_unquote(page.encode('utf-8'))
-                if self.import_page(path, page, db, replace=replace):
-                    printout(_("  %(page)s imported from %(filename)s",
-                               filename=path, page=page))
-        db.commit()
+        @with_transaction(self.env)
+        def do_transaction(db):
+            for path in paths:
+                if os.path.isdir(path):
+                    self.load_pages(path, db, replace=replace)
+                else:
+                    page = os.path.basename(path)
+                    page = unicode_unquote(page.encode('utf-8'))
+                    if self.import_page(path, page, db, replace=replace):
+                        printout(_("  %(page)s imported from %(filename)s",
+                                   filename=path, page=page))
     
     def _do_load(self, *paths):
         self._load_or_replace(paths, replace=False)
         self._load_or_replace(paths, replace=True)
     
     def _do_upgrade(self):
-        db = self.env.get_db_cnx()
-        self.load_pages(pkg_resources.resource_filename('trac.wiki', 
-                                                        'default-pages'),
-                        db, ignore=['WikiStart', 'checkwiki.py'],
-                        create_only=['InterMapTxt'])
-        db.commit()
+        @with_transaction(self.env)
+        def do_transaction(db):
+            self.load_pages(pkg_resources.resource_filename('trac.wiki',
+                                                            'default-pages'),
+                            db, ignore=['WikiStart', 'checkwiki.py'],
+                            create_only=['InterMapTxt'])