blob: e3f4884c0ade8a96aa50c2bc04c8324b4020b4f6 [file] [log] [blame]
/*
* 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.HashMap;
import java.util.Map;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class RowTest
{
private static final String KEYSPACE1 = "RowTest";
private static final String CF_STANDARD1 = "Standard1";
private int nowInSeconds;
private DecoratedKey dk;
private ColumnFamilyStore cfs;
private CFMetaData cfm;
@BeforeClass
public static void defineSchema() throws ConfigurationException
{
CFMetaData cfMetadata = CFMetaData.Builder.create(KEYSPACE1, CF_STANDARD1)
.addPartitionKey("key", BytesType.instance)
.addClusteringColumn("col1", AsciiType.instance)
.addRegularColumn("a", AsciiType.instance)
.addRegularColumn("b", AsciiType.instance)
.build();
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace(KEYSPACE1,
KeyspaceParams.simple(1),
cfMetadata);
}
@Before
public void setup()
{
nowInSeconds = FBUtilities.nowInSeconds();
dk = Util.dk("key0");
cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1);
cfm = cfs.metadata;
}
@Test
public void testMergeRangeTombstones() throws InterruptedException
{
PartitionUpdate update1 = new PartitionUpdate(cfm, dk, cfm.partitionColumns(), 1);
writeRangeTombstone(update1, "1", "11", 123, 123);
writeRangeTombstone(update1, "2", "22", 123, 123);
writeRangeTombstone(update1, "3", "31", 123, 123);
writeRangeTombstone(update1, "4", "41", 123, 123);
PartitionUpdate update2 = new PartitionUpdate(cfm, dk, cfm.partitionColumns(), 1);
writeRangeTombstone(update2, "1", "11", 123, 123);
writeRangeTombstone(update2, "111", "112", 1230, 123);
writeRangeTombstone(update2, "2", "24", 123, 123);
writeRangeTombstone(update2, "3", "31", 1230, 123);
writeRangeTombstone(update2, "4", "41", 123, 1230);
writeRangeTombstone(update2, "5", "51", 123, 1230);
try (UnfilteredRowIterator merged = UnfilteredRowIterators.merge(ImmutableList.of(update1.unfilteredIterator(), update2.unfilteredIterator()), nowInSeconds))
{
Object[][] expected = new Object[][]{ { "1", "11", 123l, 123 },
{ "111", "112", 1230l, 123 },
{ "2", "24", 123l, 123 },
{ "3", "31", 1230l, 123 },
{ "4", "41", 123l, 1230 },
{ "5", "51", 123l, 1230 } };
int i = 0;
while (merged.hasNext())
{
RangeTombstoneBoundMarker openMarker = (RangeTombstoneBoundMarker)merged.next();
Slice.Bound openBound = openMarker.clustering();
DeletionTime openDeletion = new DeletionTime(openMarker.deletionTime().markedForDeleteAt(),
openMarker.deletionTime().localDeletionTime());
RangeTombstoneBoundMarker closeMarker = (RangeTombstoneBoundMarker)merged.next();
Slice.Bound closeBound = closeMarker.clustering();
DeletionTime closeDeletion = new DeletionTime(closeMarker.deletionTime().markedForDeleteAt(),
closeMarker.deletionTime().localDeletionTime());
assertEquals(openDeletion, closeDeletion);
assertRangeTombstoneMarkers(openBound, closeBound, openDeletion, expected[i++]);
}
}
}
@Test
public void testResolve()
{
ColumnDefinition defA = cfm.getColumnDefinition(new ColumnIdentifier("a", true));
ColumnDefinition defB = cfm.getColumnDefinition(new ColumnIdentifier("b", true));
Row.Builder builder = BTreeRow.unsortedBuilder(nowInSeconds);
builder.newRow(cfm.comparator.make("c1"));
writeSimpleCellValue(builder, cfm, defA, "a1", 0);
writeSimpleCellValue(builder, cfm, defA, "a2", 1);
writeSimpleCellValue(builder, cfm, defB, "b1", 1);
Row row = builder.build();
PartitionUpdate update = PartitionUpdate.singleRowUpdate(cfm, dk, row);
Unfiltered unfiltered = update.unfilteredIterator().next();
assertTrue(unfiltered.kind() == Unfiltered.Kind.ROW);
row = (Row) unfiltered;
assertEquals("a2", defA.cellValueType().getString(row.getCell(defA).value()));
assertEquals("b1", defB.cellValueType().getString(row.getCell(defB).value()));
assertEquals(2, row.columns().size());
}
@Test
public void testExpiringColumnExpiration() throws IOException
{
int ttl = 1;
ColumnDefinition def = cfm.getColumnDefinition(new ColumnIdentifier("a", true));
Cell cell = BufferCell.expiring(def, 0, ttl, nowInSeconds, ((AbstractType) def.cellValueType()).decompose("a1"));
PartitionUpdate update = PartitionUpdate.singleRowUpdate(cfm, dk, BTreeRow.singleCellRow(cfm.comparator.make("c1"), cell));
new Mutation(update).applyUnsafe();
// when we read with a nowInSeconds before the cell has expired,
// the PartitionIterator includes the row we just wrote
Row row = Util.getOnlyRow(Util.cmd(cfs, dk).includeRow("c1").withNowInSeconds(nowInSeconds).build());
assertEquals("a1", ByteBufferUtil.string(row.getCell(def).value()));
// when we read with a nowInSeconds after the cell has expired, the row is filtered
// so the PartitionIterator is empty
Util.assertEmpty(Util.cmd(cfs, dk).includeRow("c1").withNowInSeconds(nowInSeconds + ttl + 1).build());
}
@Test
public void testHashCode()
{
ColumnDefinition defA = cfm.getColumnDefinition(new ColumnIdentifier("a", true));
ColumnDefinition defB = cfm.getColumnDefinition(new ColumnIdentifier("b", true));
Row.Builder builder = BTreeRow.unsortedBuilder(nowInSeconds);
builder.newRow(cfm.comparator.make("c1"));
writeSimpleCellValue(builder, cfm, defA, "a1", 0);
writeSimpleCellValue(builder, cfm, defA, "a2", 1);
writeSimpleCellValue(builder, cfm, defB, "b1", 1);
Row row = builder.build();
Map<Row, Integer> map = new HashMap<>();
map.put(row, 1);
assertEquals(Integer.valueOf(1), map.get(row));
}
private void assertRangeTombstoneMarkers(Slice.Bound start, Slice.Bound end, DeletionTime deletionTime, Object[] expected)
{
AbstractType clusteringType = (AbstractType)cfm.comparator.subtype(0);
assertEquals(1, start.size());
assertEquals(start.kind(), Slice.Bound.Kind.INCL_START_BOUND);
assertEquals(expected[0], clusteringType.getString(start.get(0)));
assertEquals(1, end.size());
assertEquals(end.kind(), Slice.Bound.Kind.INCL_END_BOUND);
assertEquals(expected[1], clusteringType.getString(end.get(0)));
assertEquals(expected[2], deletionTime.markedForDeleteAt());
assertEquals(expected[3], deletionTime.localDeletionTime());
}
public void writeRangeTombstone(PartitionUpdate update, Object start, Object end, long markedForDeleteAt, int localDeletionTime)
{
ClusteringComparator comparator = cfs.getComparator();
update.add(new RangeTombstone(Slice.make(comparator.make(start), comparator.make(end)), new DeletionTime(markedForDeleteAt, localDeletionTime)));
}
private void writeSimpleCellValue(Row.Builder builder,
CFMetaData cfm,
ColumnDefinition columnDefinition,
String value,
long timestamp)
{
builder.addCell(BufferCell.live(cfm, columnDefinition, timestamp, ((AbstractType) columnDefinition.cellValueType()).decompose(value)));
}
}