/* * Copyright 2016-2017 the original author or authors. * * Licensed 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.springframework.cassandra.core.cql.generator; import static org.assertj.core.api.Assertions.*; import static org.springframework.cassandra.core.cql.CqlIdentifier.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cassandra.core.ReservedKeyword; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.cassandra.core.keyspace.CreateTableSpecification; import org.springframework.cassandra.core.keyspace.Option; import org.springframework.cassandra.core.keyspace.TableOption; import org.springframework.cassandra.core.keyspace.TableOption.CachingOption; import org.springframework.cassandra.core.keyspace.TableOption.CompactionOption; import org.springframework.cassandra.core.keyspace.TableOption.CompressionOption; import org.springframework.cassandra.core.keyspace.TableOption.KeyCachingOption; import com.datastax.driver.core.DataType; /** * Unit tests for {@link CreateTableCqlGenerator}. * * @author Matthew T. Adams * @author David Webb */ public class CreateTableCqlGeneratorUnitTests { private static final Logger log = LoggerFactory.getLogger(CreateTableCqlGeneratorUnitTests.class); /** * Asserts that the preamble is first & correctly formatted in the given CQL string. */ public static void assertPreamble(CqlIdentifier tableName, String cql) { assertThat(cql.startsWith("CREATE TABLE " + tableName + " ")).isTrue(); } /** * Asserts that the given primary key definition is contained in the given CQL string properly. * * @param primaryKeyString IE, "foo", "foo, bar, baz", "(foo, bar), baz", etc */ public static void assertPrimaryKey(String primaryKeyString, String cql) { assertThat(cql.contains(", PRIMARY KEY (" + primaryKeyString + "))")).isTrue(); } /** * Asserts that the given list of columns definitions are contained in the given CQL string properly. * * @param columnSpec IE, "foo text, bar blob" */ public static void assertColumns(String columnSpec, String cql) { assertThat(cql.contains("(" + columnSpec + ",")).isTrue(); } /** * Asserts that the read repair change is set properly */ public static void assertStringOption(String name, String value, String cql) { log.info(name + " -> " + value); assertThat(cql.contains(name + " = '" + value + "'")).isTrue(); } /** * Asserts that the option is set */ public static void assertDoubleOption(String name, Double value, String cql) { log.info(name + " -> " + value); assertThat(cql.contains(name + " = " + value)).isTrue(); } public static void assertLongOption(String name, Long value, String cql) { log.info(name + " -> " + value); assertThat(cql.contains(name + " = " + value)).isTrue(); } /** * Asserts that the read repair change is set properly */ public static void assertNullOption(String name, String cql) { log.info(name); assertThat(cql.contains(" " + name + " ")).isTrue(); } /** * Convenient base class that other test classes can use so as not to repeat the generics declarations or * {@link #generator()} method. */ public static abstract class CreateTableTest extends AbstractTableOperationCqlGeneratorTest<CreateTableSpecification, CreateTableCqlGenerator> { @Override public CreateTableCqlGenerator generator() { return new CreateTableCqlGenerator(specification); } } public static class BasicTest extends CreateTableTest { public CqlIdentifier name = cqlId("mytable"); public DataType partitionKeyType0 = DataType.text(); public CqlIdentifier partitionKey0 = cqlId("partitionKey0"); public DataType columnType1 = DataType.text(); public String column1 = "column1"; @Override public CreateTableSpecification specification() { return CreateTableSpecification.createTable().name(name).partitionKeyColumn(partitionKey0, partitionKeyType0) .column(column1, columnType1); } @Test public void test() { prepare(); assertPreamble(name, cql); assertColumns(String.format("%s %s, %s %s", partitionKey0, partitionKeyType0, column1, columnType1), cql); assertPrimaryKey(partitionKey0.toCql(), cql); } } public static class CompositePartitionKeyTest extends CreateTableTest { public CqlIdentifier name = cqlId("composite_partition_key_table"); public DataType partKeyType0 = DataType.text(); public CqlIdentifier partKey0 = cqlId("partKey0"); public DataType partKeyType1 = DataType.text(); public CqlIdentifier partKey1 = cqlId("partKey1"); public CqlIdentifier column0 = cqlId("column0"); public DataType columnType0 = DataType.text(); @Override public CreateTableSpecification specification() { return CreateTableSpecification.createTable().name(name).partitionKeyColumn(partKey0, partKeyType0) .partitionKeyColumn(partKey1, partKeyType1).column(column0, columnType0); } @Test public void test() { prepare(); assertPreamble(name, cql); assertColumns( String.format("%s %s, %s %s, %s %s", partKey0, partKeyType0, partKey1, partKeyType1, column0, columnType0), cql); assertPrimaryKey(String.format("(%s, %s)", partKey0, partKey1), cql); } } /** * Test just the Read Repair Chance * * @author David Webb */ public static class ReadRepairChanceTest extends CreateTableTest { public CqlIdentifier name = cqlId("mytable"); public DataType partitionKeyType0 = DataType.text(); public CqlIdentifier partitionKey0 = cqlId("partitionKey0"); public DataType partitionKeyType1 = DataType.timestamp(); public CqlIdentifier partitionKey1 = cqlId("create_timestamp"); public DataType columnType1 = DataType.text(); public CqlIdentifier column1 = cqlId("column1"); public Double readRepairChance = 0.5; @Override public CreateTableSpecification specification() { return CreateTableSpecification.createTable().name(name).partitionKeyColumn(partitionKey0, partitionKeyType0) .partitionKeyColumn(partitionKey1, partitionKeyType1).column(column1, columnType1) .with(TableOption.READ_REPAIR_CHANCE, readRepairChance); } @Test public void test() { prepare(); assertPreamble(name, cql); assertColumns(String.format("%s %s, %s %s, %s %s", partitionKey0, partitionKeyType0, partitionKey1, partitionKeyType1, column1, columnType1), cql); assertPrimaryKey(String.format("(%s, %s)", partitionKey0, partitionKey1), cql); assertDoubleOption(TableOption.READ_REPAIR_CHANCE.getName(), readRepairChance, cql); } } /** * Fully test all available create table options * * @author David Webb */ public static class MultipleOptionsTest extends CreateTableTest { public CqlIdentifier name = cqlId("timeseries_table"); public DataType partitionKeyType0 = DataType.timeuuid(); public CqlIdentifier partitionKey0 = cqlId("tid"); public DataType partitionKeyType1 = DataType.timestamp(); public CqlIdentifier partitionKey1 = cqlId("create_timestamp"); public DataType columnType1 = DataType.text(); public CqlIdentifier column1 = cqlId("data_point"); public Double readRepairChance = 0.5; public Double dcLocalReadRepairChance = 0.7; public Double bloomFilterFpChance = 0.001; public Boolean replcateOnWrite = Boolean.FALSE; public Long gcGraceSeconds = 600l; public String comment = "This is My Table"; public Map<Option, Object> compactionMap = new LinkedHashMap<>(); public Map<Option, Object> compressionMap = new LinkedHashMap<>(); public Map<Option, Object> cachingMap = new LinkedHashMap<>(); @Override public CreateTableSpecification specification() { // Compaction compactionMap.put(CompactionOption.CLASS, "SizeTieredCompactionStrategy"); compactionMap.put(CompactionOption.MIN_THRESHOLD, "4"); // Compression compressionMap.put(CompressionOption.SSTABLE_COMPRESSION, "SnappyCompressor"); compressionMap.put(CompressionOption.CHUNK_LENGTH_KB, 128); compressionMap.put(CompressionOption.CRC_CHECK_CHANCE, 0.75); // Caching cachingMap.put(CachingOption.KEYS, KeyCachingOption.ALL); cachingMap.put(CachingOption.ROWS_PER_PARTITION, "NONE"); return CreateTableSpecification.createTable().name(name).partitionKeyColumn(partitionKey0, partitionKeyType0) .partitionKeyColumn(partitionKey1, partitionKeyType1).column(column1, columnType1) .with(TableOption.COMPACT_STORAGE).with(TableOption.READ_REPAIR_CHANCE, readRepairChance) .with(TableOption.COMPACTION, compactionMap).with(TableOption.COMPRESSION, compressionMap) .with(TableOption.BLOOM_FILTER_FP_CHANCE, bloomFilterFpChance).with(TableOption.CACHING, cachingMap) .with(TableOption.COMMENT, comment).with(TableOption.DCLOCAL_READ_REPAIR_CHANCE, dcLocalReadRepairChance) .with(TableOption.GC_GRACE_SECONDS, gcGraceSeconds); } @Test public void test() { prepare(); log.info(cql); assertPreamble(name, cql); assertColumns(String.format("%s %s, %s %s, %s %s", partitionKey0, partitionKeyType0, partitionKey1, partitionKeyType1, column1, columnType1), cql); assertPrimaryKey(String.format("(%s, %s)", partitionKey0, partitionKey1), cql); assertNullOption(TableOption.COMPACT_STORAGE.getName(), cql); assertDoubleOption(TableOption.READ_REPAIR_CHANCE.getName(), readRepairChance, cql); assertDoubleOption(TableOption.DCLOCAL_READ_REPAIR_CHANCE.getName(), dcLocalReadRepairChance, cql); assertDoubleOption(TableOption.BLOOM_FILTER_FP_CHANCE.getName(), bloomFilterFpChance, cql); assertStringOption(TableOption.COMMENT.getName(), comment, cql); assertLongOption(TableOption.GC_GRACE_SECONDS.getName(), gcGraceSeconds, cql); } } public static class FunkyTableNameTest { public static final List<String> FUNKY_LEGAL_NAMES; static { List<String> funkies = new ArrayList<>(Arrays.asList(new String[] { /* TODO */ })); // TODO: should these work? "a \"\" x", "a\"\"\"\"x", "a b" FUNKY_LEGAL_NAMES = Collections.unmodifiableList(Arrays.stream(ReservedKeyword.values()) // .map(Enum::name) // .collect(Collectors.toList())); } @Test public void test() { for (String name : FUNKY_LEGAL_NAMES) { new TableNameTest(name).test(); } } } /** * This class is supposed to be used by other test classes. */ public static class TableNameTest extends CreateTableTest { public String tableName; public TableNameTest(String tableName) { this.tableName = tableName; } @Override public CreateTableSpecification specification() { return CreateTableSpecification.createTable().name(tableName).partitionKeyColumn(cqlId("pk"), DataType.text()); } /** * There is no @Test annotation on this method on purpose! It's supposed to be called by another test class's @Test * method so that you can loop, calling this test method as many times as are necessary. */ public void test() { prepare(); assertPreamble(cqlId(tableName), cql); } } }