[CALCITE-4752] PreparedStatement#SetObject() fails for BigDecimal values
diff --git a/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java b/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
index 0f00fd2..4c5ed11 100644
--- a/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
+++ b/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
@@ -26,6 +26,7 @@
import com.google.protobuf.Descriptors.FieldDescriptor;
import java.lang.reflect.Type;
+import java.math.BigDecimal;
import java.sql.Array;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
@@ -341,7 +342,7 @@
/** Values are represented as some sub-class of {@link Number}.
* The JSON encoding does this. */
- NUMBER(Number.class, Types.NUMERIC),
+ NUMBER(BigDecimal.class, Types.NUMERIC),
ARRAY(Array.class, Types.ARRAY),
MULTISET(List.class, Types.JAVA_OBJECT),
diff --git a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
index bdb7d5f..6b1858e 100644
--- a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
+++ b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
@@ -118,7 +118,7 @@
case Types.DOUBLE:
return new DoubleAccessor(getter);
case Types.DECIMAL:
- return new BigDecimalAccessor(getter);
+ return new NumberAccessor(getter, columnMetaData.scale);
case Types.CHAR:
switch (columnMetaData.type.rep) {
case PRIMITIVE_CHAR:
@@ -681,30 +681,9 @@
}
/**
- * Accessor that assumes that the underlying value is a {@link BigDecimal};
- * corresponds to {@link java.sql.Types#DECIMAL}.
- */
- private static class BigDecimalAccessor extends BigNumberAccessor {
- private BigDecimalAccessor(Getter getter) {
- super(getter);
- }
-
- protected Number getNumber() throws SQLException {
- return (Number) getObject();
- }
-
- public BigDecimal getBigDecimal(int scale) throws SQLException {
- return (BigDecimal) getObject();
- }
-
- public BigDecimal getBigDecimal() throws SQLException {
- return (BigDecimal) getObject();
- }
- }
-
- /**
* Accessor that assumes that the underlying value is a {@link Number};
- * corresponds to {@link java.sql.Types#NUMERIC}.
+ * corresponds to {@link java.sql.Types#NUMERIC}
+ * or {@link java.sql.Types#DECIMAL}.
*
* <p>This is useful when numbers have been translated over JSON. JSON
* converts a 0L (0 long) value to the string "0" and back to 0 (0 int).
@@ -722,14 +701,20 @@
return (Number) super.getObject();
}
+ //FIXME There are several issues with this, the code below simply implements
+ //a previous behaviour codified by the Calcite test suite.
+ //
+ // 1. It interprets a scale of 0 as a NOOP parameter, it should in fact drop all fractionals
+ // 2. The scale from MetaData is NOT applied to BigDecimal values. Why ?
+ // 3. Metadata scale is only applied for getBigDecimal(), and only in this Accessor. Why ?
public BigDecimal getBigDecimal(int scale) throws SQLException {
Number n = getNumber();
if (n == null) {
return null;
}
BigDecimal decimal = AvaticaSite.toBigDecimal(n);
- if (0 != scale) {
- return decimal.setScale(scale, RoundingMode.UNNECESSARY);
+ if (0 != scale && !(n instanceof BigDecimal)) {
+ return decimal.setScale(scale, RoundingMode.DOWN);
}
return decimal;
}
@@ -737,6 +722,7 @@
public BigDecimal getBigDecimal() throws SQLException {
return getBigDecimal(scale);
}
+
}
/**
diff --git a/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java b/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
index c35e5a1..be657f0 100644
--- a/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
@@ -47,6 +47,7 @@
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
@@ -59,6 +60,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.sql.Types;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -734,6 +736,65 @@
ConnectionSpec.getDatabaseLock().unlock();
}
}
+
+ @Test public void testBigDecimalTest() throws Exception {
+ final String tableName = "testbigdecimal";
+ try (Connection conn = DriverManager.getConnection(url);
+ Statement stmt = conn.createStatement()) {
+ conn.setAutoCommit(false);
+ stmt.execute("DROP TABLE IF EXISTS " + tableName);
+ stmt.execute("CREATE TABLE " + tableName + " ("
+ + "pk VARCHAR NOT NULL PRIMARY KEY, "
+ + "v1 DECIMAL(10,5))");
+ conn.commit();
+ try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO "
+ + tableName + " values(?, ?)")) {
+ pstmt.setString(1, "1");
+ pstmt.setBigDecimal(2, new BigDecimal("12345.67890"));
+ assertEquals(1, pstmt.executeUpdate());
+
+ pstmt.setString(1, "2");
+ pstmt.setObject(2, new BigDecimal("12345.67891"));
+ assertEquals(1, pstmt.executeUpdate());
+
+ pstmt.setString(1, "3");
+ pstmt.setObject(2, new BigDecimal("12345.67892"), Types.NUMERIC);
+ assertEquals(1, pstmt.executeUpdate());
+
+ pstmt.setString(1, "4");
+ pstmt.setObject(2, new BigDecimal("4000"), Types.DECIMAL);
+ assertEquals(1, pstmt.executeUpdate());
+
+ pstmt.setString(1, "5");
+ pstmt.setLong(2, 4000L);
+ assertEquals(1, pstmt.executeUpdate());
+
+ conn.commit();
+
+ ResultSet rs = stmt.executeQuery("select v1 from " + tableName + " order by pk asc");
+ assertTrue(rs.next());
+ assertTrue((new BigDecimal("12345.67890")).compareTo(rs.getBigDecimal(1)) == 0);
+ assertEquals("12345.67890", rs.getString(1));
+ assertTrue(rs.next());
+ assertEquals(rs.getObject(1).getClass(), BigDecimal.class);
+ assertTrue((new BigDecimal("12345.67891")).compareTo((BigDecimal) rs.getObject(1)) == 0);
+ // Not implemeneted / throws error
+ //assertTrue((new BigDecimal("12345.67891")).compareTo(
+ // (BigDecimal)rs.getObject(1, BigDecimal.class)) == 0);
+
+ assertTrue(rs.next());
+ // The build system makes it impossible to test deprecated APIs, but these also
+ // fail bacase of RoundingMode.Unneccessary in AbstractCursor#NumberAccessor.:
+ //assertTrue((new BigDecimal("12345.679")).compareTo(rs.getBigDecimal(1, 3)) == 0);
+ //assertTrue((new BigDecimal("12345.6789200")).compareTo(rs.getBigDecimal(1, 7)) == 0);
+
+ assertTrue(rs.next());
+ assertEquals("4000.00000", rs.getString(1));
+ assertEquals(4000L, rs.getLong(1));
+ assertEquals(4000, rs.getInt(1));
+ }
+ }
+ }
}
// End RemoteMetaTest.java