Repository: housepower/ClickHouse-Native-JDBC (java)

Issue:
Serialize/deserialize negative Decimal number  is wrong
Hi,

I extracted codes from `DataTypeDecimal`,  The following is the test codes:

```JAVA
    @Test
    public void testNegativeDecimal() {
        // 
        int scale = 4;
        BigDecimal negative = new BigDecimal("-18.2000");
        BigDecimal scaleFactor = BigDecimal.valueOf(Math.pow(10, scale));
        BigDecimal targetValue = negative.multiply(scaleFactor);
        BigInteger res = targetValue.toBigInteger();

        long l1 = res.longValue();
        long l2 = res.shiftRight(64).longValue();

        BigInteger v1 = new BigInteger(1, longToBytes(l1));
        BigInteger v2 = new BigInteger(1, longToBytes(l2));

        BigDecimal value = new BigDecimal(v1.add(v2.shiftLeft(64)));
        value = value.divide(scaleFactor, scale, RoundingMode.HALF_UP);

        Assert.assertEquals(negative, value);
    }
```

and the result is:

```
java.lang.AssertionError: 
Expected :-18.2000
Actual   :34028236692093846346337460743176802.9456
```


Pull request:
Fix Negative Decimal deser bug
closes #277 

Patch:
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeDecimal.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeDecimal.java
index 3770363..9da028e 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeDecimal.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/data/type/complex/DataTypeDecimal.java
@@ -168,29 +168,25 @@ public class DataTypeDecimal implements IDataType {
             }
 
             case 128: {
-                long l1 = deserializer.readLong();
-                long l2 = deserializer.readLong();
+                long []array = new long[2];
+                array[1] = deserializer.readLong();
+                array[0] = deserializer.readLong();
 
-                BigInteger v1 = new BigInteger(1, BytesUtil.longToBytes(l1));
-                BigInteger v2 = new BigInteger(1, BytesUtil.longToBytes(l2));
-
-                value = new BigDecimal(v1.add(v2.shiftLeft(64)));
+                BigInteger v1 = new BigInteger(BytesUtil.longsToBytes(array));
+                value = new BigDecimal(v1);
                 value = value.divide(scaleFactor, scale, RoundingMode.HALF_UP);
                 break;
             }
 
             case 256: {
-                long l1 = deserializer.readLong();
-                long l2 = deserializer.readLong();
-                long l3 = deserializer.readLong();
-                long l4 = deserializer.readLong();
-
-                BigInteger v1 = new BigInteger(1, BytesUtil.longToBytes(l1));
-                BigInteger v2 = new BigInteger(1, BytesUtil.longToBytes(l2));
-                BigInteger v3 = new BigInteger(1, BytesUtil.longToBytes(l3));
-                BigInteger v4 = new BigInteger(1, BytesUtil.longToBytes(l4));
-
-                value = new BigDecimal(v1.add(v2.shiftLeft(64)).add(v3.shiftLeft(64 * 2)).add(v4.shiftLeft(64 * 3)));
+                long []array = new long[4];
+                array[3] = deserializer.readLong();
+                array[2] = deserializer.readLong();
+                array[1] = deserializer.readLong();
+                array[0] = deserializer.readLong();
+
+                BigInteger v1 = new BigInteger(BytesUtil.longsToBytes(array));
+                value = new BigDecimal(v1);
                 value = value.divide(scaleFactor, scale, RoundingMode.HALF_UP);
                 break;
             }
diff --git a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/misc/BytesUtil.java b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/misc/BytesUtil.java
index c370a04..b2bac8c 100644
--- a/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/misc/BytesUtil.java
+++ b/clickhouse-native-jdbc/src/main/java/com/github/housepower/jdbc/misc/BytesUtil.java
@@ -25,6 +25,20 @@ public class BytesUtil {
         return result;
     }
 
+
+    public static byte[] longsToBytes(final long []longArray) {
+        byte[] result = new byte[Long.BYTES * longArray.length];
+        for (int index = 0; index < longArray.length; index++) {
+            long l = longArray[index];
+
+            for (int i = Long.BYTES - 1; i >= 0; i--) {
+                result[index * Long.BYTES + i] = (byte) (l & 0xFF);
+                l >>= Byte.SIZE;
+            }
+        }
+        return result;
+    }
+
     public static long bytesToLong(final byte[] b) {
         long result = 0;
         for (int i = 0; i < Long.BYTES; i++) {


Test patch:
diff --git a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/DecimalTypeITest.java b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/DecimalTypeITest.java
index a31712b..c540646 100644
--- a/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/DecimalTypeITest.java
+++ b/clickhouse-native-jdbc/src/test/java/com/github/housepower/jdbc/DecimalTypeITest.java
@@ -17,12 +17,17 @@ package com.github.housepower.jdbc;
 import org.junit.jupiter.api.Test;
 
 import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
 import java.math.RoundingMode;
-import java.sql.*;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
 
 import joptsimple.internal.Strings;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static com.github.housepower.jdbc.misc.BytesUtil.longsToBytes;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class DecimalTypeITest extends AbstractITest {
 
@@ -40,6 +45,9 @@ public class DecimalTypeITest extends AbstractITest {
             BigDecimal value256 = new BigDecimal(Strings.repeat('1', (76 - 26)));
             value256 = value256.setScale(26, RoundingMode.HALF_UP);
 
+            BigDecimal value256Neg = new BigDecimal("-1" + Strings.repeat('1', (76 - 26)));
+            value256Neg = value256Neg.setScale(26, RoundingMode.HALF_UP);
+
             BigDecimal[] valueArray = new BigDecimal[]{
                     BigDecimal.valueOf(412341.21D).setScale(3, RoundingMode.HALF_UP),
                     BigDecimal.valueOf(512341.25D).setScale(3, RoundingMode.HALF_UP)
@@ -50,16 +58,18 @@ public class DecimalTypeITest extends AbstractITest {
                               + "value64 Decimal(15,5), "
                               + "value128 Decimal(38, 16),"
                               + "value256 Decimal(76, 26),"
+                              + "value256_neg Decimal(76, 26),"
                               + "value_array Array(Decimal(5,3))) Engine=Memory()");
             PreparedStatement pstmt = connection.prepareStatement("INSERT INTO decimal_test"
-                                                                  + "(value32,value64,value128,value256,value_array) "
-                                                                  + "values(?,?,?,?,?);");
+                                                                  + "(value32,value64,value128,value256,value256_neg,value_array) "
+                                                                  + "values(?,?,?,?,?,?);");
             for (int i = 0; i < 300; i++) {
                 pstmt.setBigDecimal(1, value32);
                 pstmt.setBigDecimal(2, value64);
                 pstmt.setBigDecimal(3, value128);
                 pstmt.setBigDecimal(4, value256);
-                pstmt.setArray(5, connection.createArrayOf("Decimal(5,3)", valueArray));
+                pstmt.setBigDecimal(5, value256Neg);
+                pstmt.setArray(6, connection.createArrayOf("Decimal(5,3)", valueArray));
                 pstmt.addBatch();
             }
             pstmt.executeBatch();
@@ -77,7 +87,10 @@ public class DecimalTypeITest extends AbstractITest {
                 assertEquals(value128, rsValue128);
                 BigDecimal rsValue256 = rs.getBigDecimal(4);
                 assertEquals(value256, rsValue256);
-                ClickHouseArray rsValueArray = (ClickHouseArray) rs.getArray(5);
+                BigDecimal rsValue256Neg = rs.getBigDecimal(5);
+                assertEquals(value256Neg, rsValue256Neg);
+
+                ClickHouseArray rsValueArray = (ClickHouseArray) rs.getArray(6);
                 Object[] decimalArray = (Object[]) rsValueArray.getArray();
                 assertEquals(decimalArray.length, valueArray.length);
                 for (int i = 0; i < valueArray.length; i++) {
@@ -89,4 +102,24 @@ public class DecimalTypeITest extends AbstractITest {
         }, "allow_experimental_bigint_types", 1);
     }
 
+
+    @Test
+    public void testNegativeDecimal() {
+        int scale = 4;
+        BigDecimal negative = new BigDecimal("-18.2000", MathContext.DECIMAL128);
+        BigDecimal scaleFactor = BigDecimal.valueOf(Math.pow(10, scale));
+        BigDecimal targetValue = negative.multiply(scaleFactor);
+        BigInteger res = targetValue.toBigInteger();
+
+        long l1 = res.longValue();
+        long l2 = res.shiftRight(64).longValue();
+
+        // v =  -1820000
+        long []arr = new long[]{l2, l1};
+        BigInteger v = new BigInteger(longsToBytes(arr));
+        BigDecimal value = new BigDecimal(v);
+        value = value.divide(scaleFactor, scale, RoundingMode.HALF_UP);
+
+        assertEquals(negative, value);
+    }
 }