diff --git a/README.rst b/README.rst index 3892d56..0d3fbba 100644 --- a/README.rst +++ b/README.rst @@ -173,6 +173,9 @@ Changelog - Enrich exceptions with message from java SQLExceptions + - Be more specific about DB API exceptions: Distinguish DatabaseError and + InterfaceError. + - Fix typo LONGNARCHAR vs LONGVARCHAR (thanks @datdo for reporting #4) - 0.1.5 - 2015-03-02 diff --git a/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java b/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java index 056c25c..8ed0703 100644 --- a/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java +++ b/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java @@ -13,9 +13,20 @@ public abstract class MockConnection implements Connection { ResultSet mockResultSet; - public final void mockExceptionOnExecute(String exceptionMessage) throws SQLException { + public final void mockExceptionOnCommit(String className, String exceptionMessage) throws SQLException { + Throwable exception = createException(className, exceptionMessage); + Mockito.doThrow(exception).when(this).commit(); + } + + public final void mockExceptionOnRollback(String className, String exceptionMessage) throws SQLException { + Throwable exception = createException(className, exceptionMessage); + Mockito.doThrow(exception).when(this).rollback(); + } + + public final void mockExceptionOnExecute(String className, String exceptionMessage) throws SQLException { PreparedStatement mockPreparedStatement = Mockito.mock(PreparedStatement.class); - Mockito.when(mockPreparedStatement.execute()).thenThrow(new SQLException(exceptionMessage)); + Throwable exception = createException(className, exceptionMessage); + Mockito.when(mockPreparedStatement.execute()).thenThrow(exception); Mockito.when(this.prepareStatement(Mockito.anyString())).thenReturn(mockPreparedStatement); } @@ -33,7 +44,19 @@ public abstract class MockConnection implements Connection { Mockito.when(this.prepareStatement(Mockito.anyString())).thenReturn(mockPreparedStatement); } - private int extractTypeCodeForName(String sqlTypesName) { + public final ResultSet verifyResultSet() { + return Mockito.verify(mockResultSet); + } + + private static Throwable createException(String className, String exceptionMessage) { + try { + return (Throwable) Class.forName(className).getConstructor(String.class).newInstance(exceptionMessage); + } catch (Exception e) { + throw new RuntimeException("Couldn't initialize class " + className + ".", e); + } + } + + private static int extractTypeCodeForName(String sqlTypesName) { try { Field field = Types.class.getField(sqlTypesName); return field.getInt(null); @@ -48,8 +71,4 @@ public abstract class MockConnection implements Connection { } } - public final ResultSet verifyResultSet() { - return Mockito.verify(mockResultSet); - } - } diff --git a/src/jaydebeapi/dbapi2.py b/src/jaydebeapi/dbapi2.py index 2b05711..8fd91d9 100644 --- a/src/jaydebeapi/dbapi2.py +++ b/src/jaydebeapi/dbapi2.py @@ -41,13 +41,14 @@ _java_array_byte = None _handle_sql_exception = None -def _handle_sql_exception_jython(ex): +def _handle_sql_exception_jython(): from java.sql import SQLException - if isinstance(ex, SQLException): - # TODO get stacktrace - raise Error, ex.getMessage() + exc_info = sys.exc_info() + if isinstance(exc_info[1], SQLException): + exc_type = DatabaseError else: - raise ex + exc_type = InterfaceError + raise exc_type, exc_info[1], exc_info[2] def _jdbc_connect_jython(jclassname, jars, libs, *args): if _jdbc_name_to_const is None: @@ -103,14 +104,15 @@ def _prepare_jython(): global _handle_sql_exception _handle_sql_exception = _handle_sql_exception_jython -def _handle_sql_exception_jpype(ex): +def _handle_sql_exception_jpype(): import jpype SQLException = jpype.java.sql.SQLException - if issubclass(ex.__javaclass__, SQLException): - # TODO get stacktrace - raise Error, ex.message() + exc_info = sys.exc_info() + if issubclass(exc_info[1].__javaclass__, SQLException): + exc_type = DatabaseError else: - raise ex + exc_type = InterfaceError + raise exc_type, exc_info[1], exc_info[2] def _jdbc_connect_jpype(jclassname, jars, libs, *driver_args): import jpype @@ -353,15 +355,13 @@ class Connection(object): try: self.jconn.commit() except: - ex = sys.exc_info()[1] - _handle_sql_exception(ex) + _handle_sql_exception() def rollback(self): try: self.jconn.rollback() except: - ex = sys.exc_info()[1] - _handle_sql_exception(ex) + _handle_sql_exception() def cursor(self): return Cursor(self, self._converters) @@ -445,8 +445,7 @@ class Cursor(object): try: is_rs = self._prep.execute() except: - ex = sys.exc_info()[1] - _handle_sql_exception(ex) + _handle_sql_exception() if is_rs: self._rs = self._prep.getResultSet() self._meta = self._rs.getMetaData() @@ -454,7 +453,7 @@ class Cursor(object): else: self.rowcount = self._prep.getUpdateCount() # self._prep.getWarnings() ??? - + def executemany(self, operation, seq_of_parameters): self._close_last() self._prep = self._connection.jconn.prepareStatement(operation) diff --git a/test/test_mock.py b/test/test_mock.py index 6410186..e2bc7d6 100644 --- a/test/test_mock.py +++ b/test/test_mock.py @@ -47,12 +47,52 @@ class MockTest(unittest.TestCase): 'getObject')) verify_get(1) - def test_exception_on_execute(self): - dummy_msg = "expected" - self.conn.jconn.mockExceptionOnExecute(dummy_msg) + def test_sql_exception_on_execute(self): + self.conn.jconn.mockExceptionOnExecute("java.sql.SQLException", "expected") cursor = self.conn.cursor() try: cursor.execute("dummy stmt") fail("expected exception") - except jaydebeapi.Error, e: - self.assertEquals(str(e), 'expected') + except jaydebeapi.DatabaseError, e: + self.assertEquals(str(e), "java.sql.SQLException: expected") + + def test_runtime_exception_on_execute(self): + self.conn.jconn.mockExceptionOnExecute("java.lang.RuntimeException", "expected") + cursor = self.conn.cursor() + try: + cursor.execute("dummy stmt") + fail("expected exception") + except jaydebeapi.InterfaceError, e: + self.assertEquals(str(e), "java.lang.RuntimeException: expected") + + def test_sql_exception_on_commit(self): + self.conn.jconn.mockExceptionOnCommit("java.sql.SQLException", "expected") + try: + self.conn.commit() + fail("expected exception") + except jaydebeapi.DatabaseError, e: + self.assertEquals(str(e), "java.sql.SQLException: expected") + + def test_runtime_exception_on_commit(self): + self.conn.jconn.mockExceptionOnCommit("java.lang.RuntimeException", "expected") + try: + self.conn.commit() + fail("expected exception") + except jaydebeapi.InterfaceError, e: + self.assertEquals(str(e), "java.lang.RuntimeException: expected") + + def test_sql_exception_on_rollback(self): + self.conn.jconn.mockExceptionOnRollback("java.sql.SQLException", "expected") + try: + self.conn.rollback() + fail("expected exception") + except jaydebeapi.DatabaseError, e: + self.assertEquals(str(e), "java.sql.SQLException: expected") + + def test_runtime_exception_on_rollback(self): + self.conn.jconn.mockExceptionOnRollback("java.lang.RuntimeException", "expected") + try: + self.conn.rollback() + fail("expected exception") + except jaydebeapi.InterfaceError, e: + self.assertEquals(str(e), "java.lang.RuntimeException: expected")