Commits

Mike Bayer committed e1c0ea7

- added is_disconnect() support for oracle
- fixed _handle_dbapi_error to detect endless loops, doesn't call rollback/cursor.close
etc. in case of disconnect

Comments (0)

Files changed (5)

 
    - sqlite SLDate type will not erroneously render "microseconds" portion 
      of a datetime or time object.
-     
+   
+   - oracle
+      - added disconnect detection support for Oracle
+   
    - MSSQL
       - PyODBC no longer has a global "set nocount on".
       - Fix non-identity integer PKs on autload [ticket:824]

lib/sqlalchemy/databases/oracle.py

 
         return ([], opts)
 
+    def is_disconnect(self, e):
+        if isinstance(e, self.dbapi.InterfaceError):
+            return "not connected" in str(e)
+        else:
+            return "ORA-03114" in str(e) or "ORA-03113" in str(e)
+
     def type_descriptor(self, typeobj):
         return sqltypes.adapt_type(typeobj, colspecs)
 

lib/sqlalchemy/engine/base.py

         try:
             self.engine.dialect.do_begin(self.connection)
         except Exception, e:
-            raise self.__handle_dbapi_exception(e, None, None, None)
+            raise self._handle_dbapi_exception(e, None, None, None)
 
     def _rollback_impl(self):
         if not self.closed and not self.invalidated and self.__connection.is_valid:
                 self.engine.dialect.do_rollback(self.connection)
                 self.__transaction = None
             except Exception, e:
-                raise self.__handle_dbapi_exception(e, None, None, None)
+                raise self._handle_dbapi_exception(e, None, None, None)
         else:
             self.__transaction = None
 
             self.engine.dialect.do_commit(self.connection)
             self.__transaction = None
         except Exception, e:
-            raise self.__handle_dbapi_exception(e, None, None, None)
+            raise self._handle_dbapi_exception(e, None, None, None)
         
     def _savepoint_impl(self, name=None):
         if name is None:
         else:
             self._cursor_execute(context.cursor, context.statement, context.parameters[0], context=context)
 
-    def __handle_dbapi_exception(self, e, statement, parameters, cursor):
-        if not isinstance(e, self.dialect.dbapi.Error):
-            return e
-        is_disconnect = self.dialect.is_disconnect(e)
-        if is_disconnect:
-            self.invalidate(e)
-            self.engine.dispose()
-        if cursor:
-            cursor.close()
-        self._autorollback()
-        if self.__close_with_result:
-            self.close()
-        return exceptions.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect)
+    def _handle_dbapi_exception(self, e, statement, parameters, cursor):
+        if getattr(self, '_reentrant_error', False):
+            return exceptions.DBAPIError.instance(None, None, e)
+        self._reentrant_error = True
+        try:
+            if not isinstance(e, self.dialect.dbapi.Error):
+                return e
+            is_disconnect = self.dialect.is_disconnect(e)
+            if is_disconnect:
+                self.invalidate(e)
+                self.engine.dispose()
+            else:
+                if cursor:
+                    cursor.close()
+                self._autorollback()
+                if self.__close_with_result:
+                    self.close()
+            return exceptions.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect)
+        finally:
+            del self._reentrant_error
         
     def __create_execution_context(self, **kwargs):
         try:
             return self.engine.dialect.create_execution_context(connection=self, **kwargs)
         except Exception, e:
-            raise self.__handle_dbapi_exception(e, kwargs.get('statement', None), kwargs.get('parameters', None), None)
+            raise self._handle_dbapi_exception(e, kwargs.get('statement', None), kwargs.get('parameters', None), None)
 
     def _cursor_execute(self, cursor, statement, parameters, context=None):
         if self.engine._should_log_info:
         try:
             self.dialect.do_execute(cursor, statement, parameters, context=context)
         except Exception, e:
-            raise self.__handle_dbapi_exception(e, statement, parameters, cursor)
+            raise self._handle_dbapi_exception(e, statement, parameters, cursor)
 
     def _cursor_executemany(self, cursor, statement, parameters, context=None):
         if self.engine._should_log_info:
         try:
             self.dialect.do_executemany(cursor, statement, parameters, context=context)
         except Exception, e:
-            raise self.__handle_dbapi_exception(e, statement, parameters, cursor)
+            raise self._handle_dbapi_exception(e, statement, parameters, cursor)
 
     # poor man's multimethod/generic function thingy
     executors = {

lib/sqlalchemy/engine/default.py

                dbtype = typeengine.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi)
                if dbtype is not None:
                     inputsizes.append(dbtype)
-            self.cursor.setinputsizes(*inputsizes)
+            try:
+                self.cursor.setinputsizes(*inputsizes)
+            except Exception, e:
+                raise self._connection._handle_dbapi_exception(e, None, None, None)
         else:
             inputsizes = {}
             for key in self.compiled.bind_names.values():
                 dbtype = typeengine.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi)
                 if dbtype is not None:
                     inputsizes[key.encode(self.dialect.encoding)] = dbtype
-            self.cursor.setinputsizes(**inputsizes)
+            try:
+                self.cursor.setinputsizes(**inputsizes)
+            except Exception, e:
+                raise self._connection._handle_dbapi_exception(e, None, None, None)
 
     def __process_defaults(self):
         """generate default values for compiled insert/update statements,

test/engine/reconnect.py

 import testbase
 import sys, weakref
-from sqlalchemy import create_engine, exceptions
+from sqlalchemy import create_engine, exceptions, select
 from testlib import *
 
 
         conn = db.connect()
         
         # connection works
-        conn.execute("SELECT 1")
+        conn.execute(select([1]))
         
         # create a second connection within the pool, which we'll ensure also goes away
         conn2 = db.connect()
         dbapi.shutdown()
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError:
             pass
         assert len(dbapi.connections) == 0
         
         conn =db.connect()
-        conn.execute("SELECT 1")
+        conn.execute(select([1]))
         conn.close()
         assert len(dbapi.connections) == 1
     
         dbapi.shutdown()
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError:
             pass
         assert trans.is_active
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.InvalidRequestError, e:
             assert str(e) == "Can't reconnect until invalid transaction is rolled back"
         trans.rollback()
         assert not trans.is_active
         
-        conn.execute("SELECT 1")
+        conn.execute(select([1]))
         assert not conn.invalidated
         
         assert len(dbapi.connections) == 1
     def test_conn_reusable(self):
         conn = db.connect()
         
-        conn.execute("SELECT 1")
+        conn.execute(select([1]))
 
         assert len(dbapi.connections) == 1
         
 
         # raises error
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError:
             pass
         assert len(dbapi.connections) == 0
             
         # test reconnects
-        conn.execute("SELECT 1")
+        conn.execute(select([1]))
         assert not conn.invalidated
         assert len(dbapi.connections) == 1
         
     def test_reconnect(self):
         conn = engine.connect()
 
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         assert not conn.closed
 
         engine.test_shutdown()
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError, e:
             if not e.connection_invalidated:
         assert conn.invalidated
 
         assert conn.invalidated
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         assert not conn.invalidated
 
         # one more time
         engine.test_shutdown()
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError, e:
             if not e.connection_invalidated:
                 raise
         assert conn.invalidated
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         assert not conn.invalidated
 
         conn.close()
     
     def test_close(self):
         conn = engine.connect()
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         assert not conn.closed
 
         engine.test_shutdown()
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError, e:
             if not e.connection_invalidated:
 
         conn.close()
         conn = engine.connect()
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         
     def test_with_transaction(self):
         conn = engine.connect()
 
         trans = conn.begin()
 
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         assert not conn.closed
 
         engine.test_shutdown()
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.DBAPIError, e:
             if not e.connection_invalidated:
         assert trans.is_active
 
         try:
-            conn.execute("SELECT 1")
+            conn.execute(select([1]))
             assert False
         except exceptions.InvalidRequestError, e:
             assert str(e) == "Can't reconnect until invalid transaction is rolled back"
         assert not trans.is_active
 
         assert conn.invalidated
-        self.assertEquals(conn.execute("SELECT 1").scalar(), 1) 
+        self.assertEquals(conn.execute(select([1])).scalar(), 1) 
         assert not conn.invalidated
         
         
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.