| /* |
| * 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; |
| } |
| } |
| } |