blob: 1ceacbc11f98e7ac42dcfabdbbc17c27b2c43ccb [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.rows;
import java.util.Arrays;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.commons.lang3.StringUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.base.Joiner;
public class RowsMergingTest extends CQLTester
{
@BeforeClass
public static void setUpClass()
{
System.setProperty("cassandra.btree.branchshift", "2");
CQLTester.setUpClass();
}
@Test
public void testInsertion() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, a, b, c, d, e, f) VALUES (?, 1, 1, 1, 1, 1, 1, 1) USING TIMESTAMP 1001",
"INSERT INTO %s (pk, ck, a, b, c, d) VALUES (?, 1, 2, 2, 2, 2) USING TIMESTAMP 1002")
.expectedRow(1, 2, 2, 2, 2, 1, 1)
.check();
}
@Test
public void testRowDeletions() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, a, b, c, d, e, f) VALUES (?, 1, 1, 1, 1, 1, 1, 1) USING TIMESTAMP 1001",
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1",
"INSERT INTO %s (pk, ck, a) VALUES (?, 1, 2) USING TIMESTAMP 1003")
.expectedRow(1, 2, null, null, null, null, null)
.check();
}
@Test
public void testRowDeletionWithUpdate() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, a, b, c, d, e, f) VALUES (?, 1, 1, 1, 1, 1, 1, 1) USING TIMESTAMP 1001",
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1",
"UPDATE %s USING TIMESTAMP 1003 SET a = 2 WHERE pk = ? AND ck = 1")
.expectedRow(1, 2, null, null, null, null, null)
.check();
}
@Test
public void testRowDeletionWithOnlyUpdates() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("UPDATE %s USING TIMESTAMP 1001 SET a = 1, b = 1, c = 1, d = 1, e = 1, f = 1 WHERE pk = ? AND ck = 1",
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1",
"UPDATE %s USING TIMESTAMP 1003 SET a = 2 WHERE pk = ? AND ck = 1")
.expectedRow(1, 2, null, null, null, null, null)
.check();
}
@Test
public void testRowWithMultipleDeletions() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, a, b, c, d, e, f) VALUES (?, 1, 1, 1, 1, 1, 1, 1) USING TIMESTAMP 1001",
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1",
"UPDATE %s USING TIMESTAMP 1003 SET a = 2 WHERE pk = ? AND ck = 1",
"DELETE FROM %s USING TIMESTAMP 1004 WHERE pk = ? AND ck = 1")
.check();
}
@Test
public void testRowWithComplexCollectionOverride() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, s set<text>, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, s) VALUES (?, 1, {'a', 'b', 'c'}) USING TIMESTAMP 1001",
"UPDATE %s USING TIMESTAMP 1002 SET s = {'m', 'n'} WHERE pk = ? AND ck = 1")
.expectedRow(1, set("m", "n"))
.check();
}
@Test
public void testRowWithDeletionAndComplexCollection() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, s set<text>, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, s) VALUES (?, 1, {'a', 'b', 'c'}) USING TIMESTAMP 1001",
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1")
.check();
}
@Test
public void testRowWithDeletionAndComplexCollectionOverride() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, s set<text>, PRIMARY KEY (pk, ck))")
.queries("INSERT INTO %s (pk, ck, s) VALUES (?, 1, {'a', 'b', 'c'}) USING TIMESTAMP 1001",
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1",
"UPDATE %s USING TIMESTAMP 1003 SET s = {'m', 'n'} WHERE pk = ? AND ck = 1")
.expectedRow(1, set("m", "n"))
.check();
}
@Test
public void testRowWithComplexDeletionAfterRowDeletion() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, s set<text>, PRIMARY KEY (pk, ck))")
.queries("DELETE FROM %s USING TIMESTAMP 1001 WHERE pk = ? AND ck = 1",
"INSERT INTO %s (pk, ck, s) VALUES (?, 1, {'a', 'b', 'c'}) USING TIMESTAMP 1002",
"DELETE s FROM %s USING TIMESTAMP 1003 WHERE pk = ? AND ck = 1")
.expectedRow(1, null)
.check();
}
@Test
public void testRowDeletionsWithBatchWithBatchTimestamp() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("BEGIN BATCH USING TIMESTAMP 1001 \n" +
"INSERT INTO %s (pk, ck, a, b, c, d, e, f) VALUES (?, 1, 1, 1, 1, 1, 1, 1); \n" +
"DELETE FROM %s WHERE pk = ? AND ck = 1; \n" +
"APPLY BATCH;",
"INSERT INTO %s (pk, ck, a) VALUES (?, 1, 2) USING TIMESTAMP 1002")
.expectedRow(1, 2, null, null, null, null, null)
.check();
}
@Test
public void testRowDeletionsWithBatchWithTimestampPerOperation() throws Throwable
{
checker().schema("CREATE TABLE %s (pk int, ck int, a int, b int, c int, d int, e int, f int, PRIMARY KEY (pk, ck))")
.queries("BEGIN BATCH \n" +
"INSERT INTO %s (pk, ck, a, b, c, d, e, f) VALUES (?, 1, 1, 1, 1, 1, 1, 1) USING TIMESTAMP 1001 ; \n" +
"DELETE FROM %s USING TIMESTAMP 1002 WHERE pk = ? AND ck = 1; \n" +
"APPLY BATCH;",
"INSERT INTO %s (pk, ck, a) VALUES (?, 1, 2) USING TIMESTAMP 1003")
.expectedRow(1, 2, null, null, null, null, null)
.check();
}
public MergeChecker checker()
{
return new MergeChecker();
}
/**
* Utility class to check that a merge result in the expected row not no matter in which order the operation are applied.
*/
private class MergeChecker
{
private int pk;
private String schema;
private String[] queries;
private Object[] expectedRow;
public MergeChecker schema(String schema)
{
this.schema = schema;
return this;
}
public MergeChecker queries(String... queries)
{
this.queries = queries;
return this;
}
public MergeChecker expectedRow(Object... columnValues)
{
this.expectedRow = new Object[columnValues.length + 1];
System.arraycopy(columnValues, 0, this.expectedRow, 1, columnValues.length);
return this;
}
public void check() throws Throwable
{
createTable(schema);
checkAllPermutations(queries.length, queries);
}
private void check(String[] queries) throws Throwable
{
for (String query : queries)
{
try
{
int count = StringUtils.countMatches(query, "%s");
Object[] parameters1 = new Object[count];
Arrays.fill(parameters1, pk);
Object[] parameters = parameters1;
executeFormattedQuery(formatQueries(query, count), parameters);
}
catch (Throwable e)
{
throw new AssertionError("Executing the following queries did not lead to the expected result: \n"
+ Joiner.on("; \n").join(queries)
+ "\n when executing: \n" + query, e);
}
}
try
{
if (expectedRow != null)
{
expectedRow[0] = pk;
assertRows(execute("SELECT * FROM %s WHERE pk = ?" , pk),
expectedRow);
}
else
{
assertEmpty(execute("SELECT * FROM %s WHERE pk = ?" , pk));
}
}
catch (Throwable e)
{
throw new AssertionError("Executing the following queries did not lead to the expected result: \n" + Joiner.on("; \n").join(queries), e);
}
pk++;
}
private String formatQueries(String query, int numberOfqueries)
{
String table = keyspace() + '.' + currentTable();
return String.format(query, createFilledArray(numberOfqueries, table));
}
private Object[] createFilledArray(int length, Object fillingValue)
{
Object[] tables = new Object[length];
Arrays.fill(tables, fillingValue);
return tables;
}
private void checkAllPermutations(int n, String[] queries) throws Throwable
{
if (n == 1)
{
check(queries);
}
else
{
for (int i = 0, m = n - 1; i < m; i++)
{
checkAllPermutations(n - 1, queries);
if ((i & 1) == 0)
{
swap(queries, i, n - 1);
}
else
{
swap(queries, 0, n - 1);
}
}
checkAllPermutations(n - 1, queries);
}
}
private void swap(String[] queries, int i, int j)
{
String tmp = queries[i];
queries[i] = queries[j];
queries[j] = tmp;
}
}
}