/* * 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.IOException; import java.util.*; import java.util.concurrent.ExecutionException; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import static org.junit.Assert.assertNull; import org.junit.Test; import static junit.framework.Assert.assertEquals; import org.apache.cassandra.CleanupHelper; import org.apache.cassandra.Util; import org.apache.cassandra.db.columniterator.IdentityQueryFilter; import org.apache.cassandra.db.filter.*; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.thrift.IndexClause; import org.apache.cassandra.thrift.IndexExpression; import org.apache.cassandra.thrift.IndexOperator; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.WrappedRunnable; import java.net.InetAddress; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.CollatingOrderPreservingPartitioner; import org.apache.cassandra.io.sstable.SSTableReader; public class ColumnFamilyStoreTest extends CleanupHelper { static byte[] bytes1, bytes2; static { Random random = new Random(); bytes1 = new byte[1024]; bytes2 = new byte[128]; random.nextBytes(bytes1); random.nextBytes(bytes2); } @Test public void testGetColumnWithWrongBF() throws IOException, ExecutionException, InterruptedException { List<RowMutation> rms = new LinkedList<RowMutation>(); RowMutation rm; rm = new RowMutation("Keyspace1", "key1".getBytes()); rm.add(new QueryPath("Standard1", null, "Column1".getBytes()), "asdf".getBytes(), new TimestampClock(0)); rm.add(new QueryPath("Standard1", null, "Column2".getBytes()), "asdf".getBytes(), new TimestampClock(0)); rms.add(rm); ColumnFamilyStore store = Util.writeColumnFamily(rms); Table table = Table.open("Keyspace1"); List<SSTableReader> ssTables = table.getAllSSTables(); assertEquals(1, ssTables.size()); ssTables.get(0).forceFilterFailures(); ColumnFamily cf = store.getColumnFamily(QueryFilter.getIdentityFilter(Util.dk("key2"), new QueryPath("Standard1", null, "Column1".getBytes()))); assertNull(cf); } @Test public void testEmptyRow() throws Exception { Table table = Table.open("Keyspace1"); final ColumnFamilyStore store = table.getColumnFamilyStore("Standard2"); RowMutation rm; rm = new RowMutation("Keyspace1", "key1".getBytes()); rm.delete(new QueryPath("Standard2", null, null), new TimestampClock(System.currentTimeMillis())); rm.apply(); Runnable r = new WrappedRunnable() { 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, false, 1); 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()); cf = store.getColumnFamily(namesFilter); assert cf.isMarkedForDelete(); assert cf.getColumnsMap().isEmpty(); } }; TableTest.reTest(store, r); } /** * Writes out a bunch of keys into an SSTable, then runs anticompaction on a range. * Checks to see if anticompaction returns true. */ private void testAntiCompaction(String columnFamilyName, int insertsPerTable) throws IOException, ExecutionException, InterruptedException { List<RowMutation> rms = new ArrayList<RowMutation>(); for (int j = 0; j < insertsPerTable; j++) { String key = String.valueOf(j); RowMutation rm = new RowMutation("Keyspace1", key.getBytes()); rm.add(new QueryPath(columnFamilyName, null, "0".getBytes()), new byte[0], new TimestampClock(j)); rms.add(rm); } ColumnFamilyStore store = Util.writeColumnFamily(rms); List<Range> ranges = new ArrayList<Range>(); IPartitioner partitioner = new CollatingOrderPreservingPartitioner(); Range r = Util.range(partitioner, "0", "zzzzzzz"); ranges.add(r); List<SSTableReader> fileList = CompactionManager.instance.submitAnticompaction(store, ranges, InetAddress.getByName("127.0.0.1")).get(); assert fileList.size() >= 1; } @Test public void testAntiCompaction1() throws IOException, ExecutionException, InterruptedException { testAntiCompaction("Standard1", 100); } @Test public void testSkipStartKey() throws IOException, ExecutionException, InterruptedException { ColumnFamilyStore cfs = insertKey1Key2(); IPartitioner p = StorageService.getPartitioner(); List<Row> result = cfs.getRangeSlice(ArrayUtils.EMPTY_BYTE_ARRAY, Util.range(p, "key1", "key2"), 10, new NamesQueryFilter("asdf".getBytes())); assertEquals(1, result.size()); assert Arrays.equals(result.get(0).key.key, "key2".getBytes()); } @Test public void testIndexScan() throws IOException { RowMutation rm; rm = new RowMutation("Keyspace1", "k1".getBytes()); rm.add(new QueryPath("Indexed1", null, "notbirthdate".getBytes("UTF8")), FBUtilities.toByteArray(1L), new TimestampClock(0)); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(1L), new TimestampClock(0)); rm.apply(); rm = new RowMutation("Keyspace1", "k2".getBytes()); rm.add(new QueryPath("Indexed1", null, "notbirthdate".getBytes("UTF8")), FBUtilities.toByteArray(2L), new TimestampClock(0)); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(2L), new TimestampClock(0)); rm.apply(); rm = new RowMutation("Keyspace1", "k3".getBytes()); rm.add(new QueryPath("Indexed1", null, "notbirthdate".getBytes("UTF8")), FBUtilities.toByteArray(2L), new TimestampClock(0)); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(1L), new TimestampClock(0)); rm.apply(); rm = new RowMutation("Keyspace1", "k4aaaa".getBytes()); rm.add(new QueryPath("Indexed1", null, "notbirthdate".getBytes("UTF8")), FBUtilities.toByteArray(2L), new TimestampClock(0)); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(3L), new TimestampClock(0)); rm.apply(); // basic single-expression query IndexExpression expr = new IndexExpression("birthdate".getBytes("UTF8"), IndexOperator.EQ, FBUtilities.toByteArray(1L)); IndexClause clause = new IndexClause(Arrays.asList(expr), ArrayUtils.EMPTY_BYTE_ARRAY, 100); IFilter filter = new IdentityQueryFilter(); IPartitioner p = StorageService.getPartitioner(); Range range = new Range(p.getMinimumToken(), p.getMinimumToken()); List<Row> rows = Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, filter); assert rows != null; assert rows.size() == 2 : StringUtils.join(rows, ","); assert Arrays.equals("k1".getBytes(), rows.get(0).key.key); assert Arrays.equals("k3".getBytes(), rows.get(1).key.key); assert Arrays.equals(FBUtilities.toByteArray(1L), rows.get(0).cf.getColumn("birthdate".getBytes("UTF8")).value()); assert Arrays.equals(FBUtilities.toByteArray(1L), rows.get(1).cf.getColumn("birthdate".getBytes("UTF8")).value()); // add a second expression IndexExpression expr2 = new IndexExpression("notbirthdate".getBytes("UTF8"), IndexOperator.GTE, FBUtilities.toByteArray(2L)); clause = new IndexClause(Arrays.asList(expr, expr2), ArrayUtils.EMPTY_BYTE_ARRAY, 100); rows = Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, filter); assert rows.size() == 1 : StringUtils.join(rows, ","); assert Arrays.equals("k3".getBytes(), rows.get(0).key.key); // same query again, but with resultset not including the subordinate expression rows = Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, new NamesQueryFilter("birthdate".getBytes("UTF8"))); assert rows.size() == 1 : StringUtils.join(rows, ","); assert Arrays.equals("k3".getBytes(), rows.get(0).key.key); assert rows.get(0).cf.getColumnCount() == 1 : rows.get(0).cf; // once more, this time with a slice rowset that needs to be expanded SliceQueryFilter sqf = new SliceQueryFilter(ArrayUtils.EMPTY_BYTE_ARRAY, ArrayUtils.EMPTY_BYTE_ARRAY, false, 0); rows = Table.open("Keyspace1").getColumnFamilyStore("Indexed1").scan(clause, range, sqf); assert rows.size() == 1 : StringUtils.join(rows, ","); assert Arrays.equals("k3".getBytes(), rows.get(0).key.key); assert rows.get(0).cf.getColumnCount() == 0; } @Test public void testIndexUpdate() throws IOException { Table table = Table.open("Keyspace2"); // create a row and update the birthdate value, test that the index query fetches the new version RowMutation rm; rm = new RowMutation("Keyspace2", "k1".getBytes()); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(1L), new TimestampClock(1)); rm.apply(); rm = new RowMutation("Keyspace2", "k1".getBytes()); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(2L), new TimestampClock(2)); rm.apply(); IndexExpression expr = new IndexExpression("birthdate".getBytes("UTF8"), IndexOperator.EQ, FBUtilities.toByteArray(1L)); IndexClause clause = new IndexClause(Arrays.asList(expr), ArrayUtils.EMPTY_BYTE_ARRAY, 100); IFilter filter = new IdentityQueryFilter(); IPartitioner p = StorageService.getPartitioner(); Range range = new Range(p.getMinimumToken(), p.getMinimumToken()); List<Row> rows = table.getColumnFamilyStore("Indexed1").scan(clause, range, filter); assert rows.size() == 0; expr = new IndexExpression("birthdate".getBytes("UTF8"), IndexOperator.EQ, FBUtilities.toByteArray(2L)); clause = new IndexClause(Arrays.asList(expr), ArrayUtils.EMPTY_BYTE_ARRAY, 100); rows = table.getColumnFamilyStore("Indexed1").scan(clause, range, filter); assert Arrays.equals("k1".getBytes(), rows.get(0).key.key); // update the birthdate value with an OLDER timestamp, and test that the index ignores this rm = new RowMutation("Keyspace2", "k1".getBytes()); rm.add(new QueryPath("Indexed1", null, "birthdate".getBytes("UTF8")), FBUtilities.toByteArray(3L), new TimestampClock(0)); rm.apply(); rows = table.getColumnFamilyStore("Indexed1").scan(clause, range, filter); assert Arrays.equals("k1".getBytes(), rows.get(0).key.key); } private ColumnFamilyStore insertKey1Key2() throws IOException, ExecutionException, InterruptedException { List<RowMutation> rms = new LinkedList<RowMutation>(); RowMutation rm; rm = new RowMutation("Keyspace2", "key1".getBytes()); rm.add(new QueryPath("Standard1", null, "Column1".getBytes()), "asdf".getBytes(), new TimestampClock(0)); rms.add(rm); Util.writeColumnFamily(rms); rm = new RowMutation("Keyspace2", "key2".getBytes()); rm.add(new QueryPath("Standard1", null, "Column1".getBytes()), "asdf".getBytes(), new TimestampClock(0)); rms.add(rm); return Util.writeColumnFamily(rms); } }