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

sk0x50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new bb268f4c0 IGNITE-16670 Added the ability to skip tombstones when 
iterating through the meta storage. Fixes #918
bb268f4c0 is described below

commit bb268f4c00b240edfa637d8b0a6e397d17472fc6
Author: Denis Chudov <[email protected]>
AuthorDate: Tue Jul 5 01:18:08 2022 +0300

    IGNITE-16670 Added the ability to skip tombstones when iterating through 
the meta storage. Fixes #918
    
    Signed-off-by: Slava Koptilin <[email protected]>
---
 .../client/ItMetaStorageRaftGroupTest.java         |  2 +-
 .../client/ItMetaStorageServiceTest.java           | 16 ++++----
 .../metastorage/client/MetaStorageService.java     | 33 +++++++++++++++
 .../metastorage/client/MetaStorageServiceImpl.java | 27 +++++++++---
 .../metastorage/common/command/RangeCommand.java   | 32 +++++++++++++++
 .../metastorage/server/KeyValueStorage.java        | 10 +++--
 .../server/persistence/RangeCursor.java            | 21 ++++++----
 .../server/persistence/RocksDbKeyValueStorage.java |  8 ++--
 .../server/raft/MetaStorageListener.java           |  4 +-
 .../server/AbstractKeyValueStorageTest.java        | 48 +++++++++++++++++++++-
 .../server/SimpleInMemoryKeyValueStorage.java      | 21 ++++++----
 .../internal/metastorage/MetaStorageManager.java   | 15 ++++++-
 .../DistributedConfigurationStorageTest.java       |  2 +-
 13 files changed, 196 insertions(+), 43 deletions(-)

diff --git 
a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
 
b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
index 74b94bc7c..a9e36c008 100644
--- 
a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
+++ 
b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageRaftGroupTest.java
@@ -228,7 +228,7 @@ public class ItMetaStorageRaftGroupTest {
 
         final AtomicInteger replicatorStoppedCounter = new AtomicInteger(0);
 
-        when(mockStorage.range(EXPECTED_RESULT_ENTRY1.key().bytes(), new 
byte[]{4})).thenAnswer(invocation -> {
+        when(mockStorage.range(EXPECTED_RESULT_ENTRY1.key().bytes(), new 
byte[]{4}, false)).thenAnswer(invocation -> {
             List<org.apache.ignite.internal.metastorage.server.Entry> entries 
= new ArrayList<>(
                     List.of(EXPECTED_SRV_RESULT_ENTRY1, 
EXPECTED_SRV_RESULT_ENTRY2));
 
diff --git 
a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
 
b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
index 0139583d4..afe7e53b9 100644
--- 
a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
+++ 
b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ItMetaStorageServiceTest.java
@@ -540,7 +540,7 @@ public class ItMetaStorageServiceTest {
 
         long expRevUpperBound = 10;
 
-        when(mockStorage.range(expKeyFrom.bytes(), expKeyTo.bytes(), 
expRevUpperBound)).thenReturn(mock(Cursor.class));
+        when(mockStorage.range(expKeyFrom.bytes(), expKeyTo.bytes(), 
expRevUpperBound, false)).thenReturn(mock(Cursor.class));
 
         metaStorageSvc.range(expKeyFrom, expKeyTo, expRevUpperBound).close();
     }
@@ -556,7 +556,7 @@ public class ItMetaStorageServiceTest {
 
         ByteArray expKeyTo = new ByteArray(new byte[]{3});
 
-        when(mockStorage.range(expKeyFrom.bytes(), 
expKeyTo.bytes())).thenReturn(mock(Cursor.class));
+        when(mockStorage.range(expKeyFrom.bytes(), expKeyTo.bytes(), 
false)).thenReturn(mock(Cursor.class));
 
         metaStorageSvc.range(expKeyFrom, expKeyTo).close();
     }
@@ -570,7 +570,7 @@ public class ItMetaStorageServiceTest {
     public void testRangeWitNullAsKeyTo() throws Exception {
         ByteArray expKeyFrom = new ByteArray(new byte[]{1});
 
-        when(mockStorage.range(expKeyFrom.bytes(), 
null)).thenReturn(mock(Cursor.class));
+        when(mockStorage.range(expKeyFrom.bytes(), null, 
false)).thenReturn(mock(Cursor.class));
 
         metaStorageSvc.range(expKeyFrom, null).close();
     }
@@ -582,7 +582,7 @@ public class ItMetaStorageServiceTest {
     public void testRangeHasNext() {
         ByteArray expKeyFrom = new ByteArray(new byte[]{1});
 
-        when(mockStorage.range(expKeyFrom.bytes(), 
null)).thenAnswer(invocation -> {
+        when(mockStorage.range(expKeyFrom.bytes(), null, 
false)).thenAnswer(invocation -> {
             var cursor = mock(Cursor.class);
 
             when(cursor.hasNext()).thenReturn(true);
@@ -600,7 +600,7 @@ public class ItMetaStorageServiceTest {
      */
     @Test
     public void testRangeNext() {
-        when(mockStorage.range(EXPECTED_RESULT_ENTRY.key().bytes(), 
null)).thenAnswer(invocation -> {
+        when(mockStorage.range(EXPECTED_RESULT_ENTRY.key().bytes(), null, 
false)).thenAnswer(invocation -> {
             var cursor = mock(Cursor.class);
 
             when(cursor.hasNext()).thenReturn(true);
@@ -619,7 +619,7 @@ public class ItMetaStorageServiceTest {
      */
     @Test
     public void testRangeNextNoSuchElementException() {
-        when(mockStorage.range(EXPECTED_RESULT_ENTRY.key().bytes(), 
null)).thenAnswer(invocation -> {
+        when(mockStorage.range(EXPECTED_RESULT_ENTRY.key().bytes(), null, 
false)).thenAnswer(invocation -> {
             var cursor = mock(Cursor.class);
 
             when(cursor.hasNext()).thenReturn(true);
@@ -644,7 +644,7 @@ public class ItMetaStorageServiceTest {
 
         Cursor cursorMock = mock(Cursor.class);
 
-        when(mockStorage.range(expKeyFrom.bytes(), 
null)).thenReturn(cursorMock);
+        when(mockStorage.range(expKeyFrom.bytes(), null, 
false)).thenReturn(cursorMock);
 
         Cursor<Entry> cursor = metaStorageSvc.range(expKeyFrom, null);
 
@@ -899,7 +899,7 @@ public class ItMetaStorageServiceTest {
      */
     @Test
     public void testCursorsCleanup() throws Exception {
-        when(mockStorage.range(EXPECTED_RESULT_ENTRY.key().bytes(), 
null)).thenAnswer(invocation -> {
+        when(mockStorage.range(EXPECTED_RESULT_ENTRY.key().bytes(), null, 
false)).thenAnswer(invocation -> {
             var cursor = mock(Cursor.class);
 
             when(cursor.hasNext()).thenReturn(true);
diff --git 
a/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
 
b/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
index f211a7539..d597f78dc 100644
--- 
a/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
+++ 
b/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageService.java
@@ -252,6 +252,23 @@ public interface MetaStorageService {
     @NotNull
     Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable ByteArray keyTo, 
long revUpperBound);
 
+    /**
+     * Retrieves entries for the given key range in lexicographic order. 
Entries will be filtered out by upper bound of given revision
+     * number.
+     *
+     * @param keyFrom           Start key of range (inclusive). Couldn't be 
{@code null}.
+     * @param keyTo             End key of range (exclusive). Could be {@code 
null}.
+     * @param revUpperBound     The upper bound for entry revision. {@code -1} 
means latest revision.
+     * @param includeTombstones Whether to include tombstone entries.
+     * @return Cursor built upon entries corresponding to the given range and 
revision.
+     * @throws OperationTimeoutException If the operation is timed out.
+     * @throws CompactedException        If the desired revisions are removed 
from the storage due to a compaction.
+     * @see ByteArray
+     * @see Entry
+     */
+    @NotNull
+    Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable ByteArray keyTo, 
long revUpperBound, boolean includeTombstones);
+
     /**
      * Retrieves entries for the given key range in lexicographic order. Short 
cut for {@link #range(ByteArray, ByteArray, long)} where
      * {@code revUpperBound == -1}.
@@ -267,6 +284,22 @@ public interface MetaStorageService {
     @NotNull
     Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable ByteArray keyTo);
 
+    /**
+     * Retrieves entries for the given key range in lexicographic order. Short 
cut for
+     * {@link #range(ByteArray, ByteArray, long, boolean)} where {@code 
revUpperBound == -1}.
+     *
+     * @param keyFrom           Start key of range (inclusive). Couldn't be 
{@code null}.
+     * @param keyTo             End key of range (exclusive). Could be {@code 
null}.
+     * @param includeTombstones Whether to include tombstone entries.
+     * @return Cursor built upon entries corresponding to the given range and 
revision.
+     * @throws OperationTimeoutException If the operation is timed out.
+     * @throws CompactedException        If the desired revisions are removed 
from the storage due to a compaction.
+     * @see ByteArray
+     * @see Entry
+     */
+    @NotNull
+    Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable ByteArray keyTo, 
boolean includeTombstones);
+
     /**
      * Subscribes on meta storage updates matching the parameters.
      *
diff --git 
a/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageServiceImpl.java
 
b/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageServiceImpl.java
index aebd7d5af..d6c6fb472 100644
--- 
a/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageServiceImpl.java
+++ 
b/modules/metastorage-client/src/main/java/org/apache/ignite/internal/metastorage/client/MetaStorageServiceImpl.java
@@ -209,10 +209,21 @@ public class MetaStorageServiceImpl implements 
MetaStorageService {
     /** {@inheritDoc} */
     @Override
     public @NotNull Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable 
ByteArray keyTo, long revUpperBound) {
+        return range(keyFrom, keyTo, revUpperBound, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @NotNull Cursor<Entry> range(
+            @NotNull ByteArray keyFrom,
+            @Nullable ByteArray keyTo,
+            long revUpperBound,
+            boolean includeTombstones
+    ) {
         return new CursorImpl<>(
                 metaStorageRaftGrpSvc,
                 metaStorageRaftGrpSvc.run(
-                        new RangeCommand(keyFrom, keyTo, revUpperBound, 
localNodeId, uuidGenerator.randomUuid())),
+                        new RangeCommand(keyFrom, keyTo, revUpperBound, 
localNodeId, uuidGenerator.randomUuid(), includeTombstones)),
                 MetaStorageServiceImpl::singleEntryResult
         );
     }
@@ -220,11 +231,17 @@ public class MetaStorageServiceImpl implements 
MetaStorageService {
     /** {@inheritDoc} */
     @Override
     public @NotNull Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable 
ByteArray keyTo) {
+        return range(keyFrom, keyTo, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public @NotNull Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable 
ByteArray keyTo, boolean includeTombstones) {
         return new CursorImpl<>(
-                metaStorageRaftGrpSvc,
-                metaStorageRaftGrpSvc.run(
-                        new RangeCommand(keyFrom, keyTo, localNodeId, 
uuidGenerator.randomUuid())),
-                MetaStorageServiceImpl::singleEntryResult
+            metaStorageRaftGrpSvc,
+            metaStorageRaftGrpSvc.run(
+                new RangeCommand(keyFrom, keyTo, localNodeId, 
uuidGenerator.randomUuid())),
+            MetaStorageServiceImpl::singleEntryResult
         );
     }
 
diff --git 
a/modules/metastorage-common/src/main/java/org/apache/ignite/internal/metastorage/common/command/RangeCommand.java
 
b/modules/metastorage-common/src/main/java/org/apache/ignite/internal/metastorage/common/command/RangeCommand.java
index 5280022ca..57acc95dc 100644
--- 
a/modules/metastorage-common/src/main/java/org/apache/ignite/internal/metastorage/common/command/RangeCommand.java
+++ 
b/modules/metastorage-common/src/main/java/org/apache/ignite/internal/metastorage/common/command/RangeCommand.java
@@ -48,6 +48,9 @@ public final class RangeCommand implements WriteCommand {
     @NotNull
     private final IgniteUuid cursorId;
 
+    /** Whether to include tombstone entries. */
+    private final boolean includeTombstones;
+
     /**
      * Constructor.
      *
@@ -80,12 +83,34 @@ public final class RangeCommand implements WriteCommand {
             long revUpperBound,
             @NotNull String requesterNodeId,
             @NotNull IgniteUuid cursorId
+    ) {
+        this(keyFrom, keyTo, revUpperBound, requesterNodeId, cursorId, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param keyFrom           Start key of range (inclusive).
+     * @param keyTo             End key of range (exclusive).
+     * @param revUpperBound     The upper bound for entry revision. {@code -1} 
means latest revision.
+     * @param requesterNodeId   Id of the node that requests range.
+     * @param cursorId          Id of cursor that is associated with the 
current command.
+     * @param includeTombstones Whether to include tombstones.
+     */
+    public RangeCommand(
+            @NotNull ByteArray keyFrom,
+            @Nullable ByteArray keyTo,
+            long revUpperBound,
+            @NotNull String requesterNodeId,
+            @NotNull IgniteUuid cursorId,
+            boolean includeTombstones
     ) {
         this.keyFrom = keyFrom.bytes();
         this.keyTo = keyTo == null ? null : keyTo.bytes();
         this.revUpperBound = revUpperBound;
         this.requesterNodeId = requesterNodeId;
         this.cursorId = cursorId;
+        this.includeTombstones = includeTombstones;
     }
 
     /**
@@ -123,4 +148,11 @@ public final class RangeCommand implements WriteCommand {
     public IgniteUuid getCursorId() {
         return cursorId;
     }
+
+    /**
+     * Returns the boolean value indicating whether this range command is 
supposed to include tombstone entries into the cursor.
+     */
+    public boolean includeTombstones() {
+        return includeTombstones;
+    }
 }
diff --git 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
index 56d6e33f4..81598771e 100644
--- 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
+++ 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -170,11 +170,12 @@ public interface KeyValueStorage extends AutoCloseable {
     /**
      * Returns cursor by entries which correspond to the given keys range.
      *
-     * @param keyFrom Start key of range (inclusive).
-     * @param keyTo   Last key of range (exclusive).
+     * @param keyFrom           Start key of range (inclusive).
+     * @param keyTo             Last key of range (exclusive).
+     * @param includeTombstones Whether to include tombstone entries.
      * @return Cursor by entries which correspond to the given keys range.
      */
-    Cursor<Entry> range(byte[] keyFrom, byte @Nullable [] keyTo);
+    Cursor<Entry> range(byte[] keyFrom, byte @Nullable [] keyTo, boolean 
includeTombstones);
 
     /**
      * Returns cursor by entries which correspond to the given keys range and 
bounded by revision number.
@@ -182,9 +183,10 @@ public interface KeyValueStorage extends AutoCloseable {
      * @param keyFrom       Start key of range (inclusive).
      * @param keyTo         Last key of range (exclusive).
      * @param revUpperBound Upper bound of revision.
+     * @param includeTombstones Whether to include tombstone entries.
      * @return Cursor by entries which correspond to the given keys range.
      */
-    Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long revUpperBound);
+    Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long revUpperBound, 
boolean includeTombstones);
 
     /**
      * Creates subscription on updates of entries corresponding to the given 
keys range and starting from the given revision number.
diff --git 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RangeCursor.java
 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RangeCursor.java
index a20d12eae..c16e29f24 100644
--- 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RangeCursor.java
+++ 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RangeCursor.java
@@ -52,6 +52,9 @@ class RangeCursor implements Cursor<Entry> {
     /** Key of the last returned entry. */
     private byte[] lastRetKey;
 
+    /** Whether to include tombstone entries. */
+    private final boolean includeTombstones;
+
     /**
      * {@code true} if the iteration is finished.
      */
@@ -60,16 +63,18 @@ class RangeCursor implements Cursor<Entry> {
     /**
      * Constructor.
      *
-     * @param storage Storage.
-     * @param keyFrom {@link #keyFrom}.
-     * @param keyTo   {@link #keyTo}.
-     * @param rev     {@link #rev}.
+     * @param storage           Storage.
+     * @param keyFrom           {@link #keyFrom}.
+     * @param keyTo             {@link #keyTo}.
+     * @param rev               {@link #rev}.
+     * @param includeTombstones {@link #includeTombstones}.
      */
-    RangeCursor(RocksDbKeyValueStorage storage, byte[] keyFrom, byte @Nullable 
[] keyTo, long rev) {
+    RangeCursor(RocksDbKeyValueStorage storage, byte[] keyFrom, byte @Nullable 
[] keyTo, long rev, boolean includeTombstones) {
         this.storage = storage;
         this.keyFrom = keyFrom;
         this.keyTo = keyTo;
         this.rev = rev;
+        this.includeTombstones = includeTombstones;
         this.it = createIterator();
     }
 
@@ -147,9 +152,11 @@ class RangeCursor implements Cursor<Entry> {
 
                             Entry entry = storage.doGetValue(key, lastRev);
 
-                            assert !entry.empty() : "Iterator should not 
return empty entry.";
+                            if (!entry.tombstone() || includeTombstones) {
+                                assert !entry.empty() : "Iterator should not 
return empty entry.";
 
-                            nextRetEntry = entry;
+                                nextRetEntry = entry;
+                            }
                         }
                     }
                 } finally {
diff --git 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
index 33ed663da..989dddf71 100644
--- 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
+++ 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
@@ -685,14 +685,14 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
 
     /** {@inheritDoc} */
     @Override
-    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo) {
-        return new RangeCursor(this, keyFrom, keyTo, rev);
+    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, boolean 
includeTombstones) {
+        return new RangeCursor(this, keyFrom, keyTo, rev, includeTombstones);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long 
revUpperBound) {
-        return new RangeCursor(this, keyFrom, keyTo, revUpperBound);
+    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long 
revUpperBound, boolean includeTombstones) {
+        return new RangeCursor(this, keyFrom, keyTo, revUpperBound, 
includeTombstones);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/raft/MetaStorageListener.java
 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/raft/MetaStorageListener.java
index 8bb646035..c85b5a9d8 100644
--- 
a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/raft/MetaStorageListener.java
+++ 
b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/raft/MetaStorageListener.java
@@ -250,8 +250,8 @@ public class MetaStorageListener implements 
RaftGroupListener {
                 IgniteUuid cursorId = rangeCmd.getCursorId();
 
                 Cursor<Entry> cursor = (rangeCmd.revUpperBound() != -1)
-                        ? storage.range(rangeCmd.keyFrom(), rangeCmd.keyTo(), 
rangeCmd.revUpperBound()) :
-                        storage.range(rangeCmd.keyFrom(), rangeCmd.keyTo());
+                        ? storage.range(rangeCmd.keyFrom(), rangeCmd.keyTo(), 
rangeCmd.revUpperBound(), rangeCmd.includeTombstones()) :
+                        storage.range(rangeCmd.keyFrom(), rangeCmd.keyTo(), 
rangeCmd.includeTombstones());
 
                 cursors.put(
                         cursorId,
diff --git 
a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractKeyValueStorageTest.java
 
b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractKeyValueStorageTest.java
index 77b983ce7..34b265dd7 100644
--- 
a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractKeyValueStorageTest.java
+++ 
b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractKeyValueStorageTest.java
@@ -1958,7 +1958,7 @@ public abstract class AbstractKeyValueStorageTest {
         assertEquals(3, storage.updateCounter());
 
         // Range for latest revision without max bound.
-        Cursor<Entry> cur = storage.range(key1, null);
+        Cursor<Entry> cur = storage.range(key1, null, false);
 
         Iterator<Entry> it = cur.iterator();
 
@@ -2006,7 +2006,7 @@ public abstract class AbstractKeyValueStorageTest {
         }
 
         // Range for latest revision with max bound.
-        cur = storage.range(key1, key3);
+        cur = storage.range(key1, key3, false);
 
         it = cur.iterator();
 
@@ -2043,6 +2043,50 @@ public abstract class AbstractKeyValueStorageTest {
         }
     }
 
+    @Test
+    public void rangeCursorSkippingTombstones() {
+        byte[] key1 = key(1);
+        byte[] val1 = keyValue(1, 1);
+
+        byte[] key2 = key(2);
+        byte[] val2 = keyValue(2, 2);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        storage.remove(key1);
+
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+
+        storage.put(key2, val2);
+
+        assertEquals(3, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        // Range that includes tombstones.
+        Cursor<Entry> cur = storage.range(key1, null, true);
+
+        assertEquals(2, cur.stream().count());
+
+        // Range that doesn't include tombstones.
+        cur = storage.range(key1, null, false);
+
+        Entry e = cur.next();
+
+        assertArrayEquals(key2, e.key());
+
+        assertFalse(e.tombstone());
+
+        // Check that there are no more elements in cursor.
+        assertFalse(cur.hasNext());
+    }
+
     @Test
     public void watchCursorLexicographicTest() throws Exception {
         assertEquals(0, storage.revision());
diff --git 
a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
 
b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
index ffea7e0e5..71f202314 100644
--- 
a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
+++ 
b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -353,16 +353,16 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
     /** {@inheritDoc} */
     @Override
-    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo) {
+    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, boolean 
includeTombstones) {
         synchronized (mux) {
-            return new RangeCursor(keyFrom, keyTo, rev);
+            return new RangeCursor(keyFrom, keyTo, rev, includeTombstones);
         }
     }
 
     /** {@inheritDoc} */
     @Override
-    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long 
revUpperBound) {
-        return new RangeCursor(keyFrom, keyTo, revUpperBound);
+    public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long 
revUpperBound, boolean includeTombstones) {
+        return new RangeCursor(keyFrom, keyTo, revUpperBound, 
includeTombstones);
     }
 
     /** {@inheritDoc} */
@@ -642,12 +642,15 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
         private byte[] lastRetKey;
 
+        private final boolean includeTombstones;
+
         private boolean finished;
 
-        RangeCursor(byte[] keyFrom, byte[] keyTo, long rev) {
+        RangeCursor(byte[] keyFrom, byte[] keyTo, long rev, boolean 
includeTombstones) {
             this.keyFrom = keyFrom;
             this.keyTo = keyTo;
             this.rev = rev;
+            this.includeTombstones = includeTombstones;
             this.it = createIterator();
         }
 
@@ -718,11 +721,13 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
                                 Entry entry = doGetValue(key, lastRev);
 
-                                assert !entry.empty() : "Iterator should not 
return empty entry.";
+                                if (!entry.tombstone() || includeTombstones) {
+                                    assert !entry.empty() : "Iterator should 
not return empty entry.";
 
-                                nextRetEntry = entry;
+                                    nextRetEntry = entry;
 
-                                break;
+                                    break;
+                                }
                             }
                         }
                     }
diff --git 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
index c76cbe5c5..163762ffb 100644
--- 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
+++ 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
@@ -666,12 +666,25 @@ public class MetaStorageManager implements 
IgniteComponent {
      * @see MetaStorageService#range(ByteArray, ByteArray)
      */
     public @NotNull Cursor<Entry> range(@NotNull ByteArray keyFrom, @Nullable 
ByteArray keyTo) throws NodeStoppingException {
+        return range(keyFrom, keyTo, false);
+    }
+
+    /**
+     * Retrieves entries for the given key range in lexicographic order.
+     *
+     * @see MetaStorageService#range(ByteArray, ByteArray, boolean)
+     */
+    public @NotNull Cursor<Entry> range(
+            @NotNull ByteArray keyFrom,
+            @Nullable ByteArray keyTo,
+            boolean includeTombstones
+    ) throws NodeStoppingException {
         if (!busyLock.enterBusy()) {
             throw new NodeStoppingException();
         }
 
         try {
-            return new CursorWrapper<>(metaStorageSvcFut.thenApply(svc -> 
svc.range(keyFrom, keyTo)));
+            return new CursorWrapper<>(metaStorageSvcFut.thenApply(svc -> 
svc.range(keyFrom, keyTo, includeTombstones)));
         } finally {
             busyLock.leaveBusy();
         }
diff --git 
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorageTest.java
 
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorageTest.java
index 6407ec67a..5d672c3bb 100644
--- 
a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorageTest.java
+++ 
b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/DistributedConfigurationStorageTest.java
@@ -105,7 +105,7 @@ public class DistributedConfigurationStorageTest extends 
ConfigurationStorageTes
                 ByteArray keyFrom = invocation.getArgument(0);
                 ByteArray keyTo = invocation.getArgument(1);
 
-                return new CursorAdapter(metaStorage.range(keyFrom.bytes(), 
keyTo == null ? null : keyTo.bytes()));
+                return new CursorAdapter(metaStorage.range(keyFrom.bytes(), 
keyTo == null ? null : keyTo.bytes(), false));
             });
         } catch (NodeStoppingException e) {
             throw new RuntimeException(e);

Reply via email to