You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ha...@apache.org on 2022/04/27 09:08:11 UTC

[iotdb] branch rel/0.13 updated: [To rel/0.13][IOTDB-2753] Insert a time series with a null value and report 500 (#5688)

This is an automated email from the ASF dual-hosted git repository.

haonan pushed a commit to branch rel/0.13
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/rel/0.13 by this push:
     new c946b064a8 [To rel/0.13][IOTDB-2753] Insert a time series with a null value and report 500 (#5688)
c946b064a8 is described below

commit c946b064a81818b976a2ea900574cfcb9b71bc83
Author: Alan Choo <43...@users.noreply.github.com>
AuthorDate: Wed Apr 27 17:08:05 2022 +0800

    [To rel/0.13][IOTDB-2753] Insert a time series with a null value and report 500 (#5688)
---
 .../iotdb/db/integration/IoTDBInsertNullIT.java    | 207 +++++++++++++++++++++
 .../iotdb/db/engine/memtable/AbstractMemTable.java |  23 ++-
 2 files changed, 222 insertions(+), 8 deletions(-)

diff --git a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBInsertNullIT.java b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBInsertNullIT.java
new file mode 100644
index 0000000000..7ef0f01595
--- /dev/null
+++ b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBInsertNullIT.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.integration;
+
+import org.apache.iotdb.integration.env.EnvFactory;
+import org.apache.iotdb.itbase.category.ClusterTest;
+import org.apache.iotdb.itbase.category.LocalStandaloneTest;
+import org.apache.iotdb.itbase.category.RemoteTest;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.junit.Assert.fail;
+
+@Category({LocalStandaloneTest.class, ClusterTest.class, RemoteTest.class})
+public class IoTDBInsertNullIT {
+  private static final List<String> sqls = new ArrayList<>();
+  private static Connection connection;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initBeforeClass();
+    initCreateSQLStatement();
+    insertData();
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    close();
+    EnvFactory.getEnv().cleanAfterClass();
+  }
+
+  private static void close() {
+    if (Objects.nonNull(connection)) {
+      try {
+        connection.close();
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private static void initCreateSQLStatement() {
+    sqls.add("SET STORAGE GROUP TO root.sg");
+    sqls.add("CREATE TIMESERIES root.sg.d1.s1 WITH DATATYPE=BOOLEAN");
+    sqls.add("CREATE TIMESERIES root.sg.d1.s2 WITH DATATYPE=FLOAT");
+    sqls.add("CREATE TIMESERIES root.sg.d1.s3 WITH DATATYPE=INT32");
+    sqls.add("CREATE ALIGNED TIMESERIES root.sg.d2(s1 BOOLEAN,s2 FLOAT,s3 INT32)");
+  }
+
+  private static void insertData() throws SQLException {
+    connection = EnvFactory.getEnv().getConnection();
+    Statement statement = connection.createStatement();
+
+    for (String sql : sqls) {
+      statement.execute(sql);
+    }
+
+    statement.close();
+  }
+
+  @Test
+  public void testInsertNull() {
+    String[] retArray =
+        new String[] {
+          "1,null,1.0,1,", "2,true,null,2,", "3,true,3.0,null,",
+        };
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("insert into root.sg.d1(time,s1,s2,s3) values(1,null,1.0,1)");
+      statement.execute("insert into root.sg.d1(time,s1,s2,s3) values(2,true,null,2)");
+      statement.execute("insert into root.sg.d1(time,s1,s2,s3) values(3,true,3.0,null)");
+
+      boolean hasResultSet = statement.execute("select * from root.sg.d1");
+      Assert.assertTrue(hasResultSet);
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        List<Integer> actualIndexToExpectedIndexList =
+            checkHeader(
+                resultSetMetaData,
+                "Time,root.sg.d1.s1,root.sg.d1.s2,root.sg.d1.s3",
+                new int[] {
+                  Types.TIMESTAMP, Types.BOOLEAN, Types.FLOAT, Types.INTEGER,
+                });
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          String[] expectedStrings = retArray[cnt].split(",");
+          StringBuilder expectedBuilder = new StringBuilder();
+          StringBuilder actualBuilder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            actualBuilder.append(resultSet.getString(i)).append(",");
+            expectedBuilder
+                .append(expectedStrings[actualIndexToExpectedIndexList.get(i - 1)])
+                .append(",");
+          }
+          Assert.assertEquals(expectedBuilder.toString(), actualBuilder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(3, cnt);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testInsertAlignedNull() {
+    String[] retArray =
+        new String[] {
+          "1,null,1.0,1,", "2,true,null,2,", "3,true,3.0,null,",
+        };
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("insert into root.sg.d2(time,s1,s2,s3) aligned values(1,null,1.0,1)");
+      statement.execute("insert into root.sg.d2(time,s1,s2,s3) aligned values(2,true,null,2)");
+      statement.execute("insert into root.sg.d2(time,s1,s2,s3) aligned values(3,true,3.0,null)");
+
+      boolean hasResultSet = statement.execute("select * from root.sg.d2");
+      Assert.assertTrue(hasResultSet);
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        List<Integer> actualIndexToExpectedIndexList =
+            checkHeader(
+                resultSetMetaData,
+                "Time,root.sg.d2.s1,root.sg.d2.s2,root.sg.d2.s3",
+                new int[] {
+                  Types.TIMESTAMP, Types.BOOLEAN, Types.FLOAT, Types.INTEGER,
+                });
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          String[] expectedStrings = retArray[cnt].split(",");
+          StringBuilder expectedBuilder = new StringBuilder();
+          StringBuilder actualBuilder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            actualBuilder.append(resultSet.getString(i)).append(",");
+            expectedBuilder
+                .append(expectedStrings[actualIndexToExpectedIndexList.get(i - 1)])
+                .append(",");
+          }
+          Assert.assertEquals(expectedBuilder.toString(), actualBuilder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(3, cnt);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private List<Integer> checkHeader(
+      ResultSetMetaData resultSetMetaData, String expectedHeaderStrings, int[] expectedTypes)
+      throws SQLException {
+    String[] expectedHeaders = expectedHeaderStrings.split(",");
+    Map<String, Integer> expectedHeaderToTypeIndexMap = new HashMap<>();
+    for (int i = 0; i < expectedHeaders.length; ++i) {
+      expectedHeaderToTypeIndexMap.put(expectedHeaders[i], i);
+    }
+
+    List<Integer> actualIndexToExpectedIndexList = new ArrayList<>();
+    for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+      Integer typeIndex = expectedHeaderToTypeIndexMap.get(resultSetMetaData.getColumnName(i));
+      Assert.assertNotNull(typeIndex);
+      Assert.assertEquals(expectedTypes[typeIndex], resultSetMetaData.getColumnType(i));
+      actualIndexToExpectedIndexList.add(typeIndex);
+    }
+    return actualIndexToExpectedIndexList;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/memtable/AbstractMemTable.java b/server/src/main/java/org/apache/iotdb/db/engine/memtable/AbstractMemTable.java
index 06706b1361..675eee73a3 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/memtable/AbstractMemTable.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/memtable/AbstractMemTable.java
@@ -152,10 +152,17 @@ public abstract class AbstractMemTable implements IMemTable {
 
     List<IMeasurementSchema> schemaList = new ArrayList<>();
     List<TSDataType> dataTypes = new ArrayList<>();
+    int nullPointsNumber = 0;
     for (int i = 0; i < insertRowPlan.getMeasurements().length; i++) {
+      // use measurements[i] to ignore failed partial insert
       if (measurements[i] == null) {
         continue;
       }
+      // use values[i] to ignore null value
+      if (values[i] == null) {
+        nullPointsNumber++;
+        continue;
+      }
       IMeasurementSchema schema = insertRowPlan.getMeasurementMNodes()[i].getSchema();
       schemaList.add(schema);
       dataTypes.add(schema.getType());
@@ -164,7 +171,9 @@ public abstract class AbstractMemTable implements IMemTable {
     write(insertRowPlan.getDeviceID(), schemaList, insertRowPlan.getTime(), values);
 
     int pointsInserted =
-        insertRowPlan.getMeasurements().length - insertRowPlan.getFailedMeasurementNumber();
+        insertRowPlan.getMeasurements().length
+            - insertRowPlan.getFailedMeasurementNumber()
+            - nullPointsNumber;
 
     totalPointsNum += pointsInserted;
 
@@ -190,9 +199,12 @@ public abstract class AbstractMemTable implements IMemTable {
 
     updatePlanIndexes(insertRowPlan.getIndex());
     String[] measurements = insertRowPlan.getMeasurements();
+    Object[] values = insertRowPlan.getValues();
+
     List<IMeasurementSchema> schemaList = new ArrayList<>();
     List<TSDataType> dataTypes = new ArrayList<>();
     for (int i = 0; i < insertRowPlan.getMeasurements().length; i++) {
+      // use measurements[i] to ignore failed partial insert
       if (measurements[i] == null) {
         continue;
       }
@@ -203,13 +215,8 @@ public abstract class AbstractMemTable implements IMemTable {
     if (schemaList.isEmpty()) {
       return;
     }
-    memSize +=
-        MemUtils.getAlignedRecordsSize(dataTypes, insertRowPlan.getValues(), disableMemControl);
-    writeAlignedRow(
-        insertRowPlan.getDeviceID(),
-        schemaList,
-        insertRowPlan.getTime(),
-        insertRowPlan.getValues());
+    memSize += MemUtils.getAlignedRecordsSize(dataTypes, values, disableMemControl);
+    writeAlignedRow(insertRowPlan.getDeviceID(), schemaList, insertRowPlan.getTime(), values);
     int pointsInserted =
         insertRowPlan.getMeasurements().length - insertRowPlan.getFailedMeasurementNumber();
     totalPointsNum += pointsInserted;