From a332e33a5cdad311271dcb8449936f454772f6e0 Mon Sep 17 00:00:00 2001
From: Martijn Vermaat <martijn@vermaat.name>
Date: Mon, 21 May 2012 15:07:05 +0000
Subject: [PATCH] Auto reconnect to MySQL server (not using MySQLdb)

Automatically reconnect to the MySQL server if the connection has been lost,
independent from MySQLdb reconnect option which may not always be available.
This fixes Trac ticket #91.


git-svn-id: https://humgenprojects.lumc.nl/svn/mutalyzer/trunk@529 eb6bd6ab-9ccd-42b9-aceb-e2899b4a52f1
---
 extras/config.example                         |  5 +-
 .../006-config-add-autoreconnect.migration    |  5 +-
 mutalyzer/Db.py                               | 88 +++++++++++--------
 3 files changed, 57 insertions(+), 41 deletions(-)

diff --git a/extras/config.example b/extras/config.example
index e77c0b21..7b365494 100644
--- a/extras/config.example
+++ b/extras/config.example
@@ -42,7 +42,10 @@ LocalMySQLuser = "mutalyzer"
 # Host name for the local databases.
 LocalMySQLhost = "localhost"
 
-# Automatically reconnect to MySQL server (see Trac issue #91).
+# Automatically reconnect to MySQL server using the MySQLdb reconnect option.
+# Note that this may not always be available and if not will result in an
+# error if used. Mutalyzer also implements its own reconnecting mechanism now
+# which is always on. See Trac issue #91.
 autoReconnect = yes
 
 
diff --git a/extras/migrations/006-config-add-autoreconnect.migration b/extras/migrations/006-config-add-autoreconnect.migration
index 7b4512b7..6c9a66f7 100755
--- a/extras/migrations/006-config-add-autoreconnect.migration
+++ b/extras/migrations/006-config-add-autoreconnect.migration
@@ -19,7 +19,10 @@ if [ -e /etc/mutalyzer/config ] && ! $(grep -q 'autoReconnect' /etc/mutalyzer/co
         # Insert after LocalMySQLhost line
         if $(grep -q '^LocalMySQLhost' /etc/mutalyzer/config && sed -i '/^LocalMySQLhost/ a\
 \
-# Automatically reconnect to MySQL server (see Trac issue #91).\
+# Automatically reconnect to MySQL server using the MySQLdb reconnect option.\
+# Note that this may not always be available and if not will result in an\
+# error if used. Mutalyzer also implements its own reconnecting mechanism now\
+# which is always on. See Trac issue #91.\
 autoReconnect = yes' /etc/mutalyzer/config); then
             echo -e "${COLOR_INFO}Added autoReconnect = yes to /etc/mutalyzer/config${COLOR_END}"
             echo 'Performed migration.'
diff --git a/mutalyzer/Db.py b/mutalyzer/Db.py
index 2e23fbca..a704e8bd 100644
--- a/mutalyzer/Db.py
+++ b/mutalyzer/Db.py
@@ -35,57 +35,59 @@ from mutalyzer import config
 # rewritten when this bug is fixed.
 #
 
-class Db() :
+class Db():
     """
-    Log in to a database and keep it open for queries.
+    Query the database server (and lazily keep a connection open to it).
 
-    Private variables:
-        - __db ; Interface to the database.
-
-    Special methods:
-        - __init__(dbName, mySqlUser, mySqlHost) ; Do the login.
-
-    Public methods:
-        - query(statement) ; General query function.
+    This class is subclassed below to create specific interfaces to the
+    database.
     """
-    def __init__(self, dbName, mySqlUser, mySqlHost) :
+    def __init__(self, database, user, host):
         """
-        Log in to the database.
+        Create an interface to the database.
 
-        Private variables (altered):
-            - __db ; The interface to the database.
+        @arg database: Name of the database to use.
+        @type database: str
+        @arg user: User name for the database.
+        @type user: str
+        @arg host: Host name for the database.
+        @type host: str
+        """
+        self._database = database
+        self._user = user
+        self._host = host
 
-        @arg dbName: The name of the database to use
-        @type dbName: string
-        @arg mySqlUser: User name for the database
-        @type mySqlUser: string
-        @arg mySqlHost: Host name for the database
-        @type mySqlHost: string
+        # The connection to the database server is created lazily in the query
+        # method.
+        self._connection = None
+    #__init__
 
-        Note: Depending on a configuration setting, we automatically reconnect
-            to the MySQL server. This is expecially useful for long-running
-            processes such as the batch deamon, which would otherwise loose
-            their connection on an event such as restarting the MySQL server.
+    def _connect(self):
+        """
+        Connect to the database server.
+
+        Note: We would like to automatically reconnect to the database server.
+            This is especially useful for long-running processes such as the
+            batch deamon, which would otherwise loose their connection on an
+            event such as restarting the database server.
+            The MySQL client libraries provide a reconnect option, but this
+            is unfortunately not implemented in (most versions of) the Python
+            MySQLdb module.
+            Therefore we manually implement automatic reconnects in the query
+            method, but also optionally use the reconnect option from MySQLdb
+            if specified in the Mutalyzer configuration.
             Also see Trac ticket #91.
-            Be alarmed though, that this messes up transactions. Fortunately,
-            Mutalyzer doesn't use transactions at the moment.
-            Additionally, this feature has been removed from the MySQLdb
-            library starting from Debian Wheezy. Again, see #91.
         """
-        kwargs = dict(user=mySqlUser, db=dbName, host=mySqlHost)
+        kwargs = dict(user=self._user, db=self._database, host=self._host)
         if config.get('autoReconnect'):
             kwargs.update(reconnect=True)
+        self._connection = MySQLdb.connect(**kwargs)
+    #_connect
 
-        self.__db = MySQLdb.connect(**kwargs)
-    #__init__
-
-    def query(self, statement) :
+    def query(self, statement):
         """
         Query the database.
 
-        Private variables:
-            - __db ; Interface to the database.
-
         @arg statement: The statement that is to be queried
         @type statement: tuple (string, (args))
 
@@ -112,9 +114,17 @@ class Db() :
                     escaped_args.append(None)
         #if
 
-        # And do the query.
-        cursor = self.__db.cursor()
-        cursor.execute(statement[0], tuple(escaped_args))
+        # Do the query, but first connect to the database server if needed.
+        # This makes sure lost connections are re-created automatically (e.g.
+        # in case the server was restarted for maintenance).
+        try:
+            cursor = self._connection.cursor()
+            cursor.execute(statement[0], tuple(escaped_args))
+        except (AttributeError, MySQLdb.OperationalError):
+            self._connect()
+            cursor = self._connection.cursor()
+            cursor.execute(statement[0], tuple(escaped_args))
+
         result = cursor.fetchall()
         cursor.close()
 
-- 
GitLab