diff --git a/CHANGES.txt b/CHANGES.txt index 62cdbbe656..130b4ec43d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -35,6 +35,7 @@ dev * make concurrent_reads, concurrent_writes configurable at runtime via JMX (CASSANDRA-1060) * disable GCInspector on non-Sun JVMs (CASSANDRA-1061) + * fix tombstone handling in sstable rows with no other data (CASSANDRA-1063) 0.6.1 diff --git a/README.txt b/README.txt index c08b3be92e..2feb9f3c5d 100644 --- a/README.txt +++ b/README.txt @@ -10,7 +10,7 @@ and the data model from Google's BigTable. Like Dynamo, Cassandra is eventually consistent. Like BigTable, Cassandra provides a ColumnFamily-based data model richer than typical key/value systems. -For more information see http://incubator.apache.org/cassandra +For more information see http://cassandra.apache.org/ Requirements ------------ @@ -22,8 +22,8 @@ Getting started This short guide will walk you through getting a basic one node cluster up and running, and demonstrate some simple reads and writes. - * tar -zxvf cassandra-$VERSION.tgz - * cd cassandra-$VERSION + * tar -zxvf apache-cassandra-$VERSION.tar.gz + * cd apache-cassandra-$VERSION * sudo mkdir -p /var/log/cassandra * sudo chown -R `whoami` /var/log/cassandra * sudo mkdir -p /var/lib/cassandra @@ -94,7 +94,7 @@ Wondering where to go from here? best source for additional information. * Join us in #cassandra on irc.freenode.net and ask questions. * Subscribe to the Users mailing list by sending a mail to - cassandra-user-subscribe@incubator.apache.org + user-subscribe@cassandra.apache.org diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 1779990337..431bb26762 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -808,18 +808,15 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean for (SSTableReader sstable : ssTables_) { iter = filter.getSSTableColumnIterator(sstable); - if (iter.hasNext()) // initializes iter.CF + if (iter.getColumnFamily() != null) { returnCF.delete(iter.getColumnFamily()); + iterators.add(iter); } - iterators.add(iter); } Comparator comparator = QueryFilter.getColumnComparator(getComparator()); Iterator collated = IteratorUtils.collatedIterator(comparator, iterators); - if (!collated.hasNext()) - return null; - filter.collectCollatedColumns(returnCF, collated, gcBefore); return returnCF; // caller is responsible for final removeDeleted } diff --git a/src/java/org/apache/cassandra/db/filter/SSTableNamesIterator.java b/src/java/org/apache/cassandra/db/filter/SSTableNamesIterator.java index 544f90e7fb..5378884cb3 100644 --- a/src/java/org/apache/cassandra/db/filter/SSTableNamesIterator.java +++ b/src/java/org/apache/cassandra/db/filter/SSTableNamesIterator.java @@ -60,6 +60,7 @@ public class SSTableNamesIterator extends SimpleAbstractColumnIterator implement this.columns = columnNames; this.decoratedKey = key; + // open the sstable file, if we don't have one passed to use from range scan if (file == null) { try @@ -78,19 +79,69 @@ public class SSTableNamesIterator extends SimpleAbstractColumnIterator implement } } + // read the requested columns into `cf` try { - List filteredColumnNames = getFilteredColumns(file, columnNames); + /* Read the bloom filter summarizing the columns */ + BloomFilter bf = IndexHelper.defreezeBloomFilter(file); + List indexList = IndexHelper.deserializeIndex(file); + + // we can stop early if bloom filter says none of the columns actually exist -- but, + // we can't stop before initializing the cf above, in case there's a relevant tombstone + cf = ColumnFamily.serializer().deserializeFromSSTableNoColumns(ssTable.makeColumnFamily(), file); + + List filteredColumnNames1 = new ArrayList(columnNames.size()); + for (byte[] name : columnNames) + { + if (bf.isPresent(name)) + { + filteredColumnNames1.add(name); + } + } + List filteredColumnNames = filteredColumnNames1; if (filteredColumnNames.isEmpty()) return; - getColumns(ssTable, file, columnNames, filteredColumnNames); + file.readInt(); // column count + + /* get the various column ranges we have to read */ + AbstractType comparator = ssTable.getColumnComparator(); + SortedSet ranges = new TreeSet(IndexHelper.getComparator(comparator)); + for (byte[] name : filteredColumnNames) + { + int index = IndexHelper.indexFor(name, indexList, comparator, false); + if (index == indexList.size()) + continue; + IndexHelper.IndexInfo indexInfo = indexList.get(index); + if (comparator.compare(name, indexInfo.firstName) < 0) + continue; + ranges.add(indexInfo); + } + + file.mark(); + for (IndexHelper.IndexInfo indexInfo : ranges) + { + file.reset(); + long curOffsert = file.skipBytes((int)indexInfo.offset); + assert curOffsert == indexInfo.offset; + // TODO only completely deserialize columns we are interested in + while (file.bytesPastMark() < indexInfo.offset + indexInfo.width) + { + final IColumn column = cf.getColumnSerializer().deserialize(file); + // we check vs the original Set, not the filtered List, for efficiency + if (columnNames.contains(column.name())) + { + cf.addColumn(column); + } + } + } } catch (IOException e) { throw new IOError(e); } - + + // create an iterator view of the columns we read iter = cf.getSortedColumns().iterator(); } @@ -99,76 +150,6 @@ public class SSTableNamesIterator extends SimpleAbstractColumnIterator implement return decoratedKey; } - /** - * Read in the columns we are looking for - * @param ssTable Table to read from - * @param file Read from this file - * @param columnNames Names of all columns we are looking for - * @param filteredColumnNames Names of columns that are thought to exist - * @throws IOException - */ - private void getColumns(SSTableReader ssTable, FileDataInput file, SortedSet columnNames, List filteredColumnNames) throws IOException - { - List indexList = IndexHelper.deserializeIndex(file); - - cf = ColumnFamily.serializer().deserializeFromSSTableNoColumns(ssTable.makeColumnFamily(), file); - file.readInt(); // column count - - /* get the various column ranges we have to read */ - AbstractType comparator = ssTable.getColumnComparator(); - SortedSet ranges = new TreeSet(IndexHelper.getComparator(comparator)); - for (byte[] name : filteredColumnNames) - { - int index = IndexHelper.indexFor(name, indexList, comparator, false); - if (index == indexList.size()) - continue; - IndexHelper.IndexInfo indexInfo = indexList.get(index); - if (comparator.compare(name, indexInfo.firstName) < 0) - continue; - ranges.add(indexInfo); - } - - file.mark(); - for (IndexHelper.IndexInfo indexInfo : ranges) - { - file.reset(); - long curOffsert = file.skipBytes((int)indexInfo.offset); - assert curOffsert == indexInfo.offset; - // TODO only completely deserialize columns we are interested in - while (file.bytesPastMark() < indexInfo.offset + indexInfo.width) - { - final IColumn column = cf.getColumnSerializer().deserialize(file); - // we check vs the original Set, not the filtered List, for efficiency - if (columnNames.contains(column.name())) - { - cf.addColumn(column); - } - } - } - } - - /** - * Check the list of column names against the bloom filter - * @param file File to read bloom filter from - * @param columnNames Column names to filter - * @return List of columns that exist in the bloom filter - * @throws IOException - */ - private List getFilteredColumns(FileDataInput file, SortedSet columnNames) throws IOException - { - /* Read the bloom filter summarizing the columns */ - BloomFilter bf = IndexHelper.defreezeBloomFilter(file); - List filteredColumnNames = new ArrayList(columnNames.size()); - for (byte[] name : columnNames) - { - if (bf.isPresent(name)) - { - filteredColumnNames.add(name); - } - } - return filteredColumnNames; - } - public ColumnFamily getColumnFamily() { return cf; diff --git a/src/java/org/apache/cassandra/db/filter/SSTableSliceIterator.java b/src/java/org/apache/cassandra/db/filter/SSTableSliceIterator.java index 3a1e5f4981..38d8716908 100644 --- a/src/java/org/apache/cassandra/db/filter/SSTableSliceIterator.java +++ b/src/java/org/apache/cassandra/db/filter/SSTableSliceIterator.java @@ -120,7 +120,7 @@ class SSTableSliceIterator extends AbstractIterator implements IColumnI public ColumnFamily getColumnFamily() { - return reader.getEmptyColumnFamily(); + return reader == null ? null : reader.getEmptyColumnFamily(); } protected IColumn computeNext() diff --git a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java index 09aed14a52..0ee71b25c5 100644 --- a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java +++ b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java @@ -87,9 +87,14 @@ public class ColumnFamilyStoreTest extends CleanupHelper public void runMayThrow() throws IOException { QueryFilter sliceFilter = QueryFilter.getSliceFilter(Util.dk("key1"), new QueryPath("Standard2", null, null), ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, null, false, 1); - assertNull(store.getColumnFamily(sliceFilter)); + ColumnFamily cf = store.getColumnFamily(sliceFilter); + assert cf.isMarkedForDelete(); + assert cf.getColumnsMap().isEmpty(); + QueryFilter namesFilter = QueryFilter.getNamesFilter(Util.dk("key1"), new QueryPath("Standard2", null, null), "a".getBytes()); - assertNull(store.getColumnFamily(namesFilter)); + cf = store.getColumnFamily(namesFilter); + assert cf.isMarkedForDelete(); + assert cf.getColumnsMap().isEmpty(); } };