]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add connection and cursor to is_disconnect(). We aren't using it yet,
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Feb 2011 20:06:32 +0000 (15:06 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Feb 2011 20:06:32 +0000 (15:06 -0500)
but we'd like to.   Most DBAPIs don't give us anything we can do with it.
Some research was done on psycopg2 and it still seems like they give us
no adequate method (tried connection.closed, cursor.closed, connection.status).
mxodbc claims their .closed attribute will work (but I am skeptical).
- remove beahvior in pool that auto-invalidated a connection when
the cursor failed to create.  That's not the pool's job.  we need the conn
for the error logic.  Can't get any tests to fail, curious why that
behavior was there, guess we'll find out (or not).
- add support for psycopg2 version detection.  even though we have
no use for it yet...
- adjust one of the reconnect tests to work with oracle's
horrendously slow connect speed

20 files changed:
lib/sqlalchemy/connectors/mxodbc.py
lib/sqlalchemy/connectors/pyodbc.py
lib/sqlalchemy/connectors/zxJDBC.py
lib/sqlalchemy/dialects/firebird/kinterbasdb.py
lib/sqlalchemy/dialects/informix/informixdb.py
lib/sqlalchemy/dialects/mssql/adodbapi.py
lib/sqlalchemy/dialects/mssql/pymssql.py
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/dialects/mysql/mysqlconnector.py
lib/sqlalchemy/dialects/mysql/oursql.py
lib/sqlalchemy/dialects/oracle/cx_oracle.py
lib/sqlalchemy/dialects/postgresql/pg8000.py
lib/sqlalchemy/dialects/postgresql/psycopg2.py
lib/sqlalchemy/dialects/postgresql/pypostgresql.py
lib/sqlalchemy/dialects/sqlite/pysqlite.py
lib/sqlalchemy/dialects/sybase/pysybase.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/pool.py
test/engine/test_reconnect.py

index f467234ca3056ca8b8bc97513f56466023999725..5573dda407ba4595f8f6b2fe3f4b7265a5f01ac7 100644 (file)
@@ -106,9 +106,9 @@ class MxODBCConnector(Connector):
         opts.pop('database', None)
         return (args,), opts
 
-    def is_disconnect(self, e):
-        # eGenix recommends checking connection.closed here,
-        # but how can we get a handle on the current connection?
+    def is_disconnect(self, e, connection, cursor):
+        # TODO: eGenix recommends checking connection.closed here
+        # Does that detect dropped connections ?
         if isinstance(e, self.dbapi.ProgrammingError):
             return "connection already closed" in str(e)
         elif isinstance(e, self.dbapi.Error):
index c66a8a8ae3d118877015ea06f8e435e3ed75db71..3f6d6cb5facbaebec2f6a5d1e76cf4d2d7f4e7e0 100644 (file)
@@ -81,7 +81,7 @@ class PyODBCConnector(Connector):
             connectors.extend(['%s=%s' % (k,v) for k,v in keys.iteritems()])
         return [[";".join (connectors)], connect_args]
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, self.dbapi.ProgrammingError):
             return "The cursor's connection has been closed." in str(e) or \
                             'Attempt to use a closed connection.' in str(e)
index a9ff5ec95c567f763732dfc36a0054588a6a3673..20bf9d9cfdfb98696e0c5b7f209f731fa951b2d6 100644 (file)
@@ -46,7 +46,7 @@ class ZxJDBCConnector(Connector):
                 self.jdbc_driver_name],
                 opts]
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if not isinstance(e, self.dbapi.ProgrammingError):
             return False
         e = str(e)
index 216fec27033222bcf3fcdfbd3a0282f25c2854b8..ebb7805ae6450b0a0c6765d978793a135acf24e8 100644 (file)
@@ -153,7 +153,7 @@ class FBDialect_kinterbasdb(FBDialect):
         else:
             return tuple([int(x) for x in m.group(1, 2, 3)] + ['interbase'])
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, (self.dbapi.OperationalError,
                             self.dbapi.ProgrammingError)):
             msg = str(e)
index c8198381693a4563f494c725c8b30d0232b6af45..1b6833af7d539272d21909577d25a1ece027373a 100644 (file)
@@ -62,7 +62,7 @@ class InformixDialect_informixdb(InformixDialect):
         v = VERSION_RE.split(connection.connection.dbms_version)
         return (int(v[1]), int(v[2]), v[3])
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, self.dbapi.OperationalError):
             return 'closed the connection' in str(e) \
                     or 'connection not open' in str(e)
index 355214d89258fb3ec5c20a21d8dbf38a6d871bf8..f2d945de217519140c6190a4edad4b87ded22cc0 100644 (file)
@@ -62,7 +62,7 @@ class MSDialect_adodbapi(MSDialect):
             connectors.append("Integrated Security=SSPI")
         return [[";".join (connectors)], {}]
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         return isinstance(e, self.dbapi.adodbapi.DatabaseError) and \
                             "'connection failure'" in str(e)
 
index 192e6336697730a2a16ba53a3f5beed44e191ab1..8bc0ad95b7aa968708ed35a17d01a552055cf534 100644 (file)
@@ -95,7 +95,7 @@ class MSDialect_pymssql(MSDialect):
             opts['host'] = "%s:%s" % (opts['host'], port)
         return [[], opts]
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         for msg in (
             "Error 10054",
             "Not connected to any MS SQL server",
index b495cc36e71f18d8fab8adc47a453559be15d68f..882e13d2e0af8d0a310d0e5c0eedf9e1ffe95113 100644 (file)
@@ -1711,7 +1711,7 @@ class MySQLDialect(default.DefaultDialect):
         resultset = connection.execute("XA RECOVER")
         return [row['data'][0:row['gtrid_length']] for row in resultset]
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, self.dbapi.OperationalError):
             return self._extract_error_code(e) in \
                         (2006, 2013, 2014, 2045, 2055)
index d3ec1f5cf4cf2eb0a280190651318900ad04f12f..035ebe45990214250d98732893499364711cc39e 100644 (file)
@@ -118,7 +118,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
     def _extract_error_code(self, exception):
         return exception.errno
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         errnos = (2006, 2013, 2014, 2045, 2055, 2048)
         exceptions = (self.dbapi.OperationalError,self.dbapi.InterfaceError)
         if isinstance(e, exceptions):
index d3ef839b14da189f8a542af637bf5fa11cc8b5ef..8caa1eaec85319842a323acd1cf8e022caf83f15 100644 (file)
@@ -195,7 +195,7 @@ class MySQLDialect_oursql(MySQLDialect):
                                 execution_options(_oursql_plain_query=True),
                                 table, charset, full_name)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, self.dbapi.ProgrammingError):
             return e.errno is None and 'cursor' not in e.args[1] and e.args[1].endswith('closed')
         else:
index bc1c877037e0d9455aaec604989097308c1c611b..b00adcd6328b3a441e36036e37be300f26c4ae1b 100644 (file)
@@ -680,7 +680,7 @@ class OracleDialect_cx_oracle(OracleDialect):
                         for x in connection.connection.version.split('.')
                     )
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, self.dbapi.InterfaceError):
             return "not connected" in str(e)
         else:
index d3c2f1d50c1c7d7e81b981630159925ff945b69f..c4f00eabea882c3f13667f57c90d4b6051e53cd3 100644 (file)
@@ -108,7 +108,7 @@ class PGDialect_pg8000(PGDialect):
         opts.update(url.query)
         return ([], opts)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         return "connection is closed" in str(e)
 
 dialect = PGDialect_pg8000
index 50ea9d4371e427be9ddff1279f227d67eb2ca253..10d6e02697875022f4d0e2052666a6a458a75254 100644 (file)
@@ -227,6 +227,7 @@ class PGDialect_psycopg2(PGDialect):
     execution_ctx_cls = PGExecutionContext_psycopg2
     statement_compiler = PGCompiler_psycopg2
     preparer = PGIdentifierPreparer_psycopg2
+    psycopg2_version = (0, 0)
 
     colspecs = util.update_copy(
         PGDialect.colspecs,
@@ -243,6 +244,11 @@ class PGDialect_psycopg2(PGDialect):
         self.server_side_cursors = server_side_cursors
         self.use_native_unicode = use_native_unicode
         self.supports_unicode_binds = use_native_unicode
+        if self.dbapi and hasattr(self.dbapi, '__version__'):
+            m = re.match(r'(\d+)\.(\d+)\.(\d+)?', 
+                                self.dbapi.__version__)
+            if m:
+                self.psycopg2_version = tuple(map(int, m.group(1, 2, 3)))
 
     @classmethod
     def dbapi(cls):
@@ -295,7 +301,7 @@ class PGDialect_psycopg2(PGDialect):
         opts.update(url.query)
         return ([], opts)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, self.dbapi.OperationalError):
             # these error messages from libpq: interfaces/libpq/fe-misc.c.
             # TODO: these are sent through gettext in libpq and we can't 
index dd22fcb3307ca09506831a20bc0404824cd573fe..a137a6240f6d24a0487461e0520e1e958ecc502d 100644 (file)
@@ -67,7 +67,7 @@ class PGDialect_pypostgresql(PGDialect):
         opts.update(url.query)
         return ([], opts)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         return "connection is closed" in str(e)
 
 dialect = PGDialect_pypostgresql
index 14cfa93d9507c178112030605b766834a8aa19bc..646c5b86f1272713aa4a6ada7629c98e62ef7fa3 100644 (file)
@@ -238,7 +238,7 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
 
         return ([filename], opts)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         return isinstance(e, self.dbapi.ProgrammingError) and "Cannot operate on a closed database." in str(e)
 
 dialect = SQLiteDialect_pysqlite
index fed7928172c1340c1c7d9190ba519997361f2871..e12cf07dd73b2e675922f2a483a73ded5630f68d 100644 (file)
@@ -87,7 +87,7 @@ class SybaseDialect_pysybase(SybaseDialect):
        # (12, 5, 0, 0)
        return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         if isinstance(e, (self.dbapi.OperationalError,
                             self.dbapi.ProgrammingError)):
             msg = str(e)
index b78a305374732262031328ba2af26f6b3dfe29c7..f6c9741365f64e338e673c83d7d7e16d4e5dc5ae 100644 (file)
@@ -502,7 +502,7 @@ class Dialect(object):
 
         raise NotImplementedError()
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         """Return True if the given DB-API error indicates an invalid
         connection"""
 
@@ -1518,7 +1518,7 @@ class Connection(Connectable):
             if context:
                 context.handle_dbapi_exception(e)
 
-            is_disconnect = self.dialect.is_disconnect(e)
+            is_disconnect = self.dialect.is_disconnect(e, self.__connection, cursor)
             if is_disconnect:
                 self.invalidate(e)
                 self.engine.dispose()
index aa75a28532f8a67b4cb3815d5971638558704ac2..e669b305ea1250c1645fa7392b2ab1eb22e4b116 100644 (file)
@@ -324,7 +324,7 @@ class DefaultDialect(base.Dialect):
     def do_execute(self, cursor, statement, parameters, context=None):
         cursor.execute(statement, parameters)
 
-    def is_disconnect(self, e):
+    def is_disconnect(self, e, connection, cursor):
         return False
 
     def reset_isolation_level(self, dbapi_conn):
index 5150d282c5b6c163f1abf5c25fa75aca058fd6d2..7201bccf32ff2f9553ab7a2933a1c081fb62f342 100644 (file)
@@ -425,11 +425,7 @@ class _ConnectionFairy(object):
         self._close()
 
     def cursor(self, *args, **kwargs):
-        try:
-            return self.connection.cursor(*args, **kwargs)
-        except Exception, e:
-            self.invalidate(e=e)
-            raise
+        return self.connection.cursor(*args, **kwargs)
 
     def __getattr__(self, key):
         return getattr(self.connection, key)
index 31a2b705a96d55f8318d94286d5b6d5269e428ee..9b3a1b4db681237c26d202e5f6dd42f4c1dfa77d 100644 (file)
@@ -58,7 +58,7 @@ class MockReconnectTest(TestBase):
                     module=dbapi, _initialize=False)
 
         # monkeypatch disconnect checker
-        db.dialect.is_disconnect = lambda e: isinstance(e, MockDisconnect)
+        db.dialect.is_disconnect = lambda e, conn, cursor: isinstance(e, MockDisconnect)
 
     def test_reconnect(self):
         """test that an 'is_disconnect' condition will invalidate the
@@ -259,6 +259,22 @@ class RealReconnectTest(TestBase):
 
         conn.close()
 
+    def test_ensure_is_disconnect_gets_connection(self):
+        def is_disconnect(e, conn, cursor):
+            # connection is still present
+            assert conn.connection is not None
+            # the error usually occurs on connection.cursor(),
+            # though MySQLdb we get a non-working cursor.
+            # assert cursor is None
+
+        engine.dialect.is_disconnect = is_disconnect
+        conn = engine.connect()
+        engine.test_shutdown()
+        assert_raises(
+            tsa.exc.DBAPIError,
+            conn.execute, select([1])
+        )
+
     def test_invalidate_twice(self):
         conn = engine.connect()
         conn.invalidate()
@@ -280,7 +296,7 @@ class RealReconnectTest(TestBase):
 
         p1 = engine.pool
 
-        def is_disconnect(e):
+        def is_disconnect(e, conn, cursor):
             return True
 
         engine.dialect.is_disconnect = is_disconnect
@@ -374,13 +390,28 @@ class RecycleTest(TestBase):
 
     def test_basic(self):
         for threadlocal in False, True:
-            engine = engines.reconnecting_engine(options={'pool_recycle'
-                    : 1, 'pool_threadlocal': threadlocal})
+            engine = engines.reconnecting_engine(
+                        options={'pool_threadlocal': threadlocal})
+
             conn = engine.contextual_connect()
             eq_(conn.execute(select([1])).scalar(), 1)
             conn.close()
+
+            # set the pool recycle down to 1.
+            # we aren't doing this inline with the
+            # engine create since cx_oracle takes way 
+            # too long to create the 1st connection and don't
+            # want to build a huge delay into this test.
+
+            engine.pool._recycle = 1
+
+            # kill the DB connection
             engine.test_shutdown()
+
+            # wait until past the recycle period
             time.sleep(2)
+
+            # can connect, no exception
             conn = engine.contextual_connect()
             eq_(conn.execute(select([1])).scalar(), 1)
             conn.close()