databend icon indicating copy to clipboard operation
databend copied to clipboard

bug: Insert and select Double INF value cause error

Open hantmac opened this issue 1 year ago • 4 comments

Search before asking

  • [X] I had searched in the issues and found no similar issues.

Version

Databend Query v1.2.598-nightly-812688f7b6(rust-1.81.0-nightly-2024-08-05T22:07:38.113740317Z)

What's Wrong?

Using java insert into the Double.POSITIVE_INFINITY value into table and get an error sometime when select from it.

How to Reproduce?

 public void testWriteDouble() throws SQLException {
        try (Connection connection = createConnection()) {
            DatabendStatement statement = (DatabendStatement) connection.createStatement();
            statement.execute("CREATE TABLE IF NOT EXISTS test_basic_driver.table_double (\n" +
                    "    ID INT,\n" +
                    "    Name VARCHAR(50),\n" +
                    "    Age INT,\n" +
                    "    City VARCHAR(50),\n" +
                    "    Score DOUBLE\n" +
                    ");");
            Double infDouble = Double.POSITIVE_INFINITY;

            String sql = "INSERT INTO test_basic_driver.table_double (ID, Name, Age, City, Score) values";
            PreparedStatement prepareStatement = connection.prepareStatement(sql);
            prepareStatement.setInt(1, 1);
            prepareStatement.setString(2, "Alice");
            prepareStatement.setInt(3, 25);
            prepareStatement.setString(4, "Toronto");
            prepareStatement.setDouble(5, infDouble);

            prepareStatement.addBatch();
            prepareStatement.executeBatch();
            statement.execute("SELECT * FROM test_basic_driver.table_double");
            ResultSet r = statement.getResultSet();
            r.next();
            Assert.assertEquals(r.getDouble(5), "Infinity");
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

The above code get error:

java.sql.SQLException: Error executing query: SQL: SELECT * FROM test_basic_driver.table_double Query failed: QueryErrors{code=1006, message=Cannot parse request for /actions/init_query_fragments, cause: Error("invalid type: null, expected f64", line: 1, column: 3444)} cause: null
	at com.databend.jdbc.DatabendStatement.internalExecute(DatabendStatement.java:215)
	at com.databend.jdbc.DatabendStatement.execute(DatabendStatement.java:154)
	at com.databend.jdbc.TestBasicDriver.testWriteDouble(TestBasicDriver.java:200)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:104)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:645)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:851)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1177)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
	at org.testng.TestRunner.privateRun(TestRunner.java:756)
	at org.testng.TestRunner.run(TestRunner.java:610)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:387)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:382)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:340)
	at org.testng.SuiteRunner.run(SuiteRunner.java:289)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1293)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1218)
	at org.testng.TestNG.runSuites(TestNG.java:1133)
	at org.testng.TestNG.run(TestNG.java:1104)
	at com.intellij.rt.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:66)
	at com.intellij.rt.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:109)
Caused by: java.lang.RuntimeException: Query failed: QueryErrors{code=1006, message=Cannot parse request for /actions/init_query_fragments, cause: Error("invalid type: null, expected f64", line: 1, column: 3444)}
	at com.databend.client.DatabendClientV1.executeInternal(DatabendClientV1.java:181)
	at com.databend.client.DatabendClientV1.execute(DatabendClientV1.java:202)
	at com.databend.client.DatabendClientV1.<init>(DatabendClientV1.java:91)
	at com.databend.jdbc.DatabendConnection.startQuery(DatabendConnection.java:614)
	at com.databend.jdbc.DatabendStatement.internalExecute(DatabendStatement.java:186)

which is QueryErrors{code=1006, message=Cannot parse request for /actions/init_query_fragments, cause: Error("invalid type: null, expected f64", line: 1, column: 3444)}

Are you willing to submit PR?

  • [ ] Yes I am willing to submit a PR!

hantmac avatar Aug 08 '24 08:08 hantmac

After investigating, I found that the likely root cause is that the fragment we sent has a field ColumnStatistics and min max field in ColumnStatistics could be f64::INFINITY.

However, f64::INFINITY cannot be correctly serialized and deserialized. When f64::INFINITY is serialized, it becomes 'null', and attempting to deserialize this results in an error: "invalid type: null, expected f64". Below is a minimal method to reproduce this issue:

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Dummy{
    test: f64
}

fn main(){
    let dummy = Dummy{test: f64::INFINITY};

    let serialized = serde_json::to_string(&dummy).unwrap();
    let deserialized: Dummy = serde_json::from_str(&serialized).unwrap();
    println!("{:?}",deserialized);
  }
called `Result::unwrap()` on an `Err` value: Error("invalid type: null, expected f64", line: 1, column: 12)

dqhl76 avatar Aug 13 '24 15:08 dqhl76

https://github.com/serde-rs/json/issues/202

sundy-li avatar Aug 15 '24 03:08 sundy-li

ColumnStatistics and min max field in ColumnStatistics could be f64::INFINITY.

We are using F64, which is type F64 = OrderedFloat<f64>;, so there is no f64 in ColumnStatistics

sundy-li avatar Aug 15 '24 03:08 sundy-li

ColumnStatistics and min max field in ColumnStatistics could be f64::INFINITY.

We are using F64, which is type F64 = OrderedFloat<f64>;, so there is no f64 in ColumnStatistics

image

I think OrderedFloat just use f64's serialize and deserialize method

dqhl76 avatar Aug 15 '24 03:08 dqhl76