diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnBuilder.java index b613bb799..f5b4e7f92 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnBuilder.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnBuilder.java @@ -25,12 +25,12 @@ import com.datastax.driver.core.DataType; public class ColumnBuilder { /** - * Default ordering of primary key fields is {@link Ordering#ASCENDING}. + * Default ordering of primary key fields; value is {@link Ordering#ASCENDING}. */ public static final Ordering DFAULT_ORDERING = ASCENDING; private String name; - private DataType type; + private DataType type; // TODO: determining if we should be coupling this to Datastax Java Driver type? private KeyType keyType; private Ordering ordering; diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/CqlBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/CqlBuilder.java index 0a6490328..6e0fc38c9 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/CqlBuilder.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/CqlBuilder.java @@ -8,4 +8,4 @@ public class CqlBuilder { public static CreateTableBuilder createTable() { return new CreateTableBuilder(); } -} +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilder.java index 72728aadf..f19984d9f 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilder.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilder.java @@ -9,7 +9,7 @@ import static org.springframework.data.cassandra.mapping.KeyType.PRIMARY; import static org.springframework.data.cassandra.mapping.Ordering.ASCENDING; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -29,7 +29,7 @@ public class CreateTableBuilder { private boolean ifNotExists = false; private String name; private List columns = new ArrayList(); - private Map options = new HashMap(); + private Map options = new LinkedHashMap(); /** * Causes the inclusion of an IF NOT EXISTS clause. @@ -65,40 +65,27 @@ public class CreateTableBuilder { } /** - * Adds the given single-string option with no value to this table's options. Convenient overload of - * with(string, null, false, false). + * Convenience method that calls with(option, null). * - * @param singleStringOption - * @return + * @return this */ - public CreateTableBuilder with(String string) { - return with(string, null, false, false); + public CreateTableBuilder with(TableOption option) { + return with(option, null); } /** - * Adds the given single-quote-escaped then single-quoted option value by name to this table's options. Convenient - * overload of with(name, value, true, true) + * Sets the given table option. This is a convenience method that calls + * {@link #with(String, Object, boolean, boolean)} appropriately from the given {@link TableOption} and value for that + * option. * - * @see #with(String, Object, boolean, boolean) + * @param option The option to set. + * @param value The value of the option. Must be type-compatible with the {@link TableOption}. * @return this - */ - public CreateTableBuilder withQuoted(String name, Object value) { - return with(name, value, true, true); - } - - /** - * Adds the given option value by name with no quoting or escaping to this table's options. Convenient overload of - * with(name, value, false, false) - * * @see #with(String, Object, boolean, boolean) - * @return this */ - public CreateTableBuilder withUnquoted(String name, Object value) { - return with(name, value, false, false); - } - - public CreateTableBuilder with(String name, Map valueMap) { - return with(name, valueMap, false, false); + public CreateTableBuilder with(TableOption option, Object value) { + option.checkValue(value); + return with(option.getName(), value, option.escapesValue(), option.quotesValue()); } /** @@ -152,22 +139,12 @@ public class CreateTableBuilder { return this; } - /** - * Convenient method that calls with("COMPACT STORAGE", null). - * - * @see #with(String, Object) - * @return this - */ - public CreateTableBuilder withCompactStorage() { - return with("COMPACT STORAGE"); - } - protected List columns() { return columns == null ? columns = new ArrayList() : columns; } protected Map options() { - return options == null ? options = new HashMap() : options; + return options == null ? options = new LinkedHashMap() : options; } public String toCql() { @@ -269,10 +246,11 @@ public class CreateTableBuilder { // begin options // begin option clause if (clustering != null || !options.isEmpty()) { + // option preamble first = true; - cql.append(" WITH "); + // end option preamble if (clustering != null) { cql.append(clustering); @@ -297,24 +275,32 @@ public class CreateTableBuilder { cql.append(" = "); - Map valueMap = null; - if ((value instanceof Map) && !(valueMap = (Map) value).isEmpty()) { + Map valueMap = null; + if ((value instanceof Map) && !(valueMap = (Map) value).isEmpty()) { // then option value is a non-empty map // append { 'name' : 'value', ... } cql.append("{ "); boolean mapFirst = true; - for (Map.Entry entry : valueMap.entrySet()) { + for (Map.Entry entry : valueMap.entrySet()) { if (mapFirst) { mapFirst = false; } else { cql.append(", "); } - cql.append(singleQuote(entry.getKey())); // 'name' + Option option = entry.getKey(); + cql.append(singleQuote(option.getName())); // entries in map keys are always quoted cql.append(" : "); Object entryValue = entry.getValue(); - cql.append(singleQuote(entryValue == null ? "" : entryValue.toString())); // 'value' + entryValue = entryValue == null ? "" : entryValue.toString(); + if (option.escapesValue()) { + entryValue = escapeSingle(value); + } + if (option.quotesValue()) { + entryValue = singleQuote(value); + } + cql.append(entryValue); } cql.append(" }"); diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java b/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java new file mode 100644 index 000000000..1e843de47 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/DefaultOption.java @@ -0,0 +1,157 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.escapeSingle; +import static org.springframework.data.cassandra.cql.CqlStringUtils.singleQuote; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * A default implementation of {@link Option}. + * + * @author Matthew T. Adams + */ +public class DefaultOption implements Option { + + private String name; + private Class type; + private boolean requiresValue; + private boolean escapesValue; + private boolean quotesValue; + + public DefaultOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { + setName(name); + setType(type); + this.requiresValue = requiresValue; + this.escapesValue = escapesValue; + this.quotesValue = quotesValue; + + } + + protected void setName(String name) { + Assert.hasLength(name); + this.name = name; + } + + protected void setType(Class type) { + if (type != null) { + if (type.isInterface() && !(Map.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type))) { + throw new IllegalArgumentException("given type [" + type.getName() + "] must be a class, Map or Collection"); + } + } + this.type = type; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean isCoerceable(Object value) { + if (value == null || type == null) { + return true; + } + + // check collections + if (Map.class.isAssignableFrom(type)) { + return Map.class.isAssignableFrom(value.getClass()); + } + // check map + if (Collection.class.isAssignableFrom(type)) { + return Collection.class.isAssignableFrom(value.getClass()); + } + // check enum + if (type.isEnum()) { + try { + String name = value instanceof Enum ? name = ((Enum) value).name() : value.toString(); + Enum.valueOf((Class) type, name); + return true; + } catch (NullPointerException x) { + return false; + } catch (IllegalArgumentException x) { + return false; + } + } + + // check class via String constructor + try { + Constructor ctor = type.getConstructor(String.class); + if (!ctor.isAccessible()) { + ctor.setAccessible(true); + } + ctor.newInstance(value.toString()); + return true; + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } catch (IllegalArgumentException e) { + } catch (InvocationTargetException e) { + } catch (NoSuchMethodException e) { + } catch (SecurityException e) { + } + return false; + } + + public Class getType() { + return type; + } + + public String getName() { + return name; + } + + public boolean takesValue() { + return type != null; + } + + public boolean requiresValue() { + return this.requiresValue; + } + + public boolean escapesValue() { + return this.escapesValue; + } + + public boolean quotesValue() { + return this.quotesValue; + } + + public void checkValue(Object value) { + if (takesValue()) { + if (value == null) { + if (requiresValue) { + throw new IllegalArgumentException("Option [" + getName() + "] requires a value"); + } + return; // doesn't require a value, so null is ok + } + // else value is not null + if (isCoerceable(value)) { + return; + } + // else value is not coerceable into the expected type + throw new IllegalArgumentException("Option [" + getName() + "] takes value coerceable to type [" + + getType().getName() + "]"); + } + // else this option doesn't take a value + if (value != null) { + throw new IllegalArgumentException("Option [" + getName() + "] takes no value"); + } + } + + public String toString(Object value) { + if (value == null) { + return null; + } + checkValue(value); + + String string = value.toString(); + string = escapesValue ? escapeSingle(string) : string; + string = quotesValue ? singleQuote(string) : string; + return string; + } + + @Override + public String toString() { + return "[name=" + name + ", type=" + type.getName() + ", requiresValue=" + requiresValue + ", escapesValue=" + + escapesValue + ", quotesValue=" + quotesValue + "]"; + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/MapBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/MapBuilder.java new file mode 100644 index 000000000..ddfe39d62 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/MapBuilder.java @@ -0,0 +1,126 @@ +package org.springframework.data.cassandra.cql.builder; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Builder for maps, which also conveniently implements {@link Map} via delegation for convenience so you don't have to + * actually {@link #build()} it if you don't want to (or forget). + * + * @author Matthew T. Adams + * @param The key type of the map. + * @param The value type of the map. + */ +public class MapBuilder implements Map { + + /** + * Factory method to construct a new MapBuilder<Object,Object>. Convenient if imported statically. + */ + public static MapBuilder map() { + return map(Object.class, Object.class); + } + + /** + * Factory method to construct a new builder with the given key & value types. Convenient if imported statically. + */ + public static MapBuilder map(Class keyType, Class valueType) { + return new MapBuilder(); + } + + /** + * Factory method to construct a new builder with a shallow copy of the given map. Convenient if imported statically. + */ + public static MapBuilder map(Map source) { + return new MapBuilder(source); + } + + private Map map; + + public MapBuilder() { + this(new LinkedHashMap()); + } + + /** + * Constructs a new instance with a copy of the given map. + */ + public MapBuilder(Map source) { + this.map = new LinkedHashMap(source); + } + + /** + * Adds an entry to this map, then returns this. + * + * @return this + */ + public MapBuilder entry(K key, V value) { + map.put(key, value); + return this; + } + + /** + * Returns a new map based on the current state of this builder's map. + * + * @return A new Map with this builder's map's current content. + */ + public Map build() { + return new LinkedHashMap(map); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public V get(Object key) { + return map.get(key); + } + + public V put(K key, V value) { + return map.put(key, value); + } + + public V remove(Object key) { + return map.remove(key); + } + + public void putAll(Map m) { + map.putAll(m); + } + + public void clear() { + map.clear(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public boolean equals(Object o) { + return map.equals(o); + } + + public int hashCode() { + return map.hashCode(); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java b/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java new file mode 100644 index 000000000..34839351d --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/Option.java @@ -0,0 +1,61 @@ +package org.springframework.data.cassandra.cql.builder; + +/** + * Interface to represent option types. + * + * @author Matthew T. Adams + */ +public interface Option { + + /** + * The type that values must be able to be coerced into for this option. + */ + Class getType(); + + /** + * The (usually lower-cased, underscore-separated) name of this table option. + */ + String getName(); + + /** + * Whether this option takes a value. + */ + boolean takesValue(); + + /** + * Whether this option should escape single quotes in its value. + */ + boolean escapesValue(); + + /** + * Whether this option's value should be single-quoted. + */ + boolean quotesValue(); + + /** + * Whether this option requires a value. + */ + boolean requiresValue(); + + /** + * Checks that the given value can be coerced into the type given by {@link #getType()}. + */ + void checkValue(Object value); + + /** + * Tests whether the given value can be coerced into the type given by {@link #getType()}. + */ + boolean isCoerceable(Object value); + + /** + * First ensures that the given value is coerceable into the type expected by this option, then returns the result of + * {@link Object#toString()} called on the given value. If this option is escaping quotes ({@link #escapesValue()} is + * true), then single quotes will be escaped, and if this option is quoting values ( + * {@link #quotesValue()} is true), then the value will be surrounded by single quotes. Given + * null, returns null. + * + * @see #escapesValue() + * @see #quotesValue() + */ + String toString(Object value); +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java b/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java new file mode 100644 index 000000000..5e73a5f42 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/TableOption.java @@ -0,0 +1,282 @@ +package org.springframework.data.cassandra.cql.builder; + +import java.util.Map; + +/** + * Enumeration that represents all known table options. If a table option is not listed here, but is supported by + * Cassandra, use the method {@link CreateTableBuilder#with(String, Object, boolean, boolean)} to write the raw value. + * + * Implements {@link Option} via delegation, since {@link Enum}s can't extend anything. + * + * @author Matthew T. Adams + * @see CompactionOption + * @see CompressionOption + * @see CachingOption + */ +public enum TableOption implements Option { + /** + * comment + */ + COMMENT("comment", String.class, false, true, true), + /** + * COMPACT STORAGE + */ + COMPACT_STORAGE("COMPACT STORAGE", null, false, false, false), + /** + * compaction. Value is a Map<CompactionOption,Object>. + * + * @see CompactionOption + */ + COMPACTION("compaction", Map.class, false, false, false), + /** + * compression. Value is a Map<CompressionOption,Object>. + * + * @see {@link CompressionOption} + */ + COMPRESSION("compression", Map.class, false, false, false), + /** + * replicate_on_write + */ + REPLICATE_ON_WRITE("replicate_on_write", Boolean.class, false, false, false), + /** + * caching + * + * @see CachingOption + */ + CACHING("caching", CachingOption.class, false, false, false), + /** + * bloom_filter_fp_chance + */ + BLOOM_FILTER_FP_CHANCE("bloom_filter_fp_chance", Double.class, false, false, false), + /** + * read_repair_chance + */ + READ_REPAIR_CHANCE("read_repair_chance", Double.class, false, false, false), + /** + * dclocal_read_repair_chance + */ + DCLOCAL_READ_REPAIR_CHANCE("dclocal_read_repair_chance", Double.class, false, false, false), + /** + * gc_grace_seconds + */ + GC_GRACE_SECONDS("gc_grace_seconds", Long.class, false, false, false); + + private Option delegate; + + private TableOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + + /** + * Known caching options. + * + * @author Matthew T. Adams + */ + public enum CachingOption { + ALL("all"), KEYS_ONLY("keys_only"), ROWS_ONLY("rows_only"), NONE("none"); + + private String value; + + private CachingOption(String value) { + this.value = value; + } + + public String toString() { + return value; + } + } + + /** + * Known compaction options. + * + * @author Matthew T. Adams + */ + public enum CompactionOption implements Option { + /** + * tombstone_threshold + */ + TOMBSTONE_THRESHOLD("tombstone_threshold", Double.class, false, false, false), + /** + * tombstone_compaction_interval + */ + TOMBSTONE_COMPACTION_INTERVAL("tombstone_compaction_interval", Double.class, false, false, false), + /** + * min_sstable_size + */ + MIN_SSTABLE_SIZE("min_sstable_size", Long.class, false, false, false), + /** + * min_threshold + */ + MIN_THRESHOLD("min_threshold", Long.class, false, false, false), + /** + * max_threshold + */ + MAX_THRESHOLD("max_threshold", Long.class, false, false, false), + /** + * bucket_low + */ + BUCKET_LOW("bucket_low", Double.class, false, false, false), + /** + * bucket_high + */ + BUCKET_HIGH("bucket_high", Double.class, false, false, false), + /** + * sstable_size_in_mb + */ + SSTABLE_SIZE_IN_MB("sstable_size_in_mb", Long.class, false, false, false); + + private Option delegate; + + private CompactionOption(String name, Class type, boolean requiresValue, boolean escapesValue, + boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + } + + /** + * Known compression options. + * + * @author Matthew T. Adams + */ + public enum CompressionOption implements Option { + /** + * sstable_compression + */ + STABLE_COMPRESSION("sstable_compression", String.class, false, false, false), + /** + * chunk_length_kb + */ + CHUNK_LENGTH_KB("chunk_length_kb", Long.class, false, false, false), + /** + * crc_check_chance + */ + CRC_CHECK_CHANCE("crc_check_chance", Double.class, false, false, false); + + private Option delegate; + + private CompressionOption(String name, Class type, boolean requiresValue, boolean escapesValue, + boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + } +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java b/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java index 6d8540ee2..9bb04dca8 100644 --- a/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java +++ b/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java @@ -8,12 +8,12 @@ package org.springframework.data.cassandra.mapping; public enum KeyType { /** - * Used for a column that is a primary key that also is or is part of the partition key. + * Used for a column that is a primary key and that also is or is part of the partition key. */ PARTITION, /** - * Use for a primary key column that is not part of the partition key and, therefore, may also be ordered. + * Used for a primary key column that is not part of the partition key. */ PRIMARY } \ No newline at end of file diff --git a/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java b/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java deleted file mode 100644 index 69a3585e1..000000000 --- a/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.springframework.data.cassandra.cql; - -import static org.springframework.data.cassandra.cql.builder.CqlBuilder.createTable; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; -import org.springframework.data.cassandra.cql.builder.CreateTableBuilder; - -import com.datastax.driver.core.DataType; - -public class CreateTableBuilderTest { - - @Test - public void createTableTest() { - String name = "mytable"; - DataType type0 = DataType.text(); - String partition0 = "partitionKey0"; - String partition1 = "partitionKey1"; - String primary0 = "primary0"; - DataType type1 = DataType.text(); - String column1 = "column1"; - DataType type2 = DataType.bigint(); - String column2 = "column2"; - Object value2 = "this is a comment"; - String option2 = "comment"; - Object value3 = "0.00075"; - String option3 = "bloom_filter_fp_chance"; - Map value4 = new HashMap(); - value4.put("class", "LeveledCompactionStrategy"); - String option4 = "compaction"; - - CreateTableBuilder builder = createTable().ifNotExists().name(name).partitionKeyColumn(partition0, type0) - .partitionKeyColumn(partition1, type0).primaryKeyColumn(primary0, type0).column(column1, type1) - .column(column2, type2).withQuoted(option2, value2).withUnquoted(option3, value3).with(option4, value4) - .withCompactStorage(); - - String cql = builder.toCql(); - System.out.println(cql); - } -} diff --git a/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java b/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java new file mode 100644 index 000000000..00018ae15 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java @@ -0,0 +1,44 @@ +package org.springframework.data.cassandra.cql.builder; + +import static junit.framework.Assert.assertEquals; +import static org.springframework.data.cassandra.cql.builder.CqlBuilder.createTable; +import static org.springframework.data.cassandra.cql.builder.MapBuilder.map; +import static org.springframework.data.cassandra.cql.builder.TableOption.BLOOM_FILTER_FP_CHANCE; +import static org.springframework.data.cassandra.cql.builder.TableOption.CACHING; +import static org.springframework.data.cassandra.cql.builder.TableOption.COMMENT; +import static org.springframework.data.cassandra.cql.builder.TableOption.COMPACTION; +import static org.springframework.data.cassandra.cql.builder.TableOption.CompactionOption.TOMBSTONE_THRESHOLD; + +import org.junit.Test; +import org.springframework.data.cassandra.cql.builder.TableOption.CachingOption; + +import com.datastax.driver.core.DataType; + +public class CreateTableBuilderTest { + + @Test + public void createTableTest() { + String name = "mytable"; + DataType type0 = DataType.text(); + String partKey0 = "partitionKey0"; + String partition1 = "partitionKey1"; + String primary0 = "primary0"; + DataType type1 = DataType.text(); + String column1 = "column1"; + DataType type2 = DataType.bigint(); + String column2 = "column2"; + Object comment = "this is a comment"; + Object bloom = "0.00075"; + Object caching = CachingOption.KEYS_ONLY; + + CreateTableBuilder builder = createTable().ifNotExists().name(name).partitionKeyColumn(partKey0, type0) + .partitionKeyColumn(partition1, type0).primaryKeyColumn(primary0, type0).column(column1, type1) + .column(column2, type2).with(COMMENT, comment).with(BLOOM_FILTER_FP_CHANCE, bloom) + .with(COMPACTION, map().entry(TOMBSTONE_THRESHOLD, "0.15")).with(CACHING, caching); + + String cql = builder.toCql(); + assertEquals( + "CREATE TABLE IF NOT EXISTS mytable (partitionKey0 text, partitionKey1 text, primary0 text, column1 text, column2 bigint, PRIMARY KEY ((partitionKey0, partitionKey1), primary0) WITH CLUSTERING ORDER BY (primary0 ASC) AND comment = 'this is a comment' AND bloom_filter_fp_chance = 0.00075 AND compaction = { 'tombstone_threshold' : 0.15 } AND caching = keys_only;", + cql); + } +} diff --git a/src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java b/src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java new file mode 100644 index 000000000..34569262f --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/OptionTest.java @@ -0,0 +1,102 @@ +package org.springframework.data.cassandra.cql.builder; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; + +public class OptionTest { + + @Test(expected = IllegalArgumentException.class) + public void testOptionWithNullName() { + new DefaultOption(null, Object.class, true, true, true); + } + + @Test(expected = IllegalArgumentException.class) + public void testOptionWithEmptyName() { + new DefaultOption("", Object.class, true, true, true); + } + + @Test + public void testOptionWithNullType() { + new DefaultOption("opt", null, true, true, true); + new DefaultOption("opt", null, false, true, true); + } + + @Test + public void testOptionWithNullTypeIsCoerceable() { + Option op = new DefaultOption("opt", null, true, true, true); + assertTrue(op.isCoerceable("")); + assertTrue(op.isCoerceable(null)); + } + + @Test + public void testOptionValueCoercion() { + String name = "my_option"; + Class type = String.class; + boolean requires = true; + boolean escapes = true; + boolean quotes = true; + + Option op = new DefaultOption(name, type, requires, escapes, quotes); + + assertTrue(op.isCoerceable("opt")); + assertEquals("'opt'", op.toString("opt")); + assertEquals("'opt''n'", op.toString("opt'n")); + + type = Long.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + String expected = "1"; + for (Object value : new Object[] { 1, "1" }) { + assertTrue(op.isCoerceable(value)); + assertEquals(expected, op.toString(value)); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = Long.class; + escapes = false; + quotes = true; + op = new DefaultOption(name, type, requires, escapes, quotes); + + expected = "'1'"; + for (Object value : new Object[] { 1, "1" }) { + assertTrue(op.isCoerceable(value)); + assertEquals(expected, op.toString(value)); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = Double.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + String[] expecteds = new String[] { "1", "1.0", "1.0", "1", "1.0", null }; + Object[] values = new Object[] { 1, 1.0F, 1.0D, "1", "1.0", null }; + for (int i = 0; i < values.length; i++) { + assertTrue(op.isCoerceable(values[i])); + assertEquals(expecteds[i], op.toString(values[i])); + } + assertFalse(op.isCoerceable("x")); + assertTrue(op.isCoerceable(null)); + + type = RetentionPolicy.class; + escapes = false; + quotes = false; + op = new DefaultOption(name, type, requires, escapes, quotes); + + assertTrue(op.isCoerceable(null)); + assertTrue(op.isCoerceable(RetentionPolicy.CLASS)); + assertTrue(op.isCoerceable("CLASS")); + assertFalse(op.isCoerceable("x")); + assertEquals("CLASS", op.toString("CLASS")); + assertEquals("CLASS", op.toString(RetentionPolicy.CLASS)); + } +}