Skip to content

Commit 7a8079d

Browse files
authored
Table: Ignore null attribute values in insert (#17790)
1 parent 29d0d51 commit 7a8079d

5 files changed

Lines changed: 185 additions & 9 deletions

File tree

integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBInsertTableIT.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,41 @@ public void testInsertSingleColumn() throws SQLException, InterruptedException {
10181018
}
10191019
}
10201020

1021+
@Test
1022+
public void testInsertNullAttributeDoesNotOverwriteDeviceAttribute() throws SQLException {
1023+
try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
1024+
Statement statement = connection.createStatement()) {
1025+
statement.execute("use \"test\"");
1026+
statement.execute(
1027+
"create table attr_null_insert(tag1 string tag, attr1 string attribute, attr2 string attribute, s1 int32 field)");
1028+
1029+
statement.execute(
1030+
"insert into attr_null_insert(time, tag1, attr1, attr2, s1) values(1, 'd1', 'old1', 'old2', 1)");
1031+
statement.execute(
1032+
"insert into attr_null_insert(time, tag1, attr1, attr2, s1) values(2, 'd1', null, null, 2)");
1033+
assertDeviceAttributes(statement, "old1", "old2");
1034+
1035+
statement.execute(
1036+
"insert into attr_null_insert(time, tag1, attr1, attr2, s1) values(3, 'd1', null, 'new2', 3)");
1037+
assertDeviceAttributes(statement, "old1", "new2");
1038+
1039+
statement.execute("update attr_null_insert set attr1 = null where tag1 = 'd1'");
1040+
assertDeviceAttributes(statement, null, "new2");
1041+
}
1042+
}
1043+
1044+
private void assertDeviceAttributes(
1045+
final Statement statement, final String expectedAttr1, final String expectedAttr2)
1046+
throws SQLException {
1047+
try (final ResultSet resultSet = statement.executeQuery("show devices from attr_null_insert")) {
1048+
assertTrue(resultSet.next());
1049+
assertEquals("d1", resultSet.getString("tag1"));
1050+
assertEquals(expectedAttr1, resultSet.getString("attr1"));
1051+
assertEquals(expectedAttr2, resultSet.getString("attr2"));
1052+
assertFalse(resultSet.next());
1053+
}
1054+
}
1055+
10211056
@Test
10221057
public void testInsertWithTTL() {
10231058
try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableDeviceSchemaValidator.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import org.apache.tsfile.file.metadata.IDeviceID;
3838
import org.apache.tsfile.utils.Binary;
39+
import org.apache.tsfile.utils.Constants;
3940
import org.slf4j.Logger;
4041
import org.slf4j.LoggerFactory;
4142

@@ -74,7 +75,9 @@ public void validateDeviceSchema(
7475
// High-cost operations, shall only be called once
7576
final List<Object[]> deviceIdList = schemaValidation.getDeviceIdList();
7677
final List<String> attributeKeyList = schemaValidation.getAttributeColumnNameList();
77-
final List<Object[]> attributeValueList = schemaValidation.getAttributeValueList();
78+
// In normal inserts, null attributes mean no-op. Raw null is reserved for explicit clears.
79+
final List<Object[]> attributeValueList =
80+
normalizeNullAttributeValuesForInsert(schemaValidation.getAttributeValueList());
7881

7982
if (LOGGER.isDebugEnabled()) {
8083
LOGGER.debug(
@@ -194,13 +197,52 @@ static boolean isAttributeUpdateRequired(
194197
for (int j = 0, size = attributeKeyList.size(); j < size; j++) {
195198
final Object inputValue =
196199
deviceAttributeValueList.length > j ? deviceAttributeValueList[j] : null;
200+
if (inputValue == null || inputValue == Constants.NONE) {
201+
continue;
202+
}
197203
if (!Objects.equals(attributeMap.get(attributeKeyList.get(j)), inputValue)) {
198204
return true;
199205
}
200206
}
201207
return false;
202208
}
203209

210+
private List<Object[]> normalizeNullAttributeValuesForInsert(
211+
final List<Object[]> attributeValueList) {
212+
List<Object[]> result = null;
213+
for (int i = 0; i < attributeValueList.size(); i++) {
214+
final Object[] deviceAttributeValueList = attributeValueList.get(i);
215+
final Object[] normalizedAttributeValueList =
216+
normalizeNullAttributeValuesForInsert(deviceAttributeValueList);
217+
if (result != null) {
218+
result.add(normalizedAttributeValueList);
219+
} else if (normalizedAttributeValueList != deviceAttributeValueList) {
220+
result = new ArrayList<>(attributeValueList.size());
221+
for (int j = 0; j < i; j++) {
222+
result.add(attributeValueList.get(j));
223+
}
224+
result.add(normalizedAttributeValueList);
225+
}
226+
}
227+
return result == null ? attributeValueList : result;
228+
}
229+
230+
private Object[] normalizeNullAttributeValuesForInsert(final Object[] deviceAttributeValueList) {
231+
for (int i = 0; i < deviceAttributeValueList.length; i++) {
232+
if (deviceAttributeValueList[i] == null) {
233+
final Object[] result =
234+
Arrays.copyOf(deviceAttributeValueList, deviceAttributeValueList.length);
235+
for (int j = i; j < result.length; j++) {
236+
if (result[j] == null) {
237+
result[j] = Constants.NONE;
238+
}
239+
}
240+
return result;
241+
}
242+
}
243+
return deviceAttributeValueList;
244+
}
245+
204246
private void autoCreateOrUpdateDeviceSchema(
205247
final ITableDeviceSchemaValidation schemaValidation,
206248
final ValidateResult previousValidateResult,

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,27 @@ public List<String> getAttributeColumnNameList() {
9090
public List<Object[]> getAttributeValueList() {
9191
prepareDeviceID2LastIdxMap();
9292
final InsertTabletStatement insertTabletStatement = getInnerTreeStatement();
93-
List<Object[]> result = new ArrayList<>(insertTabletStatement.getRowCount());
9493
final List<Integer> attrColumnIndices = insertTabletStatement.getAttrColumnIndices();
95-
for (Integer rowIndex : deviceID2LastIdxMap.values()) {
96-
Object[] attrValues = new Object[attrColumnIndices.size()];
94+
95+
final Map<IDeviceID, Object[]> deviceID2AttributeValues =
96+
new LinkedHashMap<>(deviceID2LastIdxMap.size());
97+
for (final IDeviceID deviceID : deviceID2LastIdxMap.keySet()) {
98+
deviceID2AttributeValues.put(deviceID, new Object[attrColumnIndices.size()]);
99+
}
100+
101+
for (int rowIndex = 0; rowIndex < insertTabletStatement.getRowCount(); rowIndex++) {
102+
final Object[] attrValues =
103+
deviceID2AttributeValues.get(insertTabletStatement.getTableDeviceID(rowIndex));
97104
for (int attrColNum = 0; attrColNum < attrColumnIndices.size(); attrColNum++) {
98105
final int columnIndex = attrColumnIndices.get(attrColNum);
99106
if (!insertTabletStatement.isNull(rowIndex, columnIndex)) {
100107
attrValues[attrColNum] =
101108
((Object[]) insertTabletStatement.getColumns()[columnIndex])[rowIndex];
102109
}
103110
}
104-
result.add(attrValues);
105111
}
106-
return result;
112+
113+
return new ArrayList<>(deviceID2AttributeValues.values());
107114
}
108115

109116
// The map cannot be maintained during construction because the IDeviceID may be reset later.

iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableDeviceSchemaValidatorTest.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher;
2121

2222
import org.apache.tsfile.utils.Binary;
23+
import org.apache.tsfile.utils.Constants;
2324
import org.junit.Assert;
2425
import org.junit.Test;
2526

@@ -32,17 +33,39 @@
3233
public class TableDeviceSchemaValidatorTest {
3334

3435
@Test
35-
public void testNullAttributeNeedsUpdate() {
36+
public void testNullAttributeSkipsUpdate() {
3637
final Map<String, Binary> attributeMap = new HashMap<>();
3738
attributeMap.put("attr", new Binary("x", StandardCharsets.UTF_8));
3839

39-
Assert.assertTrue(
40+
Assert.assertFalse(
4041
TableDeviceSchemaValidator.isAttributeUpdateRequired(
4142
Collections.singletonList("attr"), new Object[] {null}, attributeMap));
4243
}
4344

4445
@Test
45-
public void testMissingTailAttributeValueEqualsNull() {
46+
public void testNoneAttributeSkipsUpdate() {
47+
final Map<String, Binary> attributeMap = new HashMap<>();
48+
attributeMap.put("attr", new Binary("x", StandardCharsets.UTF_8));
49+
50+
Assert.assertFalse(
51+
TableDeviceSchemaValidator.isAttributeUpdateRequired(
52+
Collections.singletonList("attr"), new Object[] {Constants.NONE}, attributeMap));
53+
}
54+
55+
@Test
56+
public void testNonNullAttributeNeedsUpdate() {
57+
final Map<String, Binary> attributeMap = new HashMap<>();
58+
attributeMap.put("attr", new Binary("x", StandardCharsets.UTF_8));
59+
60+
Assert.assertTrue(
61+
TableDeviceSchemaValidator.isAttributeUpdateRequired(
62+
Collections.singletonList("attr"),
63+
new Object[] {new Binary("y", StandardCharsets.UTF_8)},
64+
attributeMap));
65+
}
66+
67+
@Test
68+
public void testMissingTailAttributeValueSkipsUpdate() {
4669
final Map<String, Binary> attributeMap = new HashMap<>();
4770
attributeMap.put("attr1", new Binary("x", StandardCharsets.UTF_8));
4871

iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTabletTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,73 @@ public void testWithNull() {
127127
assertArrayEquals(new Object[] {"id3_1"}, deviceIdList.get(2));
128128
assertArrayEquals(new Object[] {}, deviceIdList.get(3));
129129
}
130+
131+
@Test
132+
public void testDuplicateDeviceUsesLastNonNullAttributeValue() {
133+
InsertTabletStatement innerStmt = new InsertTabletStatement();
134+
innerStmt.setDevicePath(new PartialPath("table1", false));
135+
innerStmt.setTimes(new long[] {1, 2, 3, 4});
136+
innerStmt.setRowCount(4);
137+
innerStmt.setMeasurements(new String[] {"deviceId", "attr1", "attr2", "measurement"});
138+
innerStmt.setColumnCategories(
139+
new TsTableColumnCategory[] {
140+
TsTableColumnCategory.TAG,
141+
TsTableColumnCategory.ATTRIBUTE,
142+
TsTableColumnCategory.ATTRIBUTE,
143+
TsTableColumnCategory.FIELD
144+
});
145+
innerStmt.setDataTypes(
146+
new TSDataType[] {
147+
TSDataType.STRING, TSDataType.STRING, TSDataType.STRING, TSDataType.STRING
148+
});
149+
innerStmt.setColumns(
150+
new Object[] {
151+
new Binary[] {
152+
new Binary("d1", StandardCharsets.UTF_8),
153+
new Binary("d1", StandardCharsets.UTF_8),
154+
new Binary("d1", StandardCharsets.UTF_8),
155+
new Binary("d1", StandardCharsets.UTF_8)
156+
},
157+
new Binary[] {
158+
new Binary("attr1_1", StandardCharsets.UTF_8),
159+
Binary.EMPTY_VALUE,
160+
new Binary("attr1_3", StandardCharsets.UTF_8),
161+
Binary.EMPTY_VALUE
162+
},
163+
new Binary[] {
164+
new Binary("attr2_1", StandardCharsets.UTF_8),
165+
new Binary("attr2_2", StandardCharsets.UTF_8),
166+
Binary.EMPTY_VALUE,
167+
Binary.EMPTY_VALUE
168+
},
169+
new Binary[] {
170+
new Binary("m1", StandardCharsets.UTF_8),
171+
new Binary("m2", StandardCharsets.UTF_8),
172+
new Binary("m3", StandardCharsets.UTF_8),
173+
new Binary("m4", StandardCharsets.UTF_8)
174+
},
175+
});
176+
innerStmt.setBitMaps(
177+
new BitMap[] {
178+
new BitMap(4, new byte[] {0x00}),
179+
new BitMap(4, new byte[] {1 << 1 | 1 << 3}),
180+
new BitMap(4, new byte[] {1 << 2 | 1 << 3}),
181+
new BitMap(4, new byte[] {0x00}),
182+
});
183+
184+
InsertTablet insertTablet = new InsertTablet(innerStmt, null);
185+
assertEquals(Arrays.asList("attr1", "attr2"), insertTablet.getAttributeColumnNameList());
186+
List<Object[]> attributeValueList = insertTablet.getAttributeValueList();
187+
assertEquals(1, attributeValueList.size());
188+
assertArrayEquals(
189+
new Object[] {
190+
new Binary("attr1_3", StandardCharsets.UTF_8),
191+
new Binary("attr2_2", StandardCharsets.UTF_8)
192+
},
193+
attributeValueList.get(0));
194+
195+
List<Object[]> deviceIdList = insertTablet.getDeviceIdList();
196+
assertEquals(1, deviceIdList.size());
197+
assertArrayEquals(new Object[] {"d1"}, deviceIdList.get(0));
198+
}
130199
}

0 commit comments

Comments
 (0)