diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java index 420924579..b711c5a41 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java @@ -23,6 +23,7 @@ public class CqlStringUtils { protected static final String DOUBLE_SINGLE_QUOTE = "\'\'"; protected static final String DOUBLE_QUOTE = "\""; protected static final String DOUBLE_DOUBLE_QUOTE = "\"\""; + protected static final String EMPTY_STRING = ""; public static StringBuilder noNull(StringBuilder sb) { return sb == null ? new StringBuilder() : sb; @@ -129,4 +130,11 @@ public class CqlStringUtils { return thing == null ? (String) null : new StringBuilder().append(DOUBLE_QUOTE).append(thing).append(DOUBLE_QUOTE) .toString(); } + + /** + * Removed single quotes from quoted String option values + */ + public static String removeSingleQuotes(Object thing) { + return thing == null ? (String) null : ((String) thing).replaceAll(SINGLE_QUOTE, EMPTY_STRING); + } } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateIndexSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateIndexSpecification.java index 7c8a86426..1f9f89d46 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateIndexSpecification.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/CreateIndexSpecification.java @@ -107,4 +107,12 @@ public class CreateIndexSpecification extends IndexNameSpecificationreplicate_on_write */ - REPLICATE_ON_WRITE("replicate_on_write", Boolean.class, true, false, false), + REPLICATE_ON_WRITE("replicate_on_write", Boolean.class, true, false, true), /** * caching * * @see CachingOption */ - CACHING("caching", CachingOption.class, true, false, false), + CACHING("caching", CachingOption.class, true, false, true), /** * bloom_filter_fp_chance */ @@ -152,6 +152,10 @@ public enum TableOption implements Option { * @author Matthew T. Adams */ public enum CompactionOption implements Option { + /** + * tombstone_threshold + */ + CLASS("class", String.class, true, false, true), /** * tombstone_threshold */ @@ -242,7 +246,7 @@ public enum TableOption implements Option { /** * sstable_compression */ - STABLE_COMPRESSION("sstable_compression", String.class, true, false, false), + SSTABLE_COMPRESSION("sstable_compression", String.class, true, false, true), /** * chunk_length_kb */ diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java index 1cce358e3..89d682537 100644 --- a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CqlTableSpecificationAssertions.java @@ -20,10 +20,12 @@ import static org.junit.Assert.assertEquals; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cassandra.core.cql.CqlStringUtils; import org.springframework.cassandra.core.keyspace.ColumnSpecification; import org.springframework.cassandra.core.keyspace.TableDescriptor; import org.springframework.cassandra.core.keyspace.TableOption; -import org.springframework.cassandra.core.keyspace.TableOption.CachingOption; import com.datastax.driver.core.ColumnMetadata; import com.datastax.driver.core.Session; @@ -32,6 +34,8 @@ import com.datastax.driver.core.TableMetadata.Options; public class CqlTableSpecificationAssertions { + private final static Logger log = LoggerFactory.getLogger(CqlTableSpecificationAssertions.class); + public static double DELTA = 1e-6; // delta for comparisons of doubles public static void assertTable(TableDescriptor expected, String keyspace, Session session) { @@ -57,8 +61,10 @@ public class CqlTableSpecificationAssertions { for (String key : expected.keySet()) { + log.info(key + " -> " + expected.get(key)); + Object value = expected.get(key); - TableOption tableOption = getTableOptionFor(key); + TableOption tableOption = getTableOptionFor(key.toUpperCase()); if (tableOption == null && key.equalsIgnoreCase(TableOption.COMPACT_STORAGE.getName())) { // TODO: figure out how to tell if COMPACT STORAGE was used @@ -85,7 +91,7 @@ public class CqlTableSpecificationAssertions { return; case CACHING: - assertEquals(CachingOption.valueOf((String) expected).getValue(), actual); + assertEquals(((String) expected).toUpperCase(), ((String) actual).toUpperCase()); return; case COMPACTION: @@ -97,7 +103,10 @@ public class CqlTableSpecificationAssertions { return; } - assertEquals(expected, actual); + log.info(actual.getClass().getName()); + + assertEquals(expected, + tableOption.quotesValue() && !(actual instanceof CharSequence) ? CqlStringUtils.singleQuote(actual) : actual); } public static void assertCompaction(Map expected, Map actual) { @@ -122,9 +131,9 @@ public class CqlTableSpecificationAssertions { case BLOOM_FILTER_FP_CHANCE: return (T) (Double) options.getBloomFilterFalsePositiveChance(); case CACHING: - return (T) options.getCaching(); + return (T) CqlStringUtils.singleQuote(options.getCaching()); case COMMENT: - return (T) options.getComment(); + return (T) CqlStringUtils.singleQuote(options.getComment()); case COMPACTION: return (T) options.getCompaction(); case COMPACT_STORAGE: @@ -132,7 +141,7 @@ public class CqlTableSpecificationAssertions { case COMPRESSION: return (T) options.getCompression(); case DCLOCAL_READ_REPAIR_CHANCE: - return (T) (Double) options.getReadRepairChance(); + return (T) (Double) options.getLocalReadRepairChance(); case GC_GRACE_SECONDS: return (T) new Long(options.getGcGraceInSeconds()); case READ_REPAIR_CHANCE: diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java index 82872f163..63806cbc7 100644 --- a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/CreateTableCqlGeneratorIntegrationTests.java @@ -3,6 +3,8 @@ package org.springframework.cassandra.test.integration.core.cql.generator; import static org.springframework.cassandra.test.integration.core.cql.generator.CqlTableSpecificationAssertions.assertTable; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest; import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.BasicTest; import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.CompositePartitionKeyTest; @@ -15,6 +17,8 @@ import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCql */ public class CreateTableCqlGeneratorIntegrationTests { + private final static Logger log = LoggerFactory.getLogger(CreateTableCqlGeneratorIntegrationTests.class); + /** * Integration test base class that knows how to do everything except instantiate the concrete unit test type T. * diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/TableOptionsIntegrationTest.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/TableOptionsIntegrationTest.java new file mode 100644 index 000000000..b0d0bd92b --- /dev/null +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/integration/core/cql/generator/TableOptionsIntegrationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011-2013 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.test.integration.core.cql.generator; + +import static org.springframework.cassandra.test.integration.core.cql.generator.CqlTableSpecificationAssertions.assertTable; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest; +import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests; + +/** + * Test CREATE TABLE for all Options and assert against C* TableMetaData + * + * @author David Webb + */ +public class TableOptionsIntegrationTest extends AbstractEmbeddedCassandraIntegrationTest { + + private final static Logger log = LoggerFactory.getLogger(TableOptionsIntegrationTest.class); + + @Test + public void test() { + + CreateTableCqlGeneratorTests.MultipleOptionsTest optionsTest = new CreateTableCqlGeneratorTests.MultipleOptionsTest(); + + optionsTest.prepare(); + + log.info(optionsTest.cql); + + session.execute(optionsTest.cql); + + assertTable(optionsTest.specification, keyspace, session); + } +} \ No newline at end of file diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateIndexCqlGeneratorTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateIndexCqlGeneratorTests.java index 177028806..aaaf9ec12 100644 --- a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateIndexCqlGeneratorTests.java +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateIndexCqlGeneratorTests.java @@ -1,7 +1,6 @@ package org.springframework.cassandra.test.unit.core.cql.generator; import static org.junit.Assert.assertTrue; -import static org.springframework.cassandra.core.keyspace.IndexOperations.createIndex; import org.junit.Test; import org.springframework.cassandra.core.cql.generator.CreateIndexCqlGenerator; @@ -44,7 +43,7 @@ public class CreateIndexCqlGeneratorTests { public String column1 = "column1"; public CreateIndexSpecification specification() { - return createIndex().name(name).tableName(tableName).columnName(column1); + return CreateIndexSpecification.createIndex().name(name).tableName(tableName).columnName(column1); } @Test diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java index 0f3b37d1b..c0a413924 100644 --- a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/CreateTableCqlGeneratorTests.java @@ -1,15 +1,27 @@ package org.springframework.cassandra.test.unit.core.cql.generator; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; + +import java.util.LinkedHashMap; +import java.util.Map; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator; 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 com.datastax.driver.core.DataType; public class CreateTableCqlGeneratorTests { + private static final Logger log = LoggerFactory.getLogger(CreateTableCqlGeneratorTests.class); + /** * Asserts that the preamble is first & correctly formatted in the given CQL string. */ @@ -35,6 +47,35 @@ public class CreateTableCqlGeneratorTests { assertTrue(cql.contains("(" + columnSpec + ",")); } + /** + * Asserts that the read repair change is set properly + */ + public static void assertStringOption(String name, String value, String cql) { + log.info(name + " -> " + value); + assertTrue(cql.contains(name + " = '" + value + "'")); + } + + /** + * Asserts that the option is set + */ + public static void assertDoubleOption(String name, Double value, String cql) { + log.info(name + " -> " + value); + assertTrue(cql.contains(name + " = " + value)); + } + + public static void assertLongOption(String name, Long value, String cql) { + log.info(name + " -> " + value); + assertTrue(cql.contains(name + " = " + value)); + } + + /** + * Asserts that the read repair change is set properly + */ + public static void assertNullOption(String name, String cql) { + log.info(name); + assertTrue(cql.contains(" " + name + " ")); + } + /** * Convenient base class that other test classes can use so as not to repeat the generics declarations or * {@link #generator()} method. @@ -56,7 +97,8 @@ public class CreateTableCqlGeneratorTests { public String column1 = "column1"; public CreateTableSpecification specification() { - return CreateTableSpecification.createTable().name(name).partitionKeyColumn(partitionKey0, partitionKeyType0).column(column1, columnType1); + return CreateTableSpecification.createTable().name(name).partitionKeyColumn(partitionKey0, partitionKeyType0) + .column(column1, columnType1); } @Test @@ -96,4 +138,109 @@ public class CreateTableCqlGeneratorTests { assertPrimaryKey(String.format("(%s, %s)", partKey0, partKey1), cql); } } + + /** + * Test just the Read Repair Chance + * + * @author David Webb + * + */ + public static class ReadRepairChanceTest extends CreateTableTest { + + public String name = "mytable"; + public DataType partitionKeyType0 = DataType.text(); + public String partitionKey0 = "partitionKey0"; + public DataType partitionKeyType1 = DataType.timestamp(); + public String partitionKey1 = "create_timestamp"; + public DataType columnType1 = DataType.text(); + public String column1 = "column1"; + public Double readRepairChance = 0.5; + + public CreateTableSpecification specification() { + return (CreateTableSpecification) 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 + * + * TODO - Determine how to assert the options with map values + * + * @author David Webb + * + */ + public static class MultipleOptionsTest extends CreateTableTest { + + public String name = "timeseries_table"; + public DataType partitionKeyType0 = DataType.timeuuid(); + public String partitionKey0 = "tid"; + public DataType partitionKeyType1 = DataType.timestamp(); + public String partitionKey1 = "create_timestamp"; + public DataType columnType1 = DataType.text(); + public String column1 = "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 compactionMap = new LinkedHashMap(); + public Map compressionMap = new LinkedHashMap(); + + 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); + + return (CreateTableSpecification) 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, CachingOption.KEYS_ONLY).with(TableOption.REPLICATE_ON_WRITE, replcateOnWrite) + .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.CACHING.getName(), CachingOption.KEYS_ONLY.getValue(), cql); + assertStringOption(TableOption.REPLICATE_ON_WRITE.getName(), replcateOnWrite.toString(), cql); + assertStringOption(TableOption.COMMENT.getName(), comment, cql); + assertLongOption(TableOption.GC_GRACE_SECONDS.getName(), gcGraceSeconds, cql); + + } + } + } diff --git a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropIndexCqlGeneratorTests.java b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropIndexCqlGeneratorTests.java index 0c091b037..6db90bc65 100644 --- a/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropIndexCqlGeneratorTests.java +++ b/spring-cassandra/src/test/java/org/springframework/cassandra/test/unit/core/cql/generator/DropIndexCqlGeneratorTests.java @@ -1,7 +1,6 @@ package org.springframework.cassandra.test.unit.core.cql.generator; import static org.junit.Assert.assertTrue; -import static org.springframework.cassandra.core.keyspace.IndexOperations.dropIndex; import org.junit.Test; import org.springframework.cassandra.core.cql.generator.DropIndexCqlGenerator; @@ -28,7 +27,7 @@ public class DropIndexCqlGeneratorTests { public String name = "myindex"; public DropIndexSpecification specification() { - return dropIndex().name(name); + return DropIndexSpecification.dropIndex().name(name); } public DropIndexCqlGenerator generator() { @@ -49,7 +48,7 @@ public class DropIndexCqlGeneratorTests { public String name = "myindex"; public DropIndexSpecification specification() { - return dropIndex().name(name) + return DropIndexSpecification.dropIndex().name(name) // .ifExists() ; }