From 8b3a64f3aedcb93d8c784dbf6df3536eeaf462bc Mon Sep 17 00:00:00 2001 From: wot4n Date: Wed, 29 Apr 2026 14:47:17 +0200 Subject: [PATCH 1/2] add fix Signed-off-by: wot4n --- pom.xml | 2 +- .../iterator/ConcreteScanIterator.java | 2 +- .../operation/iterator/ScanIterator.java | 4 +- .../tikv/common/region/RegionStoreClient.java | 39 +++++++++++++------ .../java/org/tikv/common/KVMockServer.java | 9 ++++- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 9d95527e976..100e86ba1ec 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.tikv tikv-client-java - 3.3.4-SNAPSHOT + 3.4.0 jar TiKV Java Client A Java Client for TiKV diff --git a/src/main/java/org/tikv/common/operation/iterator/ConcreteScanIterator.java b/src/main/java/org/tikv/common/operation/iterator/ConcreteScanIterator.java index 72422736e76..2a281d3e92a 100644 --- a/src/main/java/org/tikv/common/operation/iterator/ConcreteScanIterator.java +++ b/src/main/java/org/tikv/common/operation/iterator/ConcreteScanIterator.java @@ -76,7 +76,7 @@ TiRegion loadCurrentRegionToCache() throws GrpcException { try (RegionStoreClient client = builder.build(startKey)) { client.setTimeout(conf.getScanTimeout()); BackOffer backOffer = ConcreteBackOffer.newScannerNextMaxBackOff(); - currentCache = client.scan(backOffer, startKey, version); + currentCache = client.scan(backOffer, startKey, rangeEndKey, version, keyOnly); // If we get region before scan, we will use region from cache which // may have wrong end key. This may miss some regions that split from old region. // Client will get the newest region during scan. So we need to diff --git a/src/main/java/org/tikv/common/operation/iterator/ScanIterator.java b/src/main/java/org/tikv/common/operation/iterator/ScanIterator.java index 69fd0217fd5..f5ef54384cc 100644 --- a/src/main/java/org/tikv/common/operation/iterator/ScanIterator.java +++ b/src/main/java/org/tikv/common/operation/iterator/ScanIterator.java @@ -35,6 +35,7 @@ public abstract class ScanIterator implements Iterator { protected final RegionStoreClientBuilder builder; protected List currentCache; protected ByteString startKey; + protected ByteString rangeEndKey; protected int index = -1; protected int limit; protected boolean keyOnly; @@ -52,7 +53,8 @@ public abstract class ScanIterator implements Iterator { int limit, boolean keyOnly) { this.startKey = requireNonNull(startKey, "start key is null"); - this.endKey = Key.toRawKey(requireNonNull(endKey, "end key is null")); + this.rangeEndKey = requireNonNull(endKey, "end key is null"); + this.endKey = Key.toRawKey(this.rangeEndKey); this.hasEndKey = !endKey.isEmpty(); this.limit = limit; this.keyOnly = keyOnly; diff --git a/src/main/java/org/tikv/common/region/RegionStoreClient.java b/src/main/java/org/tikv/common/region/RegionStoreClient.java index 9a4ed807503..fd95e58c6b4 100644 --- a/src/main/java/org/tikv/common/region/RegionStoreClient.java +++ b/src/main/java/org/tikv/common/region/RegionStoreClient.java @@ -337,19 +337,34 @@ private List handleBatchGetResponse( public List scan( BackOffer backOffer, ByteString startKey, long version, boolean keyOnly) { + return scan(backOffer, startKey, ByteString.EMPTY, version, keyOnly); + } + + public List scan( + BackOffer backOffer, ByteString startKey, ByteString endKey, long version, boolean keyOnly) { boolean forWrite = false; while (true) { Supplier request = - () -> - ScanRequest.newBuilder() - .setContext( - makeContext( - getResolvedLocks(version), this.storeType, backOffer.getSlowLog())) - .setStartKey(codec.encodeKey(startKey)) - .setVersion(version) - .setKeyOnly(keyOnly) - .setLimit(getConf().getScanBatchSize()) - .build(); + () -> { + ScanRequest.Builder b = + ScanRequest.newBuilder() + .setContext( + makeContext( + getResolvedLocks(version), this.storeType, backOffer.getSlowLog())) + .setVersion(version) + .setKeyOnly(keyOnly) + .setLimit(getConf().getScanBatchSize()); + + // API version matters here: v2 transactional keys are keyspace-prefixed; encodeRange applies + // that encoding to both bounds. v1 passes raw keys and only sets start on the RPC in this client path + if (getConf().getApiVersion().isV2()) { + Pair range = codec.encodeRange(startKey, endKey); + b.setStartKey(range.first).setEndKey(range.second); + } else { + b.setStartKey(codec.encodeKey(startKey)); + } + return b.build(); + }; KVErrorHandler handler = new KVErrorHandler<>( @@ -367,7 +382,9 @@ public List scan( region = regionManager.getRegionByKey(startKey, backOffer); if (handleScanResponse(backOffer, resp, version, forWrite)) { - return resp.getPairsList(); + // Return logical user-space keys (same as batchGet) so scan iterators, prefix bounds, and + // get() stay consistent in API v2; wire format includes the keyspace prefix. + return codec.decodeKvPairs(resp.getPairsList()); } } } diff --git a/src/test/java/org/tikv/common/KVMockServer.java b/src/test/java/org/tikv/common/KVMockServer.java index ea09270cfc7..41c7a53d3ed 100644 --- a/src/test/java/org/tikv/common/KVMockServer.java +++ b/src/test/java/org/tikv/common/KVMockServer.java @@ -43,6 +43,7 @@ import java.util.TreeMap; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tikv.common.key.Key; @@ -362,9 +363,13 @@ public void kvScan( } } else { SortedMap kvs = dataMap.tailMap(key); + Stream> entryStream = kvs.entrySet().stream(); + if (!request.getEndKey().isEmpty()) { + Key rangeEnd = toRawKey(request.getEndKey()); + entryStream = entryStream.filter(entry -> entry.getKey().compareTo(rangeEnd) < 0); + } builder.addAllPairs( - kvs.entrySet() - .stream() + entryStream .map( kv -> { Kvrpcpb.KvPair.Builder kvBuilder = From 5d4438a0ad6ceb815f5a6da79e199286c21b358c Mon Sep 17 00:00:00 2001 From: wot4n Date: Wed, 29 Apr 2026 15:21:11 +0200 Subject: [PATCH 2/2] reformat Signed-off-by: wot4n --- src/main/java/org/tikv/common/region/RegionStoreClient.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tikv/common/region/RegionStoreClient.java b/src/main/java/org/tikv/common/region/RegionStoreClient.java index fd95e58c6b4..499001b918e 100644 --- a/src/main/java/org/tikv/common/region/RegionStoreClient.java +++ b/src/main/java/org/tikv/common/region/RegionStoreClient.java @@ -355,8 +355,9 @@ public List scan( .setKeyOnly(keyOnly) .setLimit(getConf().getScanBatchSize()); - // API version matters here: v2 transactional keys are keyspace-prefixed; encodeRange applies - // that encoding to both bounds. v1 passes raw keys and only sets start on the RPC in this client path + // API version matters here: v2 transactional keys are keyspace-prefixed; encodeRange + // applies that encoding to both bounds. v1 passes raw keys and only sets start on the + // RPC in this client path if (getConf().getApiVersion().isV2()) { Pair range = codec.encodeRange(startKey, endKey); b.setStartKey(range.first).setEndKey(range.second);