Dropped columns can cause reverse sstable iteration to return prematurely

Patch by Blake Eggleston; Reviewed by Sam Tunnicliffe for CASSANDRA-14838


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/e4bac44a
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/e4bac44a
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/e4bac44a

Branch: refs/heads/cassandra-3.11
Commit: e4bac44a04d59d93f622d91ef40b462250dac613
Parents: e07d53a
Author: Blake Eggleston <bdeggles...@gmail.com>
Authored: Tue Oct 23 12:46:34 2018 -0700
Committer: Blake Eggleston <bdeggles...@gmail.com>
Committed: Sun Oct 28 19:29:44 2018 -0700

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 .../columniterator/SSTableReversedIterator.java |  9 +-
 .../SSTableReverseIteratorTest.java             | 98 ++++++++++++++++++++
 3 files changed, 103 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/e4bac44a/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 78c0c47..cc8e348 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0.18
+ * Dropped columns can cause reverse sstable iteration to return prematurely 
(CASSANDRA-14838)
  * Legacy sstables with  multi block range tombstones create invalid bound 
sequences (CASSANDRA-14823)
  * Expand range tombstone validation checks to multiple interim request stages 
(CASSANDRA-14824)
  * Reverse order reads can return incomplete results (CASSANDRA-14803)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/e4bac44a/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java 
b/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
index 2d95dab..8d3f4f3 100644
--- 
a/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
+++ 
b/src/java/org/apache/cassandra/db/columniterator/SSTableReversedIterator.java
@@ -404,19 +404,18 @@ public class SSTableReversedIterator extends 
AbstractSSTableIterator
                 indexState.setToBlock(nextBlockIdx);
                 readCurrentBlock(true, nextBlockIdx != lastBlockIdx);
 
-                // for pre-3.0 storage formats, index blocks that only contain 
a single row and that row crosses
+                // If an indexed block only contains data for a dropped 
column, the iterator will be empty, even
+                // though we may still have data to read in subsequent blocks
+
+                // also, for pre-3.0 storage formats, index blocks that only 
contain a single row and that row crosses
                 // index boundaries, the iterator will be empty even though we 
haven't read everything we're intending
                 // to read. In that case, we want to read the next index 
block. This shouldn't be possible in 3.0+
                 // formats (see next comment)
                 if (!iterator.hasNext() && nextBlockIdx > lastBlockIdx)
                 {
-                    Verify.verify(!sstable.descriptor.version.storeRows());
                     continue;
                 }
 
-                // for 3.0+ storage formats, since that new block is within 
the bounds we've computed in setToSlice(),
-                // we know there will always be something matching the slice 
unless we're on the lastBlockIdx (in which
-                // case there may or may not be results, but if there isn't, 
we're done for the slice).
                 return iterator.hasNext();
             }
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/e4bac44a/test/unit/org/apache/cassandra/db/columniterator/SSTableReverseIteratorTest.java
----------------------------------------------------------------------
diff --git 
a/test/unit/org/apache/cassandra/db/columniterator/SSTableReverseIteratorTest.java
 
b/test/unit/org/apache/cassandra/db/columniterator/SSTableReverseIteratorTest.java
new file mode 100644
index 0000000..2f183c0
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/db/columniterator/SSTableReverseIteratorTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db.columniterator;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+import com.google.common.collect.Iterables;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.RowIndexEntry;
+import org.apache.cassandra.db.marshal.Int32Type;
+import org.apache.cassandra.io.sstable.format.SSTableReader;
+import org.apache.cassandra.schema.KeyspaceParams;
+
+public class SSTableReverseIteratorTest
+{
+    private static final String KEYSPACE = "ks";
+    private Random random;
+
+    @BeforeClass
+    public static void setupClass()
+    {
+        SchemaLoader.prepareServer();
+        SchemaLoader.createKeyspace(KEYSPACE, KeyspaceParams.simple(1));
+    }
+
+    @Before
+    public void setUp()
+    {
+        random = new Random(0);
+    }
+
+    private ByteBuffer bytes(int size)
+    {
+        byte[] b = new byte[size];
+        random.nextBytes(b);
+        return ByteBuffer.wrap(b);
+    }
+
+    /**
+     * SSTRI shouldn't bail out if it encounters empty blocks (due to dropped 
columns)
+     */
+    @Test
+    public void emptyBlockTolerance()
+    {
+        String table = "empty_block_tolerance";
+        QueryProcessor.executeInternal(String.format("CREATE TABLE %s.%s (k 
INT, c int, v1 blob, v2 blob, primary key (k, c))", KEYSPACE, table));
+        ColumnFamilyStore tbl = 
Keyspace.open(KEYSPACE).getColumnFamilyStore(table);
+        assert tbl != null;
+
+        int key = 100;
+
+        QueryProcessor.executeInternal(String.format("UPDATE %s.%s SET v1=?, 
v2=? WHERE k=? AND c=?", KEYSPACE, table), bytes(8), bytes(8), key, 0);
+        QueryProcessor.executeInternal(String.format("UPDATE %s.%s SET v1=? 
WHERE k=? AND c=?", KEYSPACE, table), bytes(0x20000), key, 1);
+        QueryProcessor.executeInternal(String.format("UPDATE %s.%s SET v1=? 
WHERE k=? AND c=?", KEYSPACE, table), bytes(0x20000), key, 2);
+        QueryProcessor.executeInternal(String.format("UPDATE %s.%s SET v1=? 
WHERE k=? AND c=?", KEYSPACE, table), bytes(0x20000), key, 3);
+
+        tbl.forceBlockingFlush();
+        SSTableReader sstable = 
Iterables.getOnlyElement(tbl.getLiveSSTables());
+        DecoratedKey dk = 
tbl.getPartitioner().decorateKey(Int32Type.instance.decompose(key));
+        RowIndexEntry indexEntry = sstable.getPosition(dk, 
SSTableReader.Operator.EQ);
+        Assert.assertTrue(indexEntry.isIndexed());
+        Assert.assertTrue(indexEntry.columnsIndex().size() > 2);
+
+        // drop v1 so the first 2 index blocks only contain empty unfiltereds
+        QueryProcessor.executeInternal(String.format("ALTER TABLE %s.%s DROP 
v1", KEYSPACE, table));
+
+        UntypedResultSet result = 
QueryProcessor.executeInternal(String.format("SELECT v2 FROM %s.%s WHERE k=? 
ORDER BY c DESC", KEYSPACE, table), key);
+        Assert.assertEquals(1, result.size());
+
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to