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);
}
}