/* * 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.clock; import static org.junit.Assert.*; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import org.junit.Test; import org.apache.cassandra.Util; import org.apache.cassandra.db.Column; import org.apache.cassandra.db.DeletedColumn; import org.apache.cassandra.db.IClock; import org.apache.cassandra.db.IncrementCounterClock; import org.apache.cassandra.utils.FBUtilities; public class IncrementCounterReconcilerTest { private static final IncrementCounterReconciler reconciler = IncrementCounterReconciler.instance; private static final IncrementCounterContext icc = new IncrementCounterContext(); @Test public void testReconcileNormal() { IncrementCounterClock leftClock; IncrementCounterClock rightClock; Column left; Column right; Column reconciled; List<IClock> clocks; // normal + normal leftClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(10L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(27L), FBUtilities.toByteArray(1), FBUtilities.toByteArray(128L), FBUtilities.toByteArray(9), FBUtilities.toByteArray(62L), FBUtilities.toByteArray(5), FBUtilities.toByteArray(32L) )); left = new Column( "x".getBytes(), icc.total(leftClock.context()), leftClock); rightClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(7L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(9L), FBUtilities.toByteArray(1), FBUtilities.toByteArray(32L), FBUtilities.toByteArray(5), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(6), FBUtilities.toByteArray(2L) )); right = new Column( "x".getBytes(), icc.total(rightClock.context()), rightClock); reconciled = reconciler.reconcile(left, right); clocks = new LinkedList<IClock>(); clocks.add(rightClock); assert FBUtilities.compareByteArrays( ((IncrementCounterClock)leftClock.getSuperset(clocks)).context(), ((IncrementCounterClock)reconciled.clock()).context() ) == 0; // local: 27L+9L // 1: 128L // 5: 32L // 6: 2L // 9: 62L assert FBUtilities.compareByteArrays( FBUtilities.toByteArray((27L+9L)+128L+32L+2L+62L), reconciled.value() ) == 0; assert reconciled.isMarkedForDelete() == false; } @Test public void testReconcileMixed() { // note: check priority of delete vs. normal // if delete has a later timestamp, treat row as deleted // if normal has a later timestamp, ignore delete IncrementCounterClock leftClock; IncrementCounterClock rightClock; Column left; Column right; Column reconciled; List<IClock> clocks; // normal + delete: normal has higher timestamp leftClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(44L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(1), FBUtilities.toByteArray(128L), FBUtilities.toByteArray(9), FBUtilities.toByteArray(62L), FBUtilities.toByteArray(5), FBUtilities.toByteArray(32L) )); left = new Column( "x".getBytes(), "live".getBytes(), leftClock); rightClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(0L) )); right = new DeletedColumn( "x".getBytes(), ByteBuffer.allocate(4).putInt(124).array(), // localDeleteTime secs rightClock); reconciled = reconciler.reconcile(left, right); assert FBUtilities.compareByteArrays( ((IncrementCounterClock)leftClock).context(), ((IncrementCounterClock)reconciled.clock()).context() ) == 0; assert FBUtilities.compareByteArrays( "live".getBytes(), reconciled.value() ) == 0; assert reconciled.isMarkedForDelete() == false; // normal + delete: delete has higher timestamp leftClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(4L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(1), FBUtilities.toByteArray(128L), FBUtilities.toByteArray(9), FBUtilities.toByteArray(62L), FBUtilities.toByteArray(5), FBUtilities.toByteArray(32L) )); left = new Column( "x".getBytes(), "live".getBytes(), leftClock); rightClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(100L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(0L) )); right = new DeletedColumn( "x".getBytes(), ByteBuffer.allocate(4).putInt(139).array(), // localDeleteTime secs rightClock); reconciled = reconciler.reconcile(left, right); assert FBUtilities.compareByteArrays( ((IncrementCounterClock)rightClock).context(), ((IncrementCounterClock)reconciled.clock()).context() ) == 0; assert FBUtilities.compareByteArrays( ByteBuffer.allocate(4).putInt(139).array(), reconciled.value() ) == 0; assert reconciled.isMarkedForDelete() == true; // delete + normal: delete has higher timestamp leftClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(0L), FBUtilities.toByteArray(100L) )); left = new DeletedColumn( "x".getBytes(), ByteBuffer.allocate(4).putInt(139).array(), // localDeleteTime secs leftClock); rightClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(4L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(1), FBUtilities.toByteArray(128L), FBUtilities.toByteArray(9), FBUtilities.toByteArray(62L), FBUtilities.toByteArray(5), FBUtilities.toByteArray(32L) )); right = new Column( "x".getBytes(), "live".getBytes(), rightClock); reconciled = reconciler.reconcile(left, right); assert FBUtilities.compareByteArrays( ((IncrementCounterClock)leftClock).context(), ((IncrementCounterClock)reconciled.clock()).context() ) == 0; assert FBUtilities.compareByteArrays( ByteBuffer.allocate(4).putInt(139).array(), reconciled.value() ) == 0; assert reconciled.isMarkedForDelete() == true; // delete + normal: normal has higher timestamp leftClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(0L) )); left = new DeletedColumn( "x".getBytes(), ByteBuffer.allocate(4).putInt(124).array(), // localDeleteTime secs leftClock); rightClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(44L), FBUtilities.toByteArray(0L), FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(1), FBUtilities.toByteArray(128L), FBUtilities.toByteArray(9), FBUtilities.toByteArray(62L), FBUtilities.toByteArray(5), FBUtilities.toByteArray(32L) )); right = new Column( "x".getBytes(), "live".getBytes(), rightClock); reconciled = reconciler.reconcile(left, right); assert FBUtilities.compareByteArrays( ((IncrementCounterClock)rightClock).context(), ((IncrementCounterClock)reconciled.clock()).context() ) == 0; assert FBUtilities.compareByteArrays( "live".getBytes(), reconciled.value() ) == 0; assert reconciled.isMarkedForDelete() == false; } @Test public void testReconcileDeleted() { IncrementCounterClock leftClock; IncrementCounterClock rightClock; // note: merge clocks + take later localDeleteTime Column left; Column right; Column reconciled; List<IClock> clocks; // delete + delete leftClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L) )); left = new DeletedColumn( "x".getBytes(), ByteBuffer.allocate(4).putInt(139).array(), // localDeleteTime secs leftClock); rightClock = new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(6L), FBUtilities.toByteArray(0L) )); right = new DeletedColumn( "x".getBytes(), ByteBuffer.allocate(4).putInt(124).array(), // localDeleteTime secs rightClock); reconciled = reconciler.reconcile(left, right); clocks = new LinkedList<IClock>(); clocks.add(rightClock); assert FBUtilities.compareByteArrays( ((IncrementCounterClock)leftClock.getSuperset(clocks)).context(), ((IncrementCounterClock)reconciled.clock()).context() ) == 0; assert FBUtilities.compareByteArrays( FBUtilities.toByteArray(139), reconciled.value() ) == 0; assert reconciled.isMarkedForDelete() == true; } @Test public void testReconcileDeletedOrder() { byte[] columnName = "col".getBytes(); // value: 1. timestamp 10. delete timestamp 0 Column c1 = new Column( columnName, FBUtilities.toByteArray(1L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(10L), FBUtilities.toByteArray(0L) ))); // value: 0. timestamp 7. delete timestamp 0 Column c2 = new DeletedColumn( columnName, FBUtilities.toByteArray(0L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(7L), FBUtilities.toByteArray(0L) ))); // value: 2. timestamp 3. delete timestamp 0 Column c3 = new Column( columnName, FBUtilities.toByteArray(2L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L) ))); // should return same value no matter the order of reconciliation Column r1 = reconciler.reconcile(c3, c2); Column r2 = reconciler.reconcile(r1, c1); assertEquals(1, FBUtilities.byteArrayToLong(r2.value())); // a previous version of the code reconciled away the delete information in this test and the result were 3 instead of 1 // value: 1. timestamp 10. delete timestamp 0 c1 = new Column( columnName, FBUtilities.toByteArray(1L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(10L), FBUtilities.toByteArray(0L) ))); // value: 0. timestamp 7. delete timestamp 0 c2 = new DeletedColumn( columnName, FBUtilities.toByteArray(0L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(7L), FBUtilities.toByteArray(0L) ))); // value: 2. timestamp 3. delete timestamp 0 c3 = new Column( columnName, FBUtilities.toByteArray(2L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L) ))); r1 = reconciler.reconcile(c1, c2); r2 = reconciler.reconcile(r1, c3); assertEquals(1, FBUtilities.byteArrayToLong(r2.value())); // check that the correct delete timestamp is pulled into the reconciled column // value: 1. timestamp 10. delete timestamp 8 c1 = new Column( columnName, FBUtilities.toByteArray(1L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(10L), FBUtilities.toByteArray(8L) ))); // value: 0. timestamp 7. delete timestamp 0 c2 = new DeletedColumn( columnName, FBUtilities.toByteArray(0L), new IncrementCounterClock(Util.concatByteArrays( FBUtilities.toByteArray(7L), FBUtilities.toByteArray(0L) ))); r1 = reconciler.reconcile(c1, c2); IncrementCounterClock clock = (IncrementCounterClock) r1.clock(); assertEquals(8, FBUtilities.byteArrayToLong(clock.context, IncrementCounterContext.TIMESTAMP_LENGTH)); } }