diff --git a/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java b/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java index 6d8ee4a4b..eebaa12d1 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java +++ b/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java @@ -9,29 +9,28 @@ public class CqlStringUtils { protected static final String DOUBLE_QUOTE = "\""; protected static final String DOUBLE_DOUBLE_QUOTE = "\"\""; - /** - * Helper {@link StringBuilder} factory method. If given a non-null argument, returns that, else returns - * a new {@link StringBuilder}. Intended to be imported statically by other classes in the builder's fluent API - * implementation. - * - * @param sb - * @return The given {@link StringBuilder} if not null, else a new one. - * - * @author Matthew T. Adams - */ - public static StringBuilder ensureNotNull(StringBuilder sb) { + public static StringBuilder noNull(StringBuilder sb) { return sb == null ? new StringBuilder() : sb; } - public static final String IDENTIFIER_REGEX = "[a-zA-Z0-9_]*"; - public static final Pattern IDENTIFIER_PATTERN = Pattern.compile(IDENTIFIER_REGEX); + public static final String UNESCAPED_DOUBLE_QUOTE_REGEX = "TODO"; + public static final Pattern UNESCAPED_DOUBLE_QUOTE_PATTERN = Pattern.compile(UNESCAPED_DOUBLE_QUOTE_REGEX); - public static boolean isIdentifier(CharSequence chars) { - return IDENTIFIER_PATTERN.matcher(chars).matches(); + public static final String UNQUOTED_IDENTIFIER_REGEX = "[a-zA-Z_][a-zA-Z0-9_]*"; + public static final Pattern UNQUOTED_IDENTIFIER_PATTERN = Pattern.compile(UNQUOTED_IDENTIFIER_REGEX); + + public static boolean isUnquotedIdentifier(CharSequence chars) { + return UNQUOTED_IDENTIFIER_PATTERN.matcher(chars).matches(); } - public static final String QUOTED_IDENTIFIER_REGEX = "([a-zA-Z0-9_]|'{2}+|\"{2}+)*"; - public static final Pattern QUOTED_IDENTIFIER_PATTERN = Pattern.compile(IDENTIFIER_REGEX); + public static void checkUnquotedIdentifier(CharSequence chars) { + if (!CqlStringUtils.isUnquotedIdentifier(chars)) { + throw new IllegalArgumentException("[" + chars + "] is not a valid CQL identifier"); + } + } + + public static final String QUOTED_IDENTIFIER_REGEX = "[a-zA-Z_]([a-zA-Z0-9_]|\"{2}+)*"; + public static final Pattern QUOTED_IDENTIFIER_PATTERN = Pattern.compile(QUOTED_IDENTIFIER_REGEX); public static boolean isQuotedIdentifier(CharSequence chars) { return QUOTED_IDENTIFIER_PATTERN.matcher(chars).matches(); @@ -43,19 +42,45 @@ public class CqlStringUtils { } } - /** - * Trims then escapes the given {@link CharSequence}. Given null, returns null. - */ - public static String scrub(Object thing) { - return thing == null ? (String) null : escape(thing.toString().trim()); + public static boolean isIdentifier(CharSequence chars) { + return isUnquotedIdentifier(chars) || isQuotedIdentifier(chars); + } + + public static void checkIdentifier(CharSequence chars) { + if (!CqlStringUtils.isIdentifier(chars)) { + throw new IllegalArgumentException("[" + chars + "] is not a valid CQL quoted or unquoted identifier"); + } } /** - * Doubles single quote characters and doubles double quote characters (' -> '' and " -> ""). Given - * null, returns null. + * Renders the given string as a legal Cassandra identifier. + * */ - public static String escape(Object thing) { - return escapeDouble(escapeSingle(thing)); + public static String identifize(String candidate) { + + checkIdentifier(candidate); + + if (isUnquotedIdentifier(candidate)) { + return candidate; + } + // else it must be quoted + return doubleQuote(candidate); + } + + /** + * Renders the given string as a legal Cassandra string column or table option value, by escaping single quotes and + * encasing the result in single quotes. Given null, returns null. + */ + public static String valuize(String candidate) { + + if (candidate == null) { + return null; + } + return singleQuote(escapeSingle(candidate)); } /** @@ -69,7 +94,7 @@ public class CqlStringUtils { * Doubles double quote characters (" -> ""). Given null, returns null. */ public static String escapeDouble(Object things) { - return things == null ? (String) null : things.toString().replace(DOUBLE_QUOTE, DOUBLE_SINGLE_QUOTE); + return things == null ? (String) null : things.toString().replace(DOUBLE_QUOTE, DOUBLE_DOUBLE_QUOTE); } /** diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/AbstractTableBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/AbstractTableBuilder.java new file mode 100644 index 000000000..3412ee6f6 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/AbstractTableBuilder.java @@ -0,0 +1,149 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; +import static org.springframework.data.cassandra.cql.CqlStringUtils.escapeSingle; +import static org.springframework.data.cassandra.cql.CqlStringUtils.identifize; +import static org.springframework.data.cassandra.cql.CqlStringUtils.singleQuote; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.data.cassandra.cql.CqlStringUtils; + +/** + * Base class that contains behavior common to table operations. + * + * @author Matthew T. Adams + * @param T The subtype of AbstractTableBuilder. + */ +public abstract class AbstractTableBuilder> { + + protected abstract StringBuilder toCql(StringBuilder cql); + + private String name; + + protected Map options = new LinkedHashMap(); + + /** + * Sets the table name. + * + * @return this + */ + @SuppressWarnings("unchecked") + public T name(String name) { + CqlStringUtils.checkIdentifier(name); + this.name = name; + return (T) this; + } + + public String getName() { + return name; + } + + public String getNameAsIdentifier() { + return identifize(name); + } + + /** + * Convenience method that calls with(option, null). + * + * @return this + */ + public T with(TableOption option) { + return with(option, null); + } + + /** + * 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. + * + * @param option The option to set. + * @param value The value of the option. Must be type-compatible with the {@link TableOption}. + * @return this + * @see #with(String, Object, boolean, boolean) + */ + public T with(TableOption option, Object value) { + option.checkValue(value); + return (T) with(option.getName(), value, option.escapesValue(), option.quotesValue()); + } + + /** + * Adds the given option by name to this table's options. + *

+ * Options that have null values are considered single string options where the name of the option is the + * string to be used. Otherwise, the result of {@link Object#toString()} is considered to be the value of the option + * with the given name. The value, after conversion to string, may have embedded single quotes escaped according to + * parameter escape and may be single-quoted according to parameter quote. + * + * @param name The name of the option + * @param value The value of the option. If null, the value is ignored and the option is considered to be + * composed of only the name, otherwise the value's {@link Object#toString()} value is used. + * @param escape Whether to escape the value via {@link CqlStringUtils#escapeSingle(Object)}. Ignored if given value + * is an instance of a {@link Map}. + * @param quote Whether to quote the value via {@link CqlStringUtils#singleQuote(Object)}. Ignored if given value is + * an instance of a {@link Map}. + * @return this + */ + @SuppressWarnings("unchecked") + public T with(String name, Object value, boolean escape, boolean quote) { + if (!(value instanceof Map)) { + if (escape) { + value = escapeSingle(value); + } + if (quote) { + value = singleQuote(value); + } + } + options().put(name, value); + return (T) this; + } + + protected Map options() { + return options == null ? options = new LinkedHashMap() : options; + } + + protected StringBuilder optionValueMap(Map valueMap, StringBuilder cql) { + cql = noNull(cql); + + if (valueMap == null || valueMap.isEmpty()) { + return cql; + } + // else option value is a non-empty map + + // append { 'name' : 'value', ... } + cql.append("{ "); + boolean mapFirst = true; + for (Map.Entry entry : valueMap.entrySet()) { + if (mapFirst) { + mapFirst = false; + } else { + cql.append(", "); + } + + Option option = entry.getKey(); + cql.append(singleQuote(option.getName())); // entries in map keys are always quoted + cql.append(" : "); + Object entryValue = entry.getValue(); + entryValue = entryValue == null ? "" : entryValue.toString(); + if (option.escapesValue()) { + entryValue = escapeSingle(entryValue); + } + if (option.quotesValue()) { + entryValue = singleQuote(entryValue); + } + cql.append(entryValue); + } + cql.append(" }"); + + return cql; + } + + public String toCql() { + return toCql(null).toString(); + } + + public String toString() { + return toCql(null).toString(); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/AddColumn.java b/src/main/java/org/springframework/data/cassandra/cql/builder/AddColumn.java new file mode 100644 index 000000000..a8373365e --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/AddColumn.java @@ -0,0 +1,17 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; + +import com.datastax.driver.core.DataType; + +public class AddColumn extends ColumnTypeChange { + + public AddColumn(String name, DataType type) { + super(name, type); + } + + @Override + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("ADD ").append(getNameAsIdentifier()).append(" TYPE ").append(getType().getName()); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/AlterColumn.java b/src/main/java/org/springframework/data/cassandra/cql/builder/AlterColumn.java new file mode 100644 index 000000000..60a42e1ab --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/AlterColumn.java @@ -0,0 +1,17 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; + +import com.datastax.driver.core.DataType; + +public class AlterColumn extends ColumnTypeChange { + + public AlterColumn(String name, DataType type) { + super(name, type); + } + + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("ALTER ").append(getNameAsIdentifier()).append(" TYPE ") + .append(getType().getName()); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/AlterTableBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/AlterTableBuilder.java new file mode 100644 index 000000000..29ba093f4 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/AlterTableBuilder.java @@ -0,0 +1,103 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.datastax.driver.core.DataType; + +public class AlterTableBuilder extends AbstractTableBuilder { + + private List changes = new ArrayList(); + + public AlterTableBuilder drop(String column) { + changes.add(new DropColumn(column)); + return this; + } + + public AlterTableBuilder add(String column, DataType type) { + changes.add(new AddColumn(column, type)); + return this; + } + + public AlterTableBuilder alter(String column, DataType type) { + changes.add(new AlterColumn(column, type)); + return this; + } + + public AlterTableBuilder name(String name) { + return (AlterTableBuilder) super.name(name); + } + + @Override + public StringBuilder toCql(StringBuilder cql) { + cql = noNull(cql); + + preambleCql(cql); + changesCql(cql); + optionsCql(cql); + + cql.append(";"); + + return cql; + } + + protected StringBuilder preambleCql(StringBuilder cql) { + return noNull(cql).append("ALTER TABLE ").append(getNameAsIdentifier()).append(" "); + } + + protected StringBuilder changesCql(StringBuilder cql) { + cql = noNull(cql); + + boolean first = true; + for (ColumnChange change : changes) { + if (first) { + first = false; + } else { + cql.append(" "); + } + change.toCql(cql); + } + + return cql; + } + + @SuppressWarnings("unchecked") + protected StringBuilder optionsCql(StringBuilder cql) { + cql = noNull(cql); + + Map options = options(); + if (options == null || options.isEmpty()) { + return cql; + } + + cql.append(" WITH "); + boolean first = true; + for (String key : options.keySet()) { + if (first) { + first = false; + } else { + cql.append(" AND "); + } + + cql.append(key); + + Object value = options.get(key); + if (value == null) { + continue; + } + cql.append(" = "); + + if (value instanceof Map) { + optionValueMap((Map) value, cql); + continue; + } + + // else just use value as string + cql.append(value.toString()); + } + return cql; + } +} 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 f5b4e7f92..48db1fde7 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 @@ -1,12 +1,12 @@ package org.springframework.data.cassandra.cql.builder; -import static org.springframework.data.cassandra.cql.CqlStringUtils.checkQuotedIdentifier; -import static org.springframework.data.cassandra.cql.CqlStringUtils.ensureNotNull; +import static org.springframework.data.cassandra.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.data.cassandra.cql.CqlStringUtils.identifize; +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; import static org.springframework.data.cassandra.mapping.KeyType.PARTITION; import static org.springframework.data.cassandra.mapping.KeyType.PRIMARY; import static org.springframework.data.cassandra.mapping.Ordering.ASCENDING; -import org.springframework.data.cassandra.cql.CqlStringUtils; import org.springframework.data.cassandra.mapping.KeyType; import org.springframework.data.cassandra.mapping.Ordering; @@ -35,15 +35,12 @@ public class ColumnBuilder { private Ordering ordering; /** - * Sets the column's name. Quotes are not escaped. - * - * @see CqlStringUtils#escape(CharSequence) - * @see CqlStringUtils#scrub(CharSequence) + * Sets the column's name. * * @return this */ public ColumnBuilder name(String name) { - checkQuotedIdentifier(name); + checkIdentifier(name); this.name = name; return this; } @@ -138,6 +135,10 @@ public class ColumnBuilder { return name; } + public String getNameAsIdentifier() { + return identifize(name); + } + public DataType getType() { return type; } @@ -155,7 +156,7 @@ public class ColumnBuilder { } public StringBuilder toCql(StringBuilder cql) { - return (cql = ensureNotNull(cql)).append(name).append(" ").append(type); + return (cql = noNull(cql)).append(name).append(" ").append(type); } @Override diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnChange.java b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnChange.java new file mode 100644 index 000000000..e6e35b4b9 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnChange.java @@ -0,0 +1,32 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.data.cassandra.cql.CqlStringUtils.identifize; + +public abstract class ColumnChange { + + public abstract StringBuilder toCql(StringBuilder cql); + + private String name; + + public ColumnChange(String name) { + setName(name); + } + + public String toString() { + return toCql(null).toString(); + } + + private void setName(String name) { + checkIdentifier(name); + this.name = name; + } + + public String getName() { + return name; + } + + public String getNameAsIdentifier() { + return identifize(name); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnTypeChange.java b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnTypeChange.java new file mode 100644 index 000000000..a4ca0234c --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnTypeChange.java @@ -0,0 +1,24 @@ +package org.springframework.data.cassandra.cql.builder; + +import org.springframework.util.Assert; + +import com.datastax.driver.core.DataType; + +public abstract class ColumnTypeChange extends ColumnChange { + + private DataType type; + + public ColumnTypeChange(String name, DataType type) { + super(name); + setType(type); + } + + private void setType(DataType type) { + Assert.notNull(type); + this.type = type; + } + + public DataType getType() { + return type; + } +} 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 6e0fc38c9..140512587 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,39 @@ public class CqlBuilder { public static CreateTableBuilder createTable() { return new CreateTableBuilder(); } + + /** + * Entry point into the {@link CqlBuilder}'s fluent API to create a table. Convenient if imported statically. + */ + public static CreateTableBuilder createTable(String name) { + return new CreateTableBuilder().name(name); + } + + /** + * Entry point into the {@link CqlBuilder}'s fluent API to alter a table. Convenient if imported statically. + */ + public static AlterTableBuilder alterTable() { + return new AlterTableBuilder(); + } + + /** + * Entry point into the {@link CqlBuilder}'s fluent API to alter a table. Convenient if imported statically. + */ + public static AlterTableBuilder alterTable(String name) { + return new AlterTableBuilder().name(name); + } + + /** + * Entry point into the {@link CqlBuilder}'s fluent API to drop a table. Convenient if imported statically. + */ + public static DropTableBuilder dropTable() { + return new DropTableBuilder(); + } + + /** + * Entry point into the {@link CqlBuilder}'s fluent API to drop a table. Convenient if imported statically. + */ + public static DropTableBuilder dropTable(String name) { + return new DropTableBuilder().name(name); + } } \ 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 f19984d9f..69ddfecf1 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 @@ -1,19 +1,14 @@ package org.springframework.data.cassandra.cql.builder; -import static org.springframework.data.cassandra.cql.CqlStringUtils.checkQuotedIdentifier; -import static org.springframework.data.cassandra.cql.CqlStringUtils.ensureNotNull; -import static org.springframework.data.cassandra.cql.CqlStringUtils.escapeSingle; -import static org.springframework.data.cassandra.cql.CqlStringUtils.singleQuote; +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; import static org.springframework.data.cassandra.mapping.KeyType.PARTITION; 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.LinkedHashMap; import java.util.List; import java.util.Map; -import org.springframework.data.cassandra.cql.CqlStringUtils; import org.springframework.data.cassandra.mapping.KeyType; import org.springframework.data.cassandra.mapping.Ordering; @@ -24,12 +19,10 @@ import com.datastax.driver.core.DataType; * * @author Matthew T. Adams */ -public class CreateTableBuilder { +public class CreateTableBuilder extends AbstractTableBuilder { private boolean ifNotExists = false; - private String name; private List columns = new ArrayList(); - private Map options = new LinkedHashMap(); /** * Causes the inclusion of an IF NOT EXISTS clause. @@ -50,74 +43,6 @@ public class CreateTableBuilder { return this; } - /** - * Sets the table name. Quotes are not escaped. - * - * @see CqlStringUtils#escape(CharSequence) - * @see CqlStringUtils#scrub(CharSequence) - * - * @return this - */ - public CreateTableBuilder name(String name) { - checkQuotedIdentifier(name); - this.name = name; - return this; - } - - /** - * Convenience method that calls with(option, null). - * - * @return this - */ - public CreateTableBuilder with(TableOption option) { - return with(option, null); - } - - /** - * 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. - * - * @param option The option to set. - * @param value The value of the option. Must be type-compatible with the {@link TableOption}. - * @return this - * @see #with(String, Object, boolean, boolean) - */ - public CreateTableBuilder with(TableOption option, Object value) { - option.checkValue(value); - return with(option.getName(), value, option.escapesValue(), option.quotesValue()); - } - - /** - * Adds the given option by name to this table's options. - *

- * Options that have null values are considered single string options where the name of the option is the - * string to be used. Otherwise, the result of {@link Object#toString()} is considered to be the value of the option - * with the given name. The value, after conversion to string, may have embedded single quotes escaped according to - * parameter escape and may be single-quoted according to parameter quote. - * - * @param name The name of the option - * @param value The value of the option. If null, the value is ignored and the option is considered to be - * composed of only the name, otherwise the value's {@link Object#toString()} value is used. - * @param escape Whether to escape the value via {@link CqlStringUtils#escapeSingle(Object)}. Ignored if given value - * is an instance of a {@link Map}. - * @param quote Whether to quote the value via {@link CqlStringUtils#singleQuote(Object)}. Ignored if given value is - * an instance of a {@link Map}. - * @return this - */ - public CreateTableBuilder with(String name, Object value, boolean escape, boolean quote) { - if (!(value instanceof Map)) { - if (escape) { - value = escapeSingle(value); - } - if (quote) { - value = singleQuote(value); - } - } - options().put(name, value); - return this; - } - public CreateTableBuilder column(String name, DataType type) { return column(name, type, null, null); } @@ -143,30 +68,27 @@ public class CreateTableBuilder { return columns == null ? columns = new ArrayList() : columns; } - protected Map options() { - return options == null ? options = new LinkedHashMap() : options; - } + public StringBuilder toCql(StringBuilder cql) { - public String toCql() { - - StringBuilder cql = new StringBuilder(); + cql = noNull(cql); preambleCql(cql); columnsAndOptionsCql(cql); cql.append(";"); - return cql.toString(); + return cql; } protected StringBuilder preambleCql(StringBuilder cql) { - return (cql = ensureNotNull(cql)).append("CREATE TABLE ").append(ifNotExists ? "IF NOT EXISTS " : "").append(name); + return noNull(cql).append("CREATE TABLE ").append(ifNotExists ? "IF NOT EXISTS " : "") + .append(getNameAsIdentifier()); } @SuppressWarnings("unchecked") protected StringBuilder columnsAndOptionsCql(StringBuilder cql) { - cql = ensureNotNull(cql); + cql = noNull(cql); // begin columns cql.append(" ("); @@ -275,35 +197,8 @@ public class CreateTableBuilder { cql.append(" = "); - 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()) { - if (mapFirst) { - mapFirst = false; - } else { - cql.append(", "); - } - - Option option = entry.getKey(); - cql.append(singleQuote(option.getName())); // entries in map keys are always quoted - cql.append(" : "); - Object entryValue = entry.getValue(); - entryValue = entryValue == null ? "" : entryValue.toString(); - if (option.escapesValue()) { - entryValue = escapeSingle(value); - } - if (option.quotesValue()) { - entryValue = singleQuote(value); - } - cql.append(entryValue); - } - cql.append(" }"); - + if (value instanceof Map) { + optionValueMap((Map) value, cql); continue; // end non-empty value map } @@ -316,9 +211,4 @@ public class CreateTableBuilder { return cql; } - - @Override - public String toString() { - return toCql(); - } } diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/DropColumn.java b/src/main/java/org/springframework/data/cassandra/cql/builder/DropColumn.java new file mode 100644 index 000000000..e3b20dcab --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/DropColumn.java @@ -0,0 +1,15 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; + +public class DropColumn extends ColumnChange { + + public DropColumn(String name) { + super(name); + } + + @Override + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("DROP ").append(getNameAsIdentifier()); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/builder/DropTableBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/builder/DropTableBuilder.java new file mode 100644 index 000000000..9b3432881 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/DropTableBuilder.java @@ -0,0 +1,38 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.data.cassandra.cql.CqlStringUtils.noNull; +import static org.springframework.data.cassandra.cql.CqlStringUtils.identifize; + +public class DropTableBuilder { + + private String name; + private boolean ifExists; + + public DropTableBuilder name(String name) { + checkIdentifier(name); + this.name = name; + return this; + } + + public DropTableBuilder ifExists() { + return ifExists(true); + } + + public DropTableBuilder ifExists(boolean ifExists) { + this.ifExists = ifExists; + return this; + } + + public StringBuilder toCql(StringBuilder cql) { + return noNull(cql).append("DROP TABLE ").append(ifExists ? "IF EXISTS " : "").append(identifize(name)); + } + + public String toCql() { + return toCql(null).toString(); + } + + public String toString() { + return toCql(); + } +} 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 index ddfe39d62..8bebc8095 100644 --- a/src/main/java/org/springframework/data/cassandra/cql/builder/MapBuilder.java +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/MapBuilder.java @@ -7,7 +7,7 @@ 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). + * actually {@link #build()} it (or forget to). * * @author Matthew T. Adams * @param The key type of the map. diff --git a/src/test/java/org/springframework/data/cassandra/cql/CqlStringUtilsTest.java b/src/test/java/org/springframework/data/cassandra/cql/CqlStringUtilsTest.java new file mode 100644 index 000000000..dfbc60b02 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/cql/CqlStringUtilsTest.java @@ -0,0 +1,19 @@ +package org.springframework.data.cassandra.cql; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.springframework.data.cassandra.cql.CqlStringUtils.isQuotedIdentifier; +import static org.springframework.data.cassandra.cql.CqlStringUtils.isUnquotedIdentifier; + +import org.junit.Test; + +public class CqlStringUtilsTest { + + @Test + public void testIsQuotedIdentifier() throws Exception { + assertFalse(isQuotedIdentifier("my\"id")); + assertTrue(isQuotedIdentifier("my\"\"id")); + assertFalse(isUnquotedIdentifier("my\"id")); + assertTrue(isUnquotedIdentifier("myid")); + } +} diff --git a/src/test/java/org/springframework/data/cassandra/cql/builder/AlterTableBuilderTest.java b/src/test/java/org/springframework/data/cassandra/cql/builder/AlterTableBuilderTest.java new file mode 100644 index 000000000..93837984c --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/AlterTableBuilderTest.java @@ -0,0 +1,24 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.builder.CqlBuilder.alterTable; + +import org.junit.Test; + +import com.datastax.driver.core.DataType; + +public class AlterTableBuilderTest { + + @Test + public void testAlterTableBuilder() throws Exception { + String name = "mytable"; + DataType addedType = DataType.timeuuid(); + String addedName = "added_column"; + DataType alteredType = DataType.text(); + String alteredName = "altered_column"; + String droppedName = "dropped"; + + String cql = alterTable().name(name).add(addedName, addedType).alter(alteredName, alteredType).drop(droppedName) + .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 index 00018ae15..fa59954f8 100644 --- a/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilderTest.java @@ -18,7 +18,7 @@ public class CreateTableBuilderTest { @Test public void createTableTest() { - String name = "mytable"; + String name = "my\"\"table"; DataType type0 = DataType.text(); String partKey0 = "partitionKey0"; String partition1 = "partitionKey1"; @@ -38,7 +38,7 @@ public class CreateTableBuilderTest { 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;", + "CREATE TABLE IF NOT EXISTS \"my\"\"table\" (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/DropTableBuilderTest.java b/src/test/java/org/springframework/data/cassandra/cql/builder/DropTableBuilderTest.java new file mode 100644 index 000000000..0982b3c40 --- /dev/null +++ b/src/test/java/org/springframework/data/cassandra/cql/builder/DropTableBuilderTest.java @@ -0,0 +1,15 @@ +package org.springframework.data.cassandra.cql.builder; + +import static org.springframework.data.cassandra.cql.builder.CqlBuilder.dropTable; + +import org.junit.Test; + +public class DropTableBuilderTest { + + @Test + public void testDropTableBuilder() throws Exception { + String cql = dropTable().name("mytable").ifExists().toCql(); + + System.out.println(cql); + } +}