/* * 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.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.TreeMap; import com.google.common.collect.Iterables; import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.db.composites.CellName; import org.apache.cassandra.db.context.CounterContext; import org.apache.cassandra.io.sstable.ColumnStats; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.CounterId; import org.apache.cassandra.utils.FBUtilities; import static junit.framework.Assert.assertTrue; import static org.apache.cassandra.Util.column; import static org.apache.cassandra.Util.cellname; import static org.apache.cassandra.Util.tombstone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; public class ColumnFamilyTest extends SchemaLoader { static int version = MessagingService.current_version; // TODO test SuperColumns more @Test public void testSingleColumn() throws IOException { ColumnFamily cf; cf = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); cf.addColumn(column("C", "v", 1)); DataOutputBuffer bufOut = new DataOutputBuffer(); ColumnFamily.serializer.serialize(cf, bufOut, version); ByteArrayInputStream bufIn = new ByteArrayInputStream(bufOut.getData(), 0, bufOut.getLength()); cf = ColumnFamily.serializer.deserialize(new DataInputStream(bufIn), version); assert cf != null; assert cf.metadata().cfName.equals("Standard1"); assert cf.getSortedColumns().size() == 1; } @Test public void testManyColumns() throws IOException { ColumnFamily cf; TreeMap<String, String> map = new TreeMap<>(); for (int i = 100; i < 1000; ++i) { map.put(Integer.toString(i), "Avinash Lakshman is a good man: " + i); } // write cf = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); DataOutputBuffer bufOut = new DataOutputBuffer(); for (String cName : map.navigableKeySet()) { cf.addColumn(column(cName, map.get(cName), 314)); } ColumnFamily.serializer.serialize(cf, bufOut, version); // verify ByteArrayInputStream bufIn = new ByteArrayInputStream(bufOut.getData(), 0, bufOut.getLength()); cf = ColumnFamily.serializer.deserialize(new DataInputStream(bufIn), version); for (String cName : map.navigableKeySet()) { ByteBuffer val = cf.getColumn(cellname(cName)).value(); assert new String(val.array(),val.position(),val.remaining()).equals(map.get(cName)); } assert Iterables.size(cf.getColumnNames()) == map.size(); } @Test public void testGetColumnCount() { ColumnFamily cf = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); cf.addColumn(column("col1", "", 1)); cf.addColumn(column("col2", "", 2)); cf.addColumn(column("col1", "", 3)); assert 2 == cf.getColumnCount(); assert 2 == cf.getSortedColumns().size(); } @Test public void testDigest() { ColumnFamily cf = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); ColumnFamily cf2 = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); ByteBuffer digest = ColumnFamily.digest(cf); cf.addColumn(column("col1", "", 1)); cf2.addColumn(column("col1", "", 1)); assert !digest.equals(ColumnFamily.digest(cf)); digest = ColumnFamily.digest(cf); assert digest.equals(ColumnFamily.digest(cf2)); cf.addColumn(column("col2", "", 2)); assert !digest.equals(ColumnFamily.digest(cf)); digest = ColumnFamily.digest(cf); cf.addColumn(column("col1", "", 3)); assert !digest.equals(ColumnFamily.digest(cf)); digest = ColumnFamily.digest(cf); cf.delete(new DeletionTime(4, 4)); assert !digest.equals(ColumnFamily.digest(cf)); digest = ColumnFamily.digest(cf); cf.delete(tombstone("col1", "col11", 5, 5)); assert !digest.equals(ColumnFamily.digest(cf)); digest = ColumnFamily.digest(cf); assert digest.equals(ColumnFamily.digest(cf)); cf.delete(tombstone("col2", "col21", 5, 5)); assert !digest.equals(ColumnFamily.digest(cf)); digest = ColumnFamily.digest(cf); cf.delete(tombstone("col1", "col11", 5, 5)); // this does not change RangeTombstoneLList assert digest.equals(ColumnFamily.digest(cf)); } @Test public void testTimestamp() { ColumnFamily cf = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); cf.addColumn(column("col1", "val1", 2)); cf.addColumn(column("col1", "val2", 2)); // same timestamp, new value cf.addColumn(column("col1", "val3", 1)); // older timestamp -- should be ignored assert ByteBufferUtil.bytes("val2").equals(cf.getColumn(cellname("col1")).value()); } @Test public void testMergeAndAdd() { ColumnFamily cf_new = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); ColumnFamily cf_old = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); ColumnFamily cf_result = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); ByteBuffer val = ByteBufferUtil.bytes("sample value"); ByteBuffer val2 = ByteBufferUtil.bytes("x value "); cf_new.addColumn(cellname("col1"), val, 3); cf_new.addColumn(cellname("col2"), val, 4); cf_old.addColumn(cellname("col2"), val2, 1); cf_old.addColumn(cellname("col3"), val2, 2); cf_result.addAll(cf_new); cf_result.addAll(cf_old); assert 3 == cf_result.getColumnCount() : "Count is " + cf_new.getColumnCount(); //addcolumns will only add if timestamp >= old timestamp assert val.equals(cf_result.getColumn(cellname("col2")).value()); // check that tombstone wins timestamp ties cf_result.addTombstone(cellname("col1"), 0, 3); assertFalse(cf_result.getColumn(cellname("col1")).isLive()); cf_result.addColumn(cellname("col1"), val2, 3); assertFalse(cf_result.getColumn(cellname("col1")).isLive()); // check that column value wins timestamp ties in absence of tombstone cf_result.addColumn(cellname("col3"), val, 2); assert cf_result.getColumn(cellname("col3")).value().equals(val2); cf_result.addColumn(cellname("col3"), ByteBufferUtil.bytes("z"), 2); assert cf_result.getColumn(cellname("col3")).value().equals(ByteBufferUtil.bytes("z")); } @Test public void testColumnStatsRecordsRowDeletesCorrectly() { long timestamp = System.currentTimeMillis(); int localDeletionTime = (int) (System.currentTimeMillis() / 1000); ColumnFamily cf = ArrayBackedSortedColumns.factory.create("Keyspace1", "Standard1"); cf.delete(new DeletionInfo(timestamp, localDeletionTime)); ColumnStats stats = cf.getColumnStats(); assertEquals(timestamp, stats.maxTimestamp); cf.delete(new RangeTombstone(cellname("col2"), cellname("col21"), timestamp, localDeletionTime)); stats = cf.getColumnStats(); assertEquals(ByteBufferUtil.bytes("col2"), stats.minColumnNames.get(0)); assertEquals(ByteBufferUtil.bytes("col21"), stats.maxColumnNames.get(0)); cf.delete(new RangeTombstone(cellname("col6"), cellname("col61"), timestamp, localDeletionTime)); stats = cf.getColumnStats(); assertEquals(ByteBufferUtil.bytes("col2"), stats.minColumnNames.get(0)); assertEquals(ByteBufferUtil.bytes("col61"), stats.maxColumnNames.get(0)); } @Test public void testCounterDeletion() { long timestamp = FBUtilities.timestampMicros(); CellName name = cellname("counter1"); BufferCounterCell counter = new BufferCounterCell(name, CounterContext.instance().createGlobal(CounterId.fromInt(1), 1, 1), timestamp); BufferDeletedCell tombstone = new BufferDeletedCell(name, (int) (System.currentTimeMillis() / 1000), 0L); // check that the tombstone won the reconcile despite the counter cell having a higher timestamp assertTrue(counter.reconcile(tombstone) == tombstone); // check that a range tombstone overrides the counter cell, even with a lower timestamp than the counter ColumnFamily cf0 = ArrayBackedSortedColumns.factory.create("Keyspace1", "Counter1"); cf0.addColumn(counter); cf0.delete(new RangeTombstone(cellname("counter0"), cellname("counter2"), 0L, (int) (System.currentTimeMillis() / 1000))); assertTrue(cf0.deletionInfo().isDeleted(counter)); assertTrue(cf0.deletionInfo().inOrderTester(false).isDeleted(counter)); // check that a top-level deletion info overrides the counter cell, even with a lower timestamp than the counter ColumnFamily cf1 = ArrayBackedSortedColumns.factory.create("Keyspace1", "Counter1"); cf1.addColumn(counter); cf1.delete(new DeletionInfo(0L, (int) (System.currentTimeMillis() / 1000))); assertTrue(cf1.deletionInfo().isDeleted(counter)); } }