Skip to content

Commit e492e44

Browse files
authored
[Fix] Preserve table insert errors for rejected fields (#18880)
* [Fix] Preserve table insert errors for rejected fields * [Fix] Retain metadata errors for fully rejected table row batches * [Fix] Preserve table timestamps when all fields are rejected * [Test] Align rejected-field assertions and fix controlled flush fixtures
1 parent aa365aa commit e492e44

10 files changed

Lines changed: 394 additions & 24 deletions

File tree

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,68 @@ public void testInsertAllNullRow() throws SQLException {
13791379
}
13801380
}
13811381

1382+
@Test
1383+
public void testRejectedOnlyFieldStillWritesTime() throws Exception {
1384+
try (ITableSession session = EnvFactory.getEnv().getTableSessionConnection()) {
1385+
session.executeNonQueryStatement("use \"test\"");
1386+
session.executeNonQueryStatement(
1387+
"create table rejected_only_field_time (tag1 string tag, s1 int64 field)");
1388+
1389+
Tablet tablet =
1390+
new Tablet(
1391+
"rejected_only_field_time",
1392+
Arrays.asList("tag1", "s1"),
1393+
Arrays.asList(TSDataType.STRING, TSDataType.BOOLEAN),
1394+
Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
1395+
2);
1396+
for (int i = 0; i < 2; i++) {
1397+
int row = tablet.getRowSize();
1398+
tablet.addTimestamp(row, i + 1);
1399+
tablet.addValue("tag1", row, "d" + i);
1400+
tablet.addValue("s1", row, true);
1401+
}
1402+
1403+
StatementExecutionException error =
1404+
assertThrows(StatementExecutionException.class, () -> session.insert(tablet));
1405+
assertTrue(error.getMessage().contains("s1"));
1406+
1407+
Tablet withoutTag =
1408+
new Tablet(
1409+
"rejected_only_field_time",
1410+
Arrays.asList("s1"),
1411+
Arrays.asList(TSDataType.BOOLEAN),
1412+
Arrays.asList(ColumnCategory.FIELD),
1413+
1);
1414+
withoutTag.addTimestamp(0, 3);
1415+
withoutTag.addValue("s1", 0, true);
1416+
error = assertThrows(StatementExecutionException.class, () -> session.insert(withoutTag));
1417+
assertTrue(error.getMessage().contains("s1"));
1418+
assertRejectedFieldTimeRows(session);
1419+
1420+
session.executeNonQueryStatement("flush");
1421+
assertRejectedFieldTimeRows(session);
1422+
}
1423+
}
1424+
1425+
private static void assertRejectedFieldTimeRows(ITableSession session)
1426+
throws IoTDBConnectionException, StatementExecutionException {
1427+
SessionDataSet dataSet =
1428+
session.executeQueryStatement(
1429+
"select time, tag1, s1 from rejected_only_field_time order by time");
1430+
for (int i = 0; i < 3; i++) {
1431+
assertTrue(dataSet.hasNext());
1432+
RowRecord row = dataSet.next();
1433+
assertEquals(i + 1, row.getFields().get(0).getLongV());
1434+
if (i < 2) {
1435+
assertEquals("d" + i, row.getFields().get(1).getStringValue());
1436+
} else {
1437+
assertNull(row.getFields().get(1).getObjectValue(TSDataType.STRING));
1438+
}
1439+
assertNull(row.getFields().get(2).getObjectValue(TSDataType.INT64));
1440+
}
1441+
assertFalse(dataSet.hasNext());
1442+
}
1443+
13821444
@Test
13831445
public void testInsertWithChangedSchema() throws SQLException, IOException {
13841446
SimpleEnv simpleEnv = new SimpleEnv();

‎integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBSessionRelationalIT.java‎

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -449,16 +449,19 @@ public void partialInsertSQLTest() throws IoTDBConnectionException, StatementExe
449449

450450
SessionDataSet dataSet =
451451
session.executeQueryStatement("select * from partial_insert order by time");
452-
long[] timestamps = new long[] {10000, 20000, 40000, 50000};
453-
Boolean[] values = new Boolean[] {true, false, false, true};
452+
// Partial insertion retains timestamps even when the only FIELD value is rejected.
453+
long[] timestamps =
454+
new long[] {10000, 20000, 30000, 35000, 40000, 50000, 60000, 70000, 80000, 90000};
455+
Boolean[] values =
456+
new Boolean[] {true, false, null, null, false, true, null, null, null, null};
454457
int cnt = 0;
455458
while (dataSet.hasNext()) {
456459
RowRecord rec = dataSet.next();
457460
assertEquals(timestamps[cnt], rec.getFields().get(0).getLongV());
458-
assertEquals(values[cnt], rec.getFields().get(1).getBoolV());
461+
assertEquals(values[cnt], rec.getFields().get(1).getObjectValue(TSDataType.BOOLEAN));
459462
cnt++;
460463
}
461-
assertEquals(4, cnt);
464+
assertEquals(timestamps.length, cnt);
462465

463466
} finally {
464467
try (ISession session = EnvFactory.getEnv().getSessionConnection()) {
@@ -769,14 +772,23 @@ public void insertAllFieldDataTypeMismatchTest()
769772
e.getMessage());
770773
}
771774
tablet.reset();
772-
int cnt = 0;
773-
SessionDataSet dataSet =
774-
session.executeQueryStatement("select * from field_wrong_type order by time");
775-
while (dataSet.hasNext()) {
776-
dataSet.next();
777-
cnt++;
775+
for (int phase = 0; phase < 2; phase++) {
776+
try (SessionDataSet dataSet =
777+
session.executeQueryStatement("select * from field_wrong_type order by time")) {
778+
for (int row = 0; row < 10; row++) {
779+
assertTrue(dataSet.hasNext());
780+
RowRecord record = dataSet.next();
781+
assertEquals(row, record.getFields().get(0).getLongV());
782+
assertEquals("tag:" + row, record.getFields().get(1).getStringValue());
783+
assertNull(record.getFields().get(2).getDataType());
784+
assertNull(record.getFields().get(3).getDataType());
785+
}
786+
assertFalse(dataSet.hasNext());
787+
}
788+
if (phase == 0) {
789+
session.executeNonQueryStatement("flush");
790+
}
778791
}
779-
assertEquals(0, cnt);
780792
}
781793
}
782794

@@ -1512,9 +1524,17 @@ private void testOneCastWithTablet(
15121524
e.getMessage());
15131525
}
15141526
// time, tag1, m1
1515-
SessionDataSet dataSet =
1516-
session.executeQueryStatement("select * from table" + testNum + " order by time");
1517-
assertFalse(dataSet.hasNext());
1527+
try (SessionDataSet dataSet =
1528+
session.executeQueryStatement("select * from table" + testNum + " order by time")) {
1529+
for (int row = 0; row < 2; row++) {
1530+
assertTrue(dataSet.hasNext());
1531+
RowRecord record = dataSet.next();
1532+
assertEquals(row, record.getFields().get(0).getLongV());
1533+
assertEquals("d1", record.getFields().get(1).getStringValue());
1534+
assertNull(record.getFields().get(2).getDataType());
1535+
}
1536+
assertFalse(dataSet.hasNext());
1537+
}
15181538
} else {
15191539
// cannot cast, expect an exception
15201540
try {

‎iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3007,6 +3007,7 @@ private void insertRelationalTabletByGroup(Map<SessionConnection, Tablet> relati
30073007
.collect(Collectors.toList());
30083008

30093009
StringBuilder errMsgBuilder = new StringBuilder();
3010+
List<Throwable> errors = new ArrayList<>();
30103011
for (CompletableFuture<Void> completableFuture : completableFutures) {
30113012
try {
30123013
completableFuture.join();
@@ -3016,6 +3017,7 @@ private void insertRelationalTabletByGroup(Map<SessionConnection, Tablet> relati
30163017
if (cause instanceof IoTDBConnectionException) {
30173018
throw (IoTDBConnectionException) cause;
30183019
} else {
3020+
errors.add(cause);
30193021
if (errMsgBuilder.length() > 0) {
30203022
errMsgBuilder.append(";");
30213023
}
@@ -3024,7 +3026,10 @@ private void insertRelationalTabletByGroup(Map<SessionConnection, Tablet> relati
30243026
}
30253027
}
30263028
if (errMsgBuilder.length() > 0) {
3027-
throw new StatementExecutionException(errMsgBuilder.toString());
3029+
StatementExecutionException exception =
3030+
new StatementExecutionException(errMsgBuilder.toString());
3031+
errors.forEach(exception::addSuppressed);
3032+
throw exception;
30283033
}
30293034
}
30303035

‎iotdb-client/session/src/test/java/org/apache/iotdb/session/SessionCacheLeaderTest.java‎

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
package org.apache.iotdb.session;
2121

2222
import org.apache.iotdb.common.rpc.thrift.TEndPoint;
23+
import org.apache.iotdb.common.rpc.thrift.TSStatus;
2324
import org.apache.iotdb.isession.SessionConfig;
2425
import org.apache.iotdb.rpc.IoTDBConnectionException;
2526
import org.apache.iotdb.rpc.RedirectException;
2627
import org.apache.iotdb.rpc.StatementExecutionException;
28+
import org.apache.iotdb.rpc.TSStatusCode;
2729
import org.apache.iotdb.service.rpc.thrift.TSInsertRecordReq;
2830
import org.apache.iotdb.service.rpc.thrift.TSInsertRecordsOfOneDeviceReq;
2931
import org.apache.iotdb.service.rpc.thrift.TSInsertRecordsReq;
@@ -44,10 +46,12 @@
4446

4547
import java.time.ZoneId;
4648
import java.util.ArrayList;
49+
import java.util.Arrays;
4750
import java.util.HashMap;
4851
import java.util.List;
4952
import java.util.Map;
5053
import java.util.Random;
54+
import java.util.concurrent.atomic.AtomicBoolean;
5155

5256
import static org.junit.Assert.assertEquals;
5357
import static org.junit.Assert.assertNull;
@@ -705,6 +709,79 @@ public void testInsertRelationalTablet()
705709
session.close();
706710
}
707711

712+
@Test
713+
public void testGroupedRelationalTabletPreservesStatementFailures() throws Exception {
714+
AtomicBoolean failWrites = new AtomicBoolean();
715+
session =
716+
new MockSession("127.0.0.1", 55560, true, "table") {
717+
@Override
718+
public SessionConnection constructSessionConnection(
719+
Session owner, TEndPoint endpoint, ZoneId zoneId) {
720+
return new MockSessionConnection(owner, endpoint, zoneId) {
721+
@Override
722+
protected void insertTablet(TSInsertTabletReq request)
723+
throws RedirectException, IoTDBConnectionException, StatementExecutionException {
724+
if (failWrites.get()) {
725+
TSStatusCode code =
726+
endpoint.getPort() == 55560
727+
? TSStatusCode.METADATA_ERROR
728+
: TSStatusCode.DATA_TYPE_MISMATCH;
729+
throw new StatementExecutionException(
730+
new TSStatus(code.getStatusCode()).setMessage("incompatible field"));
731+
}
732+
super.insertTablet(request);
733+
}
734+
};
735+
}
736+
};
737+
session.open();
738+
try {
739+
Tablet tablet =
740+
new Tablet(
741+
"table1",
742+
Arrays.asList("id", "field"),
743+
Arrays.asList(TSDataType.STRING, TSDataType.INT64),
744+
Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
745+
50);
746+
for (int i = 0; i < 50; i++) {
747+
int row = tablet.getRowSize();
748+
tablet.addTimestamp(row, row);
749+
tablet.addValue("id", row, "id" + (row % 4));
750+
tablet.addValue("field", row, 1L);
751+
}
752+
session.insertRelationalTablet(tablet);
753+
assertEquals(4, session.tableModelDeviceIdToEndpoint.size());
754+
755+
failWrites.set(true);
756+
tablet.reset();
757+
for (int i = 0; i < 4; i++) {
758+
int row = tablet.getRowSize();
759+
tablet.addTimestamp(row, row);
760+
tablet.addValue("id", row, "id" + row);
761+
tablet.addValue("field", row, 1L);
762+
}
763+
StatementExecutionException failure =
764+
Assert.assertThrows(
765+
StatementExecutionException.class, () -> session.insertRelationalTablet(tablet));
766+
assertEquals(4, failure.getSuppressed().length);
767+
long metadataFailures = 0;
768+
long typeFailures = 0;
769+
for (Throwable cause : failure.getSuppressed()) {
770+
Assert.assertTrue(cause instanceof StatementExecutionException);
771+
int code = ((StatementExecutionException) cause).getStatusCode();
772+
if (code == TSStatusCode.METADATA_ERROR.getStatusCode()) {
773+
metadataFailures++;
774+
} else if (code == TSStatusCode.DATA_TYPE_MISMATCH.getStatusCode()) {
775+
typeFailures++;
776+
}
777+
}
778+
assertEquals(1, metadataFailures);
779+
assertEquals(3, typeFailures);
780+
} finally {
781+
session.close();
782+
}
783+
}
784+
708785
@Test
709786
public void testInsertRecordsWithSessionBroken() throws StatementExecutionException {
710787
// without leader cache
@@ -1281,7 +1358,7 @@ protected void insertRecordsOfOneDevice(TSInsertRecordsOfOneDeviceReq request)
12811358

12821359
@Override
12831360
protected void insertTablet(TSInsertTabletReq request)
1284-
throws RedirectException, IoTDBConnectionException {
1361+
throws RedirectException, IoTDBConnectionException, StatementExecutionException {
12851362
if (isConnectionBroken()) {
12861363
throw ioTDBConnectionException;
12871364
}

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertNode.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,11 @@ public boolean allMeasurementFailed() {
446446
return measurements == null || !hasValidMeasurements();
447447
}
448448

449+
public boolean shouldSkipWrite() {
450+
// Table rows remain writable through their time column after every FIELD is rejected.
451+
return allMeasurementFailed() && columnCategories == null;
452+
}
453+
449454
// endregion
450455

451456
// region progress index

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,7 +1563,7 @@ private int findLastInsertTabletIndexToMark(final InsertMultiTabletsNode insertM
15631563
for (int i = insertMultiTabletsNode.getInsertTabletNodeList().size() - 1; i >= 0; i--) {
15641564
final InsertTabletNode insertTabletNode =
15651565
insertMultiTabletsNode.getInsertTabletNodeList().get(i);
1566-
if (insertTabletNode.getRowCount() <= 0 || insertTabletNode.allMeasurementFailed()) {
1566+
if (insertTabletNode.getRowCount() <= 0 || insertTabletNode.shouldSkipWrite()) {
15671567
continue;
15681568
}
15691569
if (!insertTabletNode.shouldCheckTTL()) {
@@ -1613,7 +1613,7 @@ private boolean insertTabletToTsFileProcessor(
16131613
boolean noFailure,
16141614
long[] infoForMetrics)
16151615
throws DataTypeInconsistentException {
1616-
if (insertTabletNode.allMeasurementFailed()) {
1616+
if (insertTabletNode.shouldSkipWrite()) {
16171617
if (logger.isDebugEnabled()) {
16181618
logger.debug(
16191619
StorageEngineMessages.STORAGE_LOG_WON_T_INSERT_TABLET_BECAUSE_C2DC8032,
@@ -1821,7 +1821,7 @@ private void tryToUpdateInsertTabletLastCache(
18211821
private TsFileProcessor insertToTsFileProcessor(
18221822
InsertRowNode insertRowNode, boolean sequence, long timePartitionId)
18231823
throws WriteProcessException {
1824-
if (insertRowNode.allMeasurementFailed()) {
1824+
if (insertRowNode.shouldSkipWrite()) {
18251825
return null;
18261826
}
18271827
TsFileProcessor tsFileProcessor = getOrCreateTsFileProcessor(timePartitionId, sequence);
@@ -1850,7 +1850,7 @@ private List<InsertRowNode> insertToTsFileProcessors(
18501850
Map<TsFileProcessor, InsertRowsNode> tsFileProcessorMap = new HashMap<>();
18511851
for (int i = 0; i < areSequence.length; i++) {
18521852
InsertRowNode insertRowNode = insertRowsNode.getInsertRowNodeList().get(i);
1853-
if (insertRowNode.allMeasurementFailed()) {
1853+
if (insertRowNode.shouldSkipWrite()) {
18541854
continue;
18551855
}
18561856
final TsFileProcessor tsFileProcessor;

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ public int insertAlignedRow(InsertRowNode insertRowNode) {
266266
dataTypes.add(schema.getType());
267267
writableValues[i] = value;
268268
}
269-
if (schemaList.isEmpty()) {
269+
if (schemaList.isEmpty() && insertRowNode.getColumnCategories() == null) {
270270
return 0;
271271
}
272272
memSize +=
@@ -497,7 +497,7 @@ public void writeAlignedTablet(
497497
}
498498
}
499499
}
500-
if (schemaList.isEmpty()) {
500+
if (schemaList.isEmpty() && insertTabletNode.getColumnCategories() == null) {
501501
return;
502502
}
503503
int splitStart = start;

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/recover/file/TsFilePlanRedoer.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ void redoDelete(RelationalDeleteDataNode node) throws IOException {
9292
}
9393

9494
void redoInsert(InsertNode node) throws WriteProcessException {
95-
if (!node.hasValidMeasurements()) {
95+
if (node.shouldSkipWrite()) {
9696
return;
9797
}
9898
boolean isSingleDevice = !(node instanceof RelationalInsertTabletNode);
@@ -155,7 +155,7 @@ void redoInsert(InsertNode node) throws WriteProcessException {
155155
void redoInsertRows(InsertRowsNode insertRowsNode) {
156156
int pointsInserted = 0;
157157
for (InsertRowNode node : insertRowsNode.getInsertRowNodeList()) {
158-
if (!node.hasValidMeasurements()) {
158+
if (node.shouldSkipWrite()) {
159159
continue;
160160
}
161161
if (tsFileResource != null) {

0 commit comments

Comments
 (0)