diff --git a/README.rst b/README.rst index bba9372..2132b34 100644 --- a/README.rst +++ b/README.rst @@ -160,10 +160,15 @@ Changelog ========= - Next version - unreleased + + - Return (big) decimal types as long value if scale is zero (thanks + to @ministat) + - Fix `DECIMAL` and `NUMERIC` type conversion for Jython + - 1.2.1 - 2020-05-27 - Increased thread safety. Should resolve some of the - `No suitable driver found` errors. (thanks to @thealmightygrant) + `No suitable driver found` errors (thanks to @thealmightygrant) - 1.2.0 - 2020-05-22 diff --git a/jaydebeapi/__init__.py b/jaydebeapi/__init__.py index 7af7d59..b66c444 100644 --- a/jaydebeapi/__init__.py +++ b/jaydebeapi/__init__.py @@ -663,12 +663,29 @@ def _java_to_py(java_method): return getattr(java_val, java_method)() return to_py +def _java_to_py_bigdecimal(): + def to_py(rs, col): + java_val = rs.getObject(col) + if java_val is None: + return + if hasattr(java_val, 'scale'): + scale = java_val.scale() + if scale == 0: + return java_val.longValue() + else: + return java_val.doubleValue() + else: + return float(java_val) + return to_py + _to_double = _java_to_py('doubleValue') _to_int = _java_to_py('intValue') _to_boolean = _java_to_py('booleanValue') +_to_decimal = _java_to_py_bigdecimal() + def _init_types(types_map): global _jdbc_name_to_const _jdbc_name_to_const = types_map @@ -698,8 +715,8 @@ _DEFAULT_CONVERTERS = { 'TIME': _to_time, 'DATE': _to_date, 'BINARY': _to_binary, - 'DECIMAL': _to_double, - 'NUMERIC': _to_double, + 'DECIMAL': _to_decimal, + 'NUMERIC': _to_decimal, 'DOUBLE': _to_double, 'FLOAT': _to_double, 'TINYINT': _to_int, diff --git a/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java b/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java index 6c959fd..7d8b3c2 100644 --- a/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java +++ b/mockdriver/src/main/java/org/jaydebeapi/mockdriver/MockConnection.java @@ -1,6 +1,7 @@ package org.jaydebeapi.mockdriver; import java.lang.reflect.Field; +import java.math.BigDecimal; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; @@ -59,6 +60,38 @@ public abstract class MockConnection implements Connection { Mockito.when(this.prepareStatement(Mockito.anyString())).thenReturn(mockPreparedStatement); } + public final void mockBigDecimalResult(long value, int scale) throws SQLException { + PreparedStatement mockPreparedStatement = Mockito.mock(PreparedStatement.class); + Mockito.when(mockPreparedStatement.execute()).thenReturn(true); + mockResultSet = Mockito.mock(ResultSet.class, "ResultSet(for BigDecimal)"); + Mockito.when(mockPreparedStatement.getResultSet()).thenReturn(mockResultSet); + Mockito.when(mockResultSet.next()).thenReturn(true); + ResultSetMetaData mockMetaData = Mockito.mock(ResultSetMetaData.class); + Mockito.when(mockResultSet.getMetaData()).thenReturn(mockMetaData); + Mockito.when(mockMetaData.getColumnCount()).thenReturn(1); + + BigDecimal columnValue = BigDecimal.valueOf(value, scale); + Mockito.when(mockResultSet.getObject(1)).thenReturn(columnValue); + Mockito.when(mockMetaData.getColumnType(1)).thenReturn(Types.DECIMAL); + Mockito.when(this.prepareStatement(Mockito.anyString())).thenReturn(mockPreparedStatement); + } + + public final void mockDoubleDecimalResult(double value) throws SQLException { + PreparedStatement mockPreparedStatement = Mockito.mock(PreparedStatement.class); + Mockito.when(mockPreparedStatement.execute()).thenReturn(true); + mockResultSet = Mockito.mock(ResultSet.class, "ResultSet(for other)"); + Mockito.when(mockPreparedStatement.getResultSet()).thenReturn(mockResultSet); + Mockito.when(mockResultSet.next()).thenReturn(true); + ResultSetMetaData mockMetaData = Mockito.mock(ResultSetMetaData.class); + Mockito.when(mockResultSet.getMetaData()).thenReturn(mockMetaData); + Mockito.when(mockMetaData.getColumnCount()).thenReturn(1); + + Double columnValue = Double.valueOf(value); + Mockito.when(mockResultSet.getObject(1)).thenReturn(value); + Mockito.when(mockMetaData.getColumnType(1)).thenReturn(Types.DECIMAL); + Mockito.when(this.prepareStatement(Mockito.anyString())).thenReturn(mockPreparedStatement); + } + public final void mockDateResult(int year, int month, int day) throws SQLException { PreparedStatement mockPreparedStatement = Mockito.mock(PreparedStatement.class); Mockito.when(mockPreparedStatement.execute()).thenReturn(true); diff --git a/test/test_mock.py b/test/test_mock.py index 2d44952..61f15de 100644 --- a/test/test_mock.py +++ b/test/test_mock.py @@ -57,6 +57,27 @@ class MockTest(unittest.TestCase): result = cursor.fetchone() self.assertEquals(result[0], "1899-12-31") + def test_decimal_scale_zero(self): + self.conn.jconn.mockBigDecimalResult(12345, 0) + cursor = self.conn.cursor() + cursor.execute("dummy stmt") + result = cursor.fetchone() + self.assertEquals(str(result[0]), "12345") + + def test_decimal_places(self): + self.conn.jconn.mockBigDecimalResult(12345, 1) + cursor = self.conn.cursor() + cursor.execute("dummy stmt") + result = cursor.fetchone() + self.assertEquals(str(result[0]), "1234.5") + + def test_double_decimal(self): + self.conn.jconn.mockDoubleDecimalResult(1234.5) + cursor = self.conn.cursor() + cursor.execute("dummy stmt") + result = cursor.fetchone() + self.assertEquals(str(result[0]), "1234.5") + def test_sql_exception_on_execute(self): self.conn.jconn.mockExceptionOnExecute("java.sql.SQLException", "expected") cursor = self.conn.cursor()