Skip to content

Commit c2985da

Browse files
authored
Merge pull request #747 from tapdata/TAP-10570
fix: TAP-10570 Multi-update operations are not allowed when updating …
2 parents ac10c42 + 380a95a commit c2985da

File tree

3 files changed

+71
-11
lines changed

3 files changed

+71
-11
lines changed

connectors/mongodb-connector/src/main/java/io/tapdata/mongodb/writer/MongodbWriter.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,10 @@ public void writeRecord(List<TapRecordEvent> tapRecordEvents, TapTable table, Co
130130

131131
private void doubleActiveWrite(List<TapRecordEvent> tapRecordEvents, TapTable table, Consumer<WriteListResult<TapRecordEvent>> writeListResultConsumer, ClientSession session) throws Throwable {
132132
Document doubleActiveDoc = new Document("_id", "aaaaaaaa");
133-
UpdateOptions options = new UpdateOptions().upsert(true);
134-
mongoDatabase.getCollection("_tap_double_active").updateOne(session, doubleActiveDoc, new Document("$set", new Document("ts", System.currentTimeMillis())), options);
135-
write(table, tapRecordEvents, writeListResultConsumer, session);
136-
}
133+
UpdateOptions options = new UpdateOptions().upsert(true);
134+
mongoDatabase.getCollection("_tap_double_active").updateOne(session, doubleActiveDoc, new Document("$set", new Document("ts", System.currentTimeMillis())), options);
135+
write(table, tapRecordEvents, writeListResultConsumer, session);
136+
}
137137

138138
private void write(TapTable table, List<TapRecordEvent> tapRecordEvents, Consumer<WriteListResult<TapRecordEvent>> writeListResultConsumer, ClientSession session) throws Throwable {
139139
AtomicLong inserted = new AtomicLong(0); //insert count
@@ -528,6 +528,18 @@ private static BulkWriteOptions buildBulkWriteOptions(BulkWriteModel bulkWriteMo
528528
return bulkWriteOptions;
529529
}
530530

531+
private boolean isShardedCollection(String tableId) {
532+
return shardKeyMap != null && shardKeyMap.containsKey(tableId);
533+
}
534+
535+
private WriteModel<Document> createUpdateModel(String tableId, Document filter, Document update, UpdateOptions options) {
536+
if (isShardedCollection(tableId)) {
537+
return new UpdateOneModel<>(filter, update, options);
538+
} else {
539+
return new UpdateManyModel<>(filter, update, options);
540+
}
541+
}
542+
531543
protected List<WriteModel<Document>> normalWriteMode(AtomicLong inserted, AtomicLong updated, AtomicLong deleted, UpdateOptions options, TapTable tapTable, Collection<String> pks, TapRecordEvent recordEvent) {
532544
List<WriteModel<Document>> writeModels = new ArrayList<>();
533545
if (recordEvent instanceof TapInsertRecordEvent) {
@@ -552,17 +564,17 @@ protected List<WriteModel<Document>> normalWriteMode(AtomicLong inserted, Atomic
552564
}
553565
MongodbUtil.removeIdIfNeed(pks, insertRecordEvent.getAfter());
554566
Document update = new Document(operation, insertRecordEvent.getAfter());
555-
writeModels.add(new UpdateManyModel<>(pkFilter, update, options));
567+
writeModels.add(createUpdateModel(tapTable.getId(), pkFilter, update, options));
556568
if (MapUtils.isNotEmpty(unsetDoc)) {
557-
writeModels.add(new UpdateManyModel<>(pkFilter, new Document("$unset", unsetDoc), options));
569+
writeModels.add(createUpdateModel(tapTable.getId(), pkFilter, new Document("$unset", unsetDoc), options));
558570
}
559571
} else {
560572
if (CollectionUtils.isNotEmpty(pks) && MapUtils.isNotEmpty(unsetDoc)) {
561573
Document pkFilter = getPkFilter(pks, insertRecordEvent.getAfter());
562574
Document update = new Document("$set", insertRecordEvent.getAfter());
563-
writeModels.add(new UpdateManyModel<>(pkFilter, update, options));
575+
writeModels.add(createUpdateModel(tapTable.getId(), pkFilter, update, options));
564576
if (MapUtils.isNotEmpty(unsetDoc)) {
565-
writeModels.add(new UpdateManyModel<>(pkFilter, new Document("$unset", unsetDoc), options));
577+
writeModels.add(createUpdateModel(tapTable.getId(), pkFilter, new Document("$unset", unsetDoc), options));
566578
}
567579
} else {
568580
writeModels.add(new InsertOneModel<>(new Document(insertRecordEvent.getAfter())));
@@ -590,7 +602,7 @@ protected List<WriteModel<Document>> normalWriteMode(AtomicLong inserted, Atomic
590602
u.remove("$v"); // Exists '$v' in update operation of MongoDB(v3.6), remove it because can't apply in write model.
591603
boolean isUpdate = u.keySet().stream().anyMatch(k -> k.startsWith("$"));
592604
if (isUpdate) {
593-
writeModel = new UpdateManyModel<>(pkFilter, u, options);
605+
writeModel = createUpdateModel(tapTable.getId(), pkFilter, u, options);
594606
options.upsert(false);
595607
} else {
596608
writeModel = new ReplaceOneModel<>(pkFilter, u, new ReplaceOptions().upsert(false));
@@ -607,10 +619,10 @@ protected List<WriteModel<Document>> normalWriteMode(AtomicLong inserted, Atomic
607619
}
608620
MongodbUtil.removeIdIfNeed(pks, after);
609621
u.append("$set", after);
610-
writeModels.add(new UpdateManyModel<>(pkFilter, u, options));
622+
writeModels.add(createUpdateModel(tapTable.getId(), pkFilter, u, options));
611623
Document unsetDoc = wrapUnset(recordEvent);
612624
if (MapUtils.isNotEmpty(unsetDoc)) {
613-
writeModels.add(new UpdateManyModel<>(pkFilter, new Document("$unset", unsetDoc), options));
625+
writeModels.add(createUpdateModel(tapTable.getId(), pkFilter, new Document("$unset", unsetDoc), options));
614626
}
615627
}
616628
}

connectors/mongodb-connector/src/main/java/io/tapdata/mongodb/writer/error/BulkWriteErrorCodeHandlerEnum.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.tapdata.mongodb.writer.error.handler.Code11000Handler;
44
import io.tapdata.mongodb.writer.error.handler.Code28Handler;
55
import io.tapdata.mongodb.writer.error.handler.Code2Handler;
6+
import io.tapdata.mongodb.writer.error.handler.Code72Handler;
67

78
import java.util.HashMap;
89
import java.util.Map;
@@ -14,6 +15,7 @@
1415
**/
1516
public enum BulkWriteErrorCodeHandlerEnum {
1617
CODE_28(28, new Code28Handler()),
18+
CODE_72(72, new Code72Handler()),
1719
CODE_11000(11000, new Code11000Handler()),
1820
CODE_2(2, new Code2Handler()),
1921
;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.tapdata.mongodb.writer.error.handler;
2+
3+
import com.mongodb.MongoBulkWriteException;
4+
import com.mongodb.bulk.BulkWriteError;
5+
import com.mongodb.client.MongoCollection;
6+
import com.mongodb.client.model.*;
7+
import io.tapdata.mongodb.writer.BulkWriteModel;
8+
import io.tapdata.mongodb.writer.error.BulkWriteErrorHandler;
9+
import org.bson.Document;
10+
11+
/**
12+
* @author Tapdata
13+
* @Description Handle error code 72: Multi-update operations are not allowed when updating the shard key field
14+
* This error occurs when using UpdateManyModel on a sharded collection where the update involves shard key fields.
15+
* The solution is to convert UpdateManyModel to UpdateOneModel.
16+
* @create 2026-03-27
17+
**/
18+
public class Code72Handler implements BulkWriteErrorHandler {
19+
20+
@Override
21+
public WriteModel<Document> handle(
22+
BulkWriteModel bulkWriteModel,
23+
WriteModel<Document> writeModel,
24+
BulkWriteOptions bulkWriteOptions,
25+
MongoBulkWriteException mongoBulkWriteException,
26+
BulkWriteError writeError,
27+
MongoCollection<Document> collection
28+
) {
29+
// Convert UpdateManyModel to UpdateOneModel to avoid shard key update restriction
30+
try {
31+
if (writeModel instanceof UpdateManyModel) {
32+
UpdateManyModel<Document> updateManyModel = (UpdateManyModel<Document>) writeModel;
33+
Document filter = (Document) updateManyModel.getFilter();
34+
Document update = (Document) updateManyModel.getUpdate();
35+
UpdateOptions options = updateManyModel.getOptions();
36+
37+
// Convert to UpdateOneModel with the same filter, update and options
38+
return new UpdateOneModel<>(filter, update, options);
39+
}
40+
} catch (Throwable ignored) {
41+
// fall through to return null (can't handle)
42+
}
43+
return null;
44+
}
45+
}
46+

0 commit comments

Comments
 (0)