diff --git a/src/main/java/org/springframework/data/cassandra/cql/CqlBuilder.java b/src/main/java/org/springframework/data/cassandra/cql/CqlBuilder.java deleted file mode 100644 index 8f8d7cdd7..000000000 --- a/src/main/java/org/springframework/data/cassandra/cql/CqlBuilder.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.springframework.data.cassandra.cql; - -public class CqlBuilder { - - public static CreateTable createTable() { - return new CreateTable(); - } -} diff --git a/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java b/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java new file mode 100644 index 000000000..6d8ee4a4b --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/CqlStringUtils.java @@ -0,0 +1,92 @@ +package org.springframework.data.cassandra.cql; + +import java.util.regex.Pattern; + +public class CqlStringUtils { + + protected static final String SINGLE_QUOTE = "\'"; + protected static final String DOUBLE_SINGLE_QUOTE = "\'\'"; + 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) { + 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 boolean isIdentifier(CharSequence chars) { + return 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 boolean isQuotedIdentifier(CharSequence chars) { + return QUOTED_IDENTIFIER_PATTERN.matcher(chars).matches(); + } + + public static void checkQuotedIdentifier(CharSequence chars) { + if (!CqlStringUtils.isQuotedIdentifier(chars)) { + throw new IllegalArgumentException("[" + chars + "] is not a valid CQL quoted identifier"); + } + } + + /** + * 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()); + } + + /** + * Doubles single quote characters and doubles double quote characters (' -> '' and " -> ""). Given + * null, returns null. + */ + public static String escape(Object thing) { + return escapeDouble(escapeSingle(thing)); + } + + /** + * Doubles single quote characters (' -> ''). Given null, returns null. + */ + public static String escapeSingle(Object things) { + return things == null ? (String) null : things.toString().replace(SINGLE_QUOTE, DOUBLE_SINGLE_QUOTE); + } + + /** + * 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); + } + + /** + * Surrounds given object's {@link Object#toString()} with single quotes. Given null, returns + * null. + */ + public static String singleQuote(Object thing) { + return thing == null ? (String) null : new StringBuilder().append(SINGLE_QUOTE).append(thing).append(SINGLE_QUOTE) + .toString(); + } + + /** + * Surrounds given object's {@link Object#toString()} with double quotes. Given null, returns + * null. + */ + public static String doubleQuote(Object thing) { + return thing == null ? (String) null : new StringBuilder().append(DOUBLE_QUOTE).append(thing).append(DOUBLE_QUOTE) + .toString(); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/cql/CreateTable.java b/src/main/java/org/springframework/data/cassandra/cql/CreateTable.java deleted file mode 100644 index 3790298d6..000000000 --- a/src/main/java/org/springframework/data/cassandra/cql/CreateTable.java +++ /dev/null @@ -1,328 +0,0 @@ -package org.springframework.data.cassandra.cql; - -import static org.springframework.data.cassandra.cql.CreateTable.Column.Key.PARTITION; -import static org.springframework.data.cassandra.cql.CreateTable.Column.Key.PRIMARY; -import static org.springframework.data.cassandra.cql.CreateTable.Column.Order.ASCENDING; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.data.cassandra.cql.CreateTable.Column.Key; -import org.springframework.data.cassandra.cql.CreateTable.Column.Order; -import org.springframework.util.Assert; - -public class CreateTable { - - protected static StringBuilder ensure(StringBuilder sb) { - return sb == null ? new StringBuilder() : sb; - } - - public static class Column { - - public enum Key { - PARTITION, PRIMARY - } - - public enum Order { - ASCENDING("ASC"), DESCENDING("DESC"); - - private String cql; - - private Order(String cql) { - this.cql = cql; - } - - public String cql() { - return cql; - } - } - - private String name; - private String type; - private Key key; - private Order order = ASCENDING; - - public Column name(String name) { - Assert.hasLength(name); - this.name = name; - return this; - } - - public Column type(String type) { - Assert.hasLength(type); - this.type = type; - return this; - } - - public Column key(Key key) { - return key(key, ASCENDING); - } - - public Column key(Key key, Order order) { - this.key = key; - this.order = order; - return this; - } - - public void assertValid() { - // TODO - } - - public StringBuilder cql(StringBuilder cql) { - return (cql = ensure(cql)).append(name).append(" ").append(type); - } - - @Override - public String toString() { - return cql(null).toString(); - } - } - - private boolean ifNotExists = false; - private String name; - private List columns = new ArrayList(); - private Map options = new HashMap(); - - public CreateTable ifNotExists() { - return ifNotExists(true); - } - - public CreateTable ifNotExists(boolean ifNotExists) { - this.ifNotExists = ifNotExists; - return this; - } - - public CreateTable name(String name) { - Assert.hasLength(name); - this.name = name; - return this; - } - - public CreateTable options(Map options) { - this.options = options; - return this; - } - - public CreateTable option(String name, Object value) { - options().put(name, value); - return this; - } - - public CreateTable columns(List columns) { - columns().addAll(columns); - return this; - } - - public CreateTable column(String name, String type) { - return column(name, type, null, null); - } - - public CreateTable partition(String name, String type) { - return partition(name, type, null); - } - - public CreateTable partition(String name, String type, Order order) { - return column(name, type, PARTITION, order); - } - - public CreateTable primary(String name, String type) { - return primary(name, type, null); - } - - public CreateTable primary(String name, String type, Order order) { - return column(name, type, PRIMARY, order); - } - - public CreateTable column(String name, String type, Key key, Order order) { - columns().add(new Column().name(name).type(type).key(key, order)); - return this; - } - - protected List columns() { - return columns == null ? columns = new ArrayList() : columns; - } - - protected Map options() { - return options == null ? options = new HashMap() : options; - } - - public String cql() { - return cql(true); - } - - protected String cql(boolean validate) { - if (validate) { - assertValid(); - } - - StringBuilder cql = new StringBuilder(); - - preamble(cql); - columnsAndOptions(cql); - - cql.append(";"); - - return cql.toString(); - } - - protected StringBuilder preamble(StringBuilder cql) { - return (cql = ensure(cql)).append("CREATE TABLE ").append(ifNotExists ? "IF NOT EXISTS " : "").append(name); - } - - @SuppressWarnings("unchecked") - protected StringBuilder columnsAndOptions(StringBuilder cql) { - - cql = ensure(cql); - - // begin columns - cql.append(" ("); - - List partitionKeys = new ArrayList(); - List primaryKeys = new ArrayList(); - for (Column col : columns) { - col.cql(cql).append(", "); - - if (col.key == PARTITION) { - partitionKeys.add(col); - } else if (col.key == PRIMARY) { - primaryKeys.add(col); - } - } - - // begin primary key clause - cql.append("PRIMARY KEY "); - StringBuilder partitions = new StringBuilder(); - StringBuilder primaries = new StringBuilder(); - - if (partitionKeys.size() > 1) { - partitions.append("("); - } - - StringBuilder clustering = null; - - boolean clusteringFirst = true; - boolean first = true; - for (Column col : partitionKeys) { - if (first) { - first = false; - } else { - partitions.append(", "); - } - partitions.append(col.name); - - if (col.order != null) { // then ordering specified - if (clustering == null) { // then initialize clustering clause - clustering = new StringBuilder().append("CLUSTERING ORDER BY ("); - } - if (clusteringFirst) { - clusteringFirst = false; - } else { - clustering.append(", "); - } - clustering.append(col.name).append(" ").append(col.order.cql()); - } - } - if (clustering != null) { // then end clustering option - clustering.append(")"); - } - if (partitionKeys.size() > 1) { - partitions.append(")"); - } - - first = true; - for (Column col : primaryKeys) { - if (first) { - first = false; - } else { - primaries.append(", "); - } - primaries.append(col.name); - } - boolean parenthesize = partitionKeys.size() + primaryKeys.size() > 1; - - cql.append(parenthesize ? "(" : ""); - cql.append(partitions); - cql.append(primaryKeys.size() > 0 ? ", " : ""); - cql.append(primaries); - cql.append(parenthesize ? ")" : ""); - // end primary key clause - // end columns - - // begin options - // begin option clause - if (clustering != null || !options.isEmpty()) { - // option preamble - first = true; - - cql.append(" WITH "); - - if (clustering != null) { - cql.append(clustering); - first = false; - } - if (!options.isEmpty()) { - for (String name : options.keySet()) { - // append AND if we're not on first option - if (first) { - first = false; - } else { - cql.append(" AND "); - } - - // append = - cql.append(name); - - Object value = options.get(name); - if (value == null) { // then assume string-only, valueless option like "COMPACT STORAGE" - continue; - } - if (value instanceof CharSequence) { // then value is a string - cql.append(" = '").append(value.toString()).append("'"); - continue; // end string option - } - - 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(", "); - } - - cql.append("'").append(entry.getKey()).append("'"); // 'name' - cql.append(" : "); - Object entryValue = entry.getValue(); - cql.append("'").append(entryValue == null ? "" : entryValue.toString()).append("'"); // 'value' - } - cql.append(" } "); - - continue; // end non-empty value map - } - - // else not a string, so just use unquoted string version of value - cql.append(value.toString()); - } - } - } - // end options - - return cql; - } - - public void assertValid() { - // TODO - } - - @Override - public String toString() { - return cql(false); - } -} 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 new file mode 100644 index 000000000..65c25d8d5 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/ColumnBuilder.java @@ -0,0 +1,163 @@ +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.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; + +/** + * Builder class to help construct CQL statements that involve column manipulation. Not threadsafe. + *

+ * Use {@link #name(String)} and {@link #type(String)} to set the name and type of the column, respectively. To specify + * a PRIMARY KEY column, use {@link #primary()} or {@link #primary(Ordering)}. To specify that the + * PRIMARY KEY column is or is part of the partition key, use {@link #partition()} instead of + * {@link #primary()} or {@link #primary(Ordering)}. + * + * @author Matthew T. Adams + */ +public class ColumnBuilder { + + /** + * Default ordering of primary key fields is {@link Ordering#ASCENDING}. + */ + public static final Ordering DFAULT_ORDERING = ASCENDING; + + private String name; + private String type; + private KeyType keyType; + private Ordering ordering; + + /** + * Sets the column's name. Quotes are not escaped. + * + * @see CqlStringUtils#escape(CharSequence) + * @see CqlStringUtils#scrub(CharSequence) + * + * @return this + */ + public ColumnBuilder name(String name) { + checkQuotedIdentifier(name); + this.name = name; + return this; + } + + /** + * Sets the column's type. + * + * @return this + */ + public ColumnBuilder type(String type) { + this.type = type; + return this; + } + + /** + * Identifies this column as a primary key column that is also part of a partition key. Sets the column's + * {@link #keyType} to {@link KeyType#PARTITION} and its {@link #ordering} to null. + * + * @return this + */ + public ColumnBuilder partition() { + return partition(true); + } + + /** + * Toggles the identification of this column as a primary key column that also is or is part of a partition key. Sets + * {@link #ordering} to null and, if the given boolean is true, then sets the column's + * {@link #keyType} to {@link KeyType#PARTITION}, else sets it to null. + * + * @return this + */ + public ColumnBuilder partition(boolean partition) { + this.keyType = partition ? PARTITION : null; + this.ordering = null; + return this; + } + + /** + * Identifies this column as a primary key column with default ordering. Sets the column's {@link #keyType} to + * {@link KeyType#PRIMARY} and its {@link #ordering} to {@link #DFAULT_ORDERING}. + * + * @return this + */ + public ColumnBuilder primary() { + return primary(DFAULT_ORDERING); + } + + /** + * Identifies this column as a primary key column with the given ordering. Sets the column's {@link #keyType} to + * {@link KeyType#PRIMARY} and its {@link #ordering} to the given {@link Ordering}. + * + * @return this + */ + public ColumnBuilder primary(Ordering order) { + return primary(order, true); + } + + /** + * Toggles the identification of this column as a primary key column. If the given boolean is true, then + * sets the column's {@link #keyType} to {@link KeyType#PARTITION} and {@link #ordering} to the given {@link Ordering} + * , else sets both {@link #keyType} and {@link #ordering} to null. + * + * @return this + */ + public ColumnBuilder primary(Ordering order, boolean primary) { + this.keyType = primary ? PRIMARY : null; + this.ordering = primary ? order : null; + return this; + } + + /** + * Sets the column's {@link #keyType}. + * + * @return this + */ + /* package */ColumnBuilder keyType(KeyType keyType) { + this.keyType = keyType; + return this; + } + + /** + * Sets the column's {@link #ordering}. + * + * @return this + */ + /* package */ColumnBuilder ordering(Ordering ordering) { + this.ordering = ordering; + return this; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public KeyType getKeyType() { + return keyType; + } + + public Ordering getOrdering() { + return ordering; + } + + public String toCql() { + return toCql(null).toString(); + } + + public StringBuilder toCql(StringBuilder cql) { + return (cql = ensureNotNull(cql)).append(name).append(" ").append(type); + } + + @Override + public String toString() { + return toCql(null).append(" /* key=").append(keyType).append(", order=").append(ordering).append(" */").toString(); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..0a6490328 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/CqlBuilder.java @@ -0,0 +1,11 @@ +package org.springframework.data.cassandra.cql.builder; + +public class CqlBuilder { + + /** + * Entry point into the {@link CqlBuilder}'s fluent API to create a table. Convenient if imported statically. + */ + public static CreateTableBuilder createTable() { + return new CreateTableBuilder(); + } +} 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 new file mode 100644 index 000000000..a89949191 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/cql/builder/CreateTableBuilder.java @@ -0,0 +1,336 @@ +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.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.HashMap; +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; + +/** + * Builder class to construct CQL for a CREATE TABLE statement. Not threadsafe. + * + * @author Matthew T. Adams + */ +public class CreateTableBuilder { + + private boolean ifNotExists = false; + private String name; + private List columns = new ArrayList(); + private Map options = new HashMap(); + + /** + * Causes the inclusion of an IF NOT EXISTS clause. + * + * @return this + */ + public CreateTableBuilder ifNotExists() { + return ifNotExists(true); + } + + /** + * Toggles the inclusion of an IF NOT EXISTS clause. + * + * @return this + */ + public CreateTableBuilder ifNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + 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; + } + + /** + * Adds the given single-string option with no value to this table's options. Convenient overload of + * with(string, null, false, false). + * + * @param singleStringOption + * @return + */ + public CreateTableBuilder with(String string) { + return with(string, null, false, false); + } + + /** + * 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) + * + * @see #with(String, Object, boolean, boolean) + * @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); + } + + /** + * 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, String type) { + return column(name, type, null, null); + } + + public CreateTableBuilder partitionColumn(String name, String type) { + return column(name, type, PARTITION, null); + } + + public CreateTableBuilder primaryKeyColumn(String name, String type) { + return primaryKeyColumn(name, type, ASCENDING); + } + + public CreateTableBuilder primaryKeyColumn(String name, String type, Ordering order) { + return column(name, type, PRIMARY, order); + } + + protected CreateTableBuilder column(String name, String type, KeyType key, Ordering order) { + columns().add(new ColumnBuilder().name(name).type(type).keyType(key).ordering(order)); + 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; + } + + public String toCql() { + + StringBuilder cql = new StringBuilder(); + + preambleCql(cql); + columnsAndOptionsCql(cql); + + cql.append(";"); + + return cql.toString(); + } + + protected StringBuilder preambleCql(StringBuilder cql) { + return (cql = ensureNotNull(cql)).append("CREATE TABLE ").append(ifNotExists ? "IF NOT EXISTS " : "").append(name); + } + + @SuppressWarnings("unchecked") + protected StringBuilder columnsAndOptionsCql(StringBuilder cql) { + + cql = ensureNotNull(cql); + + // begin columns + cql.append(" ("); + + List partitionKeys = new ArrayList(); + List primaryKeys = new ArrayList(); + for (ColumnBuilder col : columns) { + col.toCql(cql).append(", "); + + if (col.getKeyType() == PARTITION) { + partitionKeys.add(col); + } else if (col.getKeyType() == PRIMARY) { + primaryKeys.add(col); + } + } + + // begin primary key clause + cql.append("PRIMARY KEY "); + StringBuilder partitions = new StringBuilder(); + StringBuilder primaries = new StringBuilder(); + + if (partitionKeys.size() > 1) { + partitions.append("("); + } + + boolean first = true; + for (ColumnBuilder col : partitionKeys) { + if (first) { + first = false; + } else { + partitions.append(", "); + } + partitions.append(col.getName()); + + } + if (partitionKeys.size() > 1) { + partitions.append(")"); + } + + StringBuilder clustering = null; + boolean clusteringFirst = true; + first = true; + for (ColumnBuilder col : primaryKeys) { + if (first) { + first = false; + } else { + primaries.append(", "); + } + primaries.append(col.getName()); + + if (col.getOrdering() != null) { // then ordering specified + if (clustering == null) { // then initialize clustering clause + clustering = new StringBuilder().append("CLUSTERING ORDER BY ("); + } + if (clusteringFirst) { + clusteringFirst = false; + } else { + clustering.append(", "); + } + clustering.append(col.getName()).append(" ").append(col.getOrdering().cql()); + } + } + if (clustering != null) { // then end clustering option + clustering.append(")"); + } + + boolean parenthesize = partitionKeys.size() + primaryKeys.size() > 1; + + cql.append(parenthesize ? "(" : ""); + cql.append(partitions); + cql.append(primaryKeys.size() > 0 ? ", " : ""); + cql.append(primaries); + cql.append(parenthesize ? ")" : ""); + // end primary key clause + // end columns + + // begin options + // begin option clause + if (clustering != null || !options.isEmpty()) { + // option preamble + first = true; + + cql.append(" WITH "); + + if (clustering != null) { + cql.append(clustering); + first = false; + } + if (!options.isEmpty()) { + for (String name : options.keySet()) { + // append AND if we're not on first option + if (first) { + first = false; + } else { + cql.append(" AND "); + } + + // append = + cql.append(name); + + Object value = options.get(name); + if (value == null) { // then assume string-only, valueless option like "COMPACT STORAGE" + continue; + } + + 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(", "); + } + + cql.append("'").append(entry.getKey()).append("'"); // 'name' + cql.append(" : "); + Object entryValue = entry.getValue(); + cql.append("'").append(entryValue == null ? "" : entryValue.toString()).append("'"); // 'value' + } + cql.append(" }"); + + continue; // end non-empty value map + } + + // else just use value as string + cql.append(value.toString()); + } + } + } + // end options + + return cql; + } + + @Override + public String toString() { + return toCql(); + } +} diff --git a/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java b/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java new file mode 100644 index 000000000..6d8540ee2 --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/KeyType.java @@ -0,0 +1,19 @@ +package org.springframework.data.cassandra.mapping; + +/** + * Values representing primary key column types. + * + * @author Matthew T. Adams + */ +public enum KeyType { + + /** + * Used for a column that is a primary key 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. + */ + PRIMARY +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/cassandra/mapping/Ordering.java b/src/main/java/org/springframework/data/cassandra/mapping/Ordering.java new file mode 100644 index 000000000..2433201ce --- /dev/null +++ b/src/main/java/org/springframework/data/cassandra/mapping/Ordering.java @@ -0,0 +1,32 @@ +package org.springframework.data.cassandra.mapping; + +/** + * Enum for Cassandra primary key column ordering. + * + * @author Matthew T. Adams + */ +public enum Ordering { + + /** + * Ascending Cassandra column ordering. + */ + ASCENDING("ASC"), + + /** + * Descending Cassandra column ordering. + */ + DESCENDING("DESC"); + + private String cql; + + private Ordering(String cql) { + this.cql = cql; + } + + /** + * Returns the CQL keyword of this {@link Ordering}. + */ + public String cql() { + return cql; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/cassandra/mapping/RowId.java b/src/main/java/org/springframework/data/cassandra/mapping/RowId.java index d335937a6..10ef7608a 100644 --- a/src/main/java/org/springframework/data/cassandra/mapping/RowId.java +++ b/src/main/java/org/springframework/data/cassandra/mapping/RowId.java @@ -31,5 +31,4 @@ import org.springframework.data.annotation.Id; @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Id public @interface RowId { - } diff --git a/src/test/java/org/springframework/data/cassandra/cql/CreateTableTest.java b/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java similarity index 55% rename from src/test/java/org/springframework/data/cassandra/cql/CreateTableTest.java rename to src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java index 15901fc95..799e28b51 100644 --- a/src/test/java/org/springframework/data/cassandra/cql/CreateTableTest.java +++ b/src/test/java/org/springframework/data/cassandra/cql/CreateTableBuilderTest.java @@ -1,14 +1,14 @@ package org.springframework.data.cassandra.cql; -import static org.springframework.data.cassandra.cql.CqlBuilder.createTable; +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.CreateTable.Column.Order; +import org.springframework.data.cassandra.cql.builder.CreateTableBuilder; -public class CreateTableTest { +public class CreateTableBuilderTest { @Test public void createTableTest() { @@ -21,8 +21,6 @@ public class CreateTableTest { String column1 = "column1"; String type2 = "text"; String column2 = "column2"; - Object value1 = null; - String option1 = "COMPACT STORAGE"; Object value2 = "this is a comment"; String option2 = "comment"; Object value3 = "0.00075"; @@ -31,12 +29,12 @@ public class CreateTableTest { value4.put("class", "LeveledCompactionStrategy"); String option4 = "compaction"; - CreateTable builder = createTable().ifNotExists().name(name).partition(partition0, type0, Order.ASCENDING) - .partition(partition1, type0, Order.DESCENDING).primary(primary0, type0).column(column1, type1) - .column(column2, type2).option(option1, value1).option(option2, value2).option(option3, value3) - .option(option4, value4); + CreateTableBuilder builder = createTable().ifNotExists().name(name).partitionColumn(partition0, type0) + .partitionColumn(partition1, type0).primaryKeyColumn(primary0, type0).column(column1, type1) + .column(column2, type2).withQuoted(option2, value2).withUnquoted(option3, value3).with(option4, value4) + .withCompactStorage(); - String cql = builder.cql(); + String cql = builder.toCql(); System.out.println(cql); } }