/* * 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; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; import org.junit.Before; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.google.common.collect.Iterators; import org.apache.cassandra.*; import org.apache.cassandra.config.*; import org.apache.cassandra.cql3.Operator; import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.rows.*; import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.metrics.ClearableHistogram; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.WrappedRunnable; import static junit.framework.Assert.assertNotNull; @RunWith(OrderedJUnit4ClassRunner.class) public class ColumnFamilyStoreTest { public static final String KEYSPACE1 = "ColumnFamilyStoreTest1"; public static final String KEYSPACE2 = "ColumnFamilyStoreTest2"; public static final String CF_STANDARD1 = "Standard1"; public static final String CF_STANDARD2 = "Standard2"; public static final String CF_SUPER1 = "Super1"; public static final String CF_SUPER6 = "Super6"; public static final String CF_INDEX1 = "Indexed1"; @BeforeClass public static void defineSchema() throws ConfigurationException { SchemaLoader.prepareServer(); SchemaLoader.createKeyspace(KEYSPACE1, KeyspaceParams.simple(1), SchemaLoader.standardCFMD(KEYSPACE1, CF_STANDARD1), SchemaLoader.standardCFMD(KEYSPACE1, CF_STANDARD2), SchemaLoader.keysIndexCFMD(KEYSPACE1, CF_INDEX1, true)); // TODO: Fix superCFMD failing on legacy table creation. Seems to be applying composite comparator to partition key // SchemaLoader.superCFMD(KEYSPACE1, CF_SUPER1, LongType.instance)); // SchemaLoader.superCFMD(KEYSPACE1, CF_SUPER6, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", LexicalUUIDType.instance, UTF8Type.instance), SchemaLoader.createKeyspace(KEYSPACE2, KeyspaceParams.simple(1), SchemaLoader.standardCFMD(KEYSPACE2, CF_STANDARD1)); } @Before public void truncateCFS() { Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1).truncateBlocking(); Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD2).truncateBlocking(); // Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_SUPER1).truncateBlocking(); Keyspace.open(KEYSPACE2).getColumnFamilyStore(CF_STANDARD1).truncateBlocking(); } @Test // create two sstables, and verify that we only deserialize data from the most recent one public void testTimeSortedQuery() { Keyspace keyspace = Keyspace.open(KEYSPACE1); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD1); new RowUpdateBuilder(cfs.metadata, 0, "key1") .clustering("Column1") .add("val", "asdf") .build() .applyUnsafe(); cfs.forceBlockingFlush(); new RowUpdateBuilder(cfs.metadata, 1, "key1") .clustering("Column1") .add("val", "asdf") .build() .applyUnsafe(); cfs.forceBlockingFlush(); ((ClearableHistogram)cfs.metric.sstablesPerReadHistogram.cf).clear(); // resets counts Util.getAll(Util.cmd(cfs, "key1").includeRow("c1").build()); assertEquals(1, cfs.metric.sstablesPerReadHistogram.cf.getCount()); } @Test public void testGetColumnWithWrongBF() { Keyspace keyspace = Keyspace.open(KEYSPACE1); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD1); List<Mutation> rms = new LinkedList<>(); rms.add(new RowUpdateBuilder(cfs.metadata, 0, "key1") .clustering("Column1") .add("val", "asdf") .build()); Util.writeColumnFamily(rms); List<SSTableReader> ssTables = keyspace.getAllSSTables(SSTableSet.LIVE); assertEquals(1, ssTables.size()); ssTables.get(0).forceFilterFailures(); Util.assertEmpty(Util.cmd(cfs, "key2").build()); } @Test public void testEmptyRow() throws Exception { Keyspace keyspace = Keyspace.open(KEYSPACE1); final ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD2); RowUpdateBuilder.deleteRow(cfs.metadata, FBUtilities.timestampMicros(), "key1", "Column1").applyUnsafe(); Runnable r = new WrappedRunnable() { public void runMayThrow() throws IOException { Row toCheck = Util.getOnlyRowUnfiltered(Util.cmd(cfs, "key1").build()); Iterator<Cell> iter = toCheck.cells().iterator(); assert(Iterators.size(iter) == 0); } }; reTest(cfs, r); } // TODO: Implement this once we have hooks to super columns available in CQL context // @Test // public void testDeleteSuperRowSticksAfterFlush() throws Throwable // { // String keyspaceName = KEYSPACE1; // String cfName= CF_SUPER1; // // Keyspace keyspace = Keyspace.open(keyspaceName); // ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(cfName); // // ByteBuffer scfName = ByteBufferUtil.bytes("SuperDuper"); // DecoratedKey key = Util.dk("flush-resurrection"); // // // create an isolated sstable. // putColSuper(cfs, key, 0, ByteBufferUtil.bytes("val"), ByteBufferUtil.bytes(1L), ByteBufferUtil.bytes(1L), ByteBufferUtil.bytes("val1")); // putColsSuper(cfs, key, scfName, // new BufferCell(cellname(1L), ByteBufferUtil.bytes("val1"), 1), // new BufferCell(cellname(2L), ByteBufferUtil.bytes("val2"), 1), // new BufferCell(cellname(3L), ByteBufferUtil.bytes("val3"), 1)); // cfs.forceBlockingFlush(); // // // insert, don't flush. // putColsSuper(cfs, key, scfName, // new BufferCell(cellname(4L), ByteBufferUtil.bytes("val4"), 1), // new BufferCell(cellname(5L), ByteBufferUtil.bytes("val5"), 1), // new BufferCell(cellname(6L), ByteBufferUtil.bytes("val6"), 1)); // // // verify insert. // final SlicePredicate sp = new SlicePredicate(); // sp.setSlice_range(new SliceRange()); // sp.getSlice_range().setCount(100); // sp.getSlice_range().setStart(ArrayUtils.EMPTY_BYTE_ARRAY); // sp.getSlice_range().setFinish(ArrayUtils.EMPTY_BYTE_ARRAY); // // assertRowAndColCount(1, 6, false, cfs.getRangeSlice(Util.range("f", "g"), null, ThriftValidation.asIFilter(sp, cfs.metadata, scfName), 100)); // // // delete // Mutation rm = new Mutation(keyspace.getName(), key.getKey()); // rm.deleteRange(cfName, SuperColumns.startOf(scfName), SuperColumns.endOf(scfName), 2); // rm.applyUnsafe(); // // // verify delete. // assertRowAndColCount(1, 0, false, cfs.getRangeSlice(Util.range("f", "g"), null, ThriftValidation.asIFilter(sp, cfs.metadata, scfName), 100)); // // // flush // cfs.forceBlockingFlush(); // // // re-verify delete. // assertRowAndColCount(1, 0, false, cfs.getRangeSlice(Util.range("f", "g"), null, ThriftValidation.asIFilter(sp, cfs.metadata, scfName), 100)); // // // late insert. // putColsSuper(cfs, key, scfName, // new BufferCell(cellname(4L), ByteBufferUtil.bytes("val4"), 1L), // new BufferCell(cellname(7L), ByteBufferUtil.bytes("val7"), 1L)); // // // re-verify delete. // assertRowAndColCount(1, 0, false, cfs.getRangeSlice(Util.range("f", "g"), null, ThriftValidation.asIFilter(sp, cfs.metadata, scfName), 100)); // // // make sure new writes are recognized. // putColsSuper(cfs, key, scfName, // new BufferCell(cellname(3L), ByteBufferUtil.bytes("val3"), 3), // new BufferCell(cellname(8L), ByteBufferUtil.bytes("val8"), 3), // new BufferCell(cellname(9L), ByteBufferUtil.bytes("val9"), 3)); // assertRowAndColCount(1, 3, false, cfs.getRangeSlice(Util.range("f", "g"), null, ThriftValidation.asIFilter(sp, cfs.metadata, scfName), 100)); // } // private static void assertRowAndColCount(int rowCount, int colCount, boolean isDeleted, Collection<Row> rows) throws CharacterCodingException // { // assert rows.size() == rowCount : "rowcount " + rows.size(); // for (Row row : rows) // { // assert row.cf != null : "cf was null"; // assert row.cf.getColumnCount() == colCount : "colcount " + row.cf.getColumnCount() + "|" + str(row.cf); // if (isDeleted) // assert row.cf.isMarkedForDelete() : "cf not marked for delete"; // } // } // // private static String str(ColumnFamily cf) throws CharacterCodingException // { // StringBuilder sb = new StringBuilder(); // for (Cell col : cf.getSortedColumns()) // sb.append(String.format("(%s,%s,%d),", ByteBufferUtil.string(col.name().toByteBuffer()), ByteBufferUtil.string(col.value()), col.timestamp())); // return sb.toString(); // } @Test public void testDeleteStandardRowSticksAfterFlush() throws Throwable { // test to make sure flushing after a delete doesn't resurrect delted cols. String keyspaceName = KEYSPACE1; String cfName = CF_STANDARD1; Keyspace keyspace = Keyspace.open(keyspaceName); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(cfName); ByteBuffer col = ByteBufferUtil.bytes("val"); ByteBuffer val = ByteBufferUtil.bytes("val1"); // insert ColumnDefinition newCol = ColumnDefinition.regularDef(cfs.metadata, ByteBufferUtil.bytes("val2"), AsciiType.instance); new RowUpdateBuilder(cfs.metadata, 0, "key1").clustering("Column1").add("val", "val1").build().applyUnsafe(); new RowUpdateBuilder(cfs.metadata, 0, "key2").clustering("Column1").add("val", "val1").build().applyUnsafe(); assertRangeCount(cfs, col, val, 2); // flush. cfs.forceBlockingFlush(); // insert, don't flush new RowUpdateBuilder(cfs.metadata, 1, "key3").clustering("Column1").add("val", "val1").build().applyUnsafe(); new RowUpdateBuilder(cfs.metadata, 1, "key4").clustering("Column1").add("val", "val1").build().applyUnsafe(); assertRangeCount(cfs, col, val, 4); // delete (from sstable and memtable) RowUpdateBuilder.deleteRow(cfs.metadata, 5, "key1", "Column1").applyUnsafe(); RowUpdateBuilder.deleteRow(cfs.metadata, 5, "key3", "Column1").applyUnsafe(); // verify delete assertRangeCount(cfs, col, val, 2); // flush cfs.forceBlockingFlush(); // re-verify delete. // first breakage is right here because of CASSANDRA-1837. assertRangeCount(cfs, col, val, 2); // simulate a 'late' insertion that gets put in after the deletion. should get inserted, but fail on read. new RowUpdateBuilder(cfs.metadata, 2, "key1").clustering("Column1").add("val", "val1").build().applyUnsafe(); new RowUpdateBuilder(cfs.metadata, 2, "key3").clustering("Column1").add("val", "val1").build().applyUnsafe(); // should still be nothing there because we deleted this row. 2nd breakage, but was undetected because of 1837. assertRangeCount(cfs, col, val, 2); // make sure that new writes are recognized. new RowUpdateBuilder(cfs.metadata, 10, "key5").clustering("Column1").add("val", "val1").build().applyUnsafe(); new RowUpdateBuilder(cfs.metadata, 10, "key6").clustering("Column1").add("val", "val1").build().applyUnsafe(); assertRangeCount(cfs, col, val, 4); // and it remains so after flush. (this wasn't failing before, but it's good to check.) cfs.forceBlockingFlush(); assertRangeCount(cfs, col, val, 4); } @Test public void testClearEphemeralSnapshots() throws Throwable { // We don't do snapshot-based repair on Windows so we don't have ephemeral snapshots from repair that need clearing. // This test will fail as we'll revert to the WindowsFailedSnapshotTracker and counts will be off, but since we // don't do snapshot-based repair on Windows, we just skip this test. Assume.assumeTrue(!FBUtilities.isWindows()); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_INDEX1); //cleanup any previous test gargbage cfs.clearSnapshot(""); int numRows = 1000; long[] colValues = new long [numRows * 2]; // each row has two columns for (int i = 0; i < colValues.length; i+=2) { colValues[i] = (i % 4 == 0 ? 1L : 2L); // index column colValues[i+1] = 3L; //other column } ScrubTest.fillIndexCF(cfs, false, colValues); cfs.snapshot("nonEphemeralSnapshot", null, false); cfs.snapshot("ephemeralSnapshot", null, true); Map<String, Pair<Long, Long>> snapshotDetails = cfs.getSnapshotDetails(); assertEquals(2, snapshotDetails.size()); assertTrue(snapshotDetails.containsKey("ephemeralSnapshot")); assertTrue(snapshotDetails.containsKey("nonEphemeralSnapshot")); ColumnFamilyStore.clearEphemeralSnapshots(cfs.getDirectories()); snapshotDetails = cfs.getSnapshotDetails(); assertEquals(1, snapshotDetails.size()); assertTrue(snapshotDetails.containsKey("nonEphemeralSnapshot")); //test cleanup cfs.clearSnapshot(""); } @Test public void testBackupAfterFlush() throws Throwable { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE2).getColumnFamilyStore(CF_STANDARD1); new RowUpdateBuilder(cfs.metadata, 0, ByteBufferUtil.bytes("key1")).clustering("Column1").add("val", "asdf").build().applyUnsafe(); cfs.forceBlockingFlush(); new RowUpdateBuilder(cfs.metadata, 0, ByteBufferUtil.bytes("key2")).clustering("Column1").add("val", "asdf").build().applyUnsafe(); cfs.forceBlockingFlush(); for (int version = 1; version <= 2; ++version) { Descriptor existing = new Descriptor(cfs.getDirectories().getDirectoryForNewSSTables(), KEYSPACE2, CF_STANDARD1, version); Descriptor desc = new Descriptor(Directories.getBackupsDirectory(existing), KEYSPACE2, CF_STANDARD1, version); for (Component c : new Component[]{ Component.DATA, Component.PRIMARY_INDEX, Component.FILTER, Component.STATS }) assertTrue("Cannot find backed-up file:" + desc.filenameFor(c), new File(desc.filenameFor(c)).exists()); } } // TODO: Fix once we have working supercolumns in 8099 // // CASSANDRA-3467. the key here is that supercolumn and subcolumn comparators are different // @Test // public void testSliceByNamesCommandOnUUIDTypeSCF() throws Throwable // { // String keyspaceName = KEYSPACE1; // String cfName = CF_SUPER6; // ByteBuffer superColName = LexicalUUIDType.instance.fromString("a4ed3562-0e8e-4b41-bdfd-c45a2774682d"); // Keyspace keyspace = Keyspace.open(keyspaceName); // ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(cfName); // DecoratedKey key = Util.dk("slice-get-uuid-type"); // // // Insert a row with one supercolumn and multiple subcolumns // putColsSuper(cfs, key, superColName, new BufferCell(cellname("a"), ByteBufferUtil.bytes("A"), 1), // new BufferCell(cellname("b"), ByteBufferUtil.bytes("B"), 1)); // // // Get the entire supercolumn like normal // ColumnFamily cfGet = cfs.getColumnFamily(QueryFilter.getIdentityFilter(key, cfName, System.currentTimeMillis())); // assertEquals(ByteBufferUtil.bytes("A"), cfGet.getColumn(CellNames.compositeDense(superColName, ByteBufferUtil.bytes("a"))).value()); // assertEquals(ByteBufferUtil.bytes("B"), cfGet.getColumn(CellNames.compositeDense(superColName, ByteBufferUtil.bytes("b"))).value()); // // // Now do the SliceByNamesCommand on the supercolumn, passing both subcolumns in as columns to get // SortedSet<CellName> sliceColNames = new TreeSet<CellName>(cfs.metadata.comparator); // sliceColNames.add(CellNames.compositeDense(superColName, ByteBufferUtil.bytes("a"))); // sliceColNames.add(CellNames.compositeDense(superColName, ByteBufferUtil.bytes("b"))); // SliceByNamesReadCommand cmd = new SliceByNamesReadCommand(keyspaceName, key.getKey(), cfName, System.currentTimeMillis(), new NamesQueryFilter(sliceColNames)); // ColumnFamily cfSliced = cmd.getRow(keyspace).cf; // // // Make sure the slice returns the same as the straight get // assertEquals(ByteBufferUtil.bytes("A"), cfSliced.getColumn(CellNames.compositeDense(superColName, ByteBufferUtil.bytes("a"))).value()); // assertEquals(ByteBufferUtil.bytes("B"), cfSliced.getColumn(CellNames.compositeDense(superColName, ByteBufferUtil.bytes("b"))).value()); // } // TODO: Fix once SSTableSimpleWriter's back in // @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-6086">CASSANDRA-6086</a> // TODO: Fix once SSTableSimpleWriter's back in // @Test // public void testLoadNewSSTablesAvoidsOverwrites() throws Throwable // { // String ks = KEYSPACE1; // String cf = CF_STANDARD1; // ColumnFamilyStore cfs = Keyspace.open(ks).getColumnFamilyStore(cf); // SSTableDeletingTask.waitForDeletions(); // // final CFMetaData cfmeta = Schema.instance.getCFMetaData(ks, cf); // Directories dir = new Directories(cfs.metadata); // // // clear old SSTables (probably left by CFS.clearUnsafe() calls in other tests) // for (Map.Entry<Descriptor, Set<Component>> entry : dir.sstableLister().list().entrySet()) // { // for (Component component : entry.getValue()) // { // FileUtils.delete(entry.getKey().filenameFor(component)); // } // } // // // sanity check // int existingSSTables = dir.sstableLister().list().keySet().size(); // assert existingSSTables == 0 : String.format("%d SSTables unexpectedly exist", existingSSTables); // // ByteBuffer key = bytes("key"); // // SSTableSimpleWriter writer = new SSTableSimpleWriter(dir.getDirectoryForNewSSTables(), // cfmeta, StorageService.getPartitioner()) // { // @Override // protected SSTableWriter getWriter() // { // // hack for reset generation // generation.set(0); // return super.getWriter(); // } // }; // writer.newRow(key); // writer.addColumn(bytes("col"), bytes("val"), 1); // writer.close(); // // writer = new SSTableSimpleWriter(dir.getDirectoryForNewSSTables(), // cfmeta, StorageService.getPartitioner()); // writer.newRow(key); // writer.addColumn(bytes("col"), bytes("val"), 1); // writer.close(); // // Set<Integer> generations = new HashSet<>(); // for (Descriptor descriptor : dir.sstableLister().list().keySet()) // generations.add(descriptor.generation); // // // we should have two generations: [1, 2] // assertEquals(2, generations.size()); // assertTrue(generations.contains(1)); // assertTrue(generations.contains(2)); // // assertEquals(0, cfs.getLiveSSTables().size()); // // // start the generation counter at 1 again (other tests have incremented it already) // cfs.resetFileIndexGenerator(); // // boolean incrementalBackupsEnabled = DatabaseDescriptor.isIncrementalBackupsEnabled(); // try // { // // avoid duplicate hardlinks to incremental backups // DatabaseDescriptor.setIncrementalBackupsEnabled(false); // cfs.loadNewSSTables(); // } // finally // { // DatabaseDescriptor.setIncrementalBackupsEnabled(incrementalBackupsEnabled); // } // // assertEquals(2, cfs.getLiveSSTables().size()); // generations = new HashSet<>(); // for (Descriptor descriptor : dir.sstableLister().list().keySet()) // generations.add(descriptor.generation); // // // normally they would get renamed to generations 1 and 2, but since those filenames already exist, // // they get skipped and we end up with generations 3 and 4 // assertEquals(2, generations.size()); // assertTrue(generations.contains(3)); // assertTrue(generations.contains(4)); // } public void reTest(ColumnFamilyStore cfs, Runnable verify) throws Exception { verify.run(); cfs.forceBlockingFlush(); verify.run(); } private void assertRangeCount(ColumnFamilyStore cfs, ByteBuffer col, ByteBuffer val, int count) { assertRangeCount(cfs, cfs.metadata.getColumnDefinition(col), val, count); } private void assertRangeCount(ColumnFamilyStore cfs, ColumnDefinition col, ByteBuffer val, int count) { int found = 0; if (count != 0) { for (FilteredPartition partition : Util.getAll(Util.cmd(cfs).filterOn(col.name.toString(), Operator.EQ, val).build())) { for (Row r : partition) { if (r.getCell(col).value().equals(val)) ++found; } } } assertEquals(count, found); } @Test public void testScrubDataDirectories() throws Throwable { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1); ColumnFamilyStore.scrubDataDirectories(cfs.metadata); new RowUpdateBuilder(cfs.metadata, 2, "key").clustering("name").add("val", "2").build().applyUnsafe(); cfs.forceBlockingFlush(); // Nuke the metadata and reload that sstable Collection<SSTableReader> ssTables = cfs.getLiveSSTables(); assertEquals(1, ssTables.size()); SSTableReader ssTable = ssTables.iterator().next(); String dataFileName = ssTable.descriptor.filenameFor(Component.DATA); String tmpDataFileName = ssTable.descriptor.tmpFilenameFor(Component.DATA); new File(dataFileName).renameTo(new File(tmpDataFileName)); ssTable.selfRef().release(); ColumnFamilyStore.scrubDataDirectories(cfs.metadata); List<File> ssTableFiles = new Directories(cfs.metadata).sstableLister(Directories.OnTxnErr.THROW).listFiles(); assertNotNull(ssTableFiles); assertEquals(0, ssTableFiles.size()); } }