diff --git a/CHANGES.txt b/CHANGES.txt
index 66ab562ab2..03d2e699a7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -55,6 +55,7 @@
* Add SnapshotCommand to trigger snapshot on remote node (CASSANDRA-3721)
* Make CFMetaData conversions to/from thrift/native schema inverses
(CASSANDRA_3559)
+ * Add initial code for CQL 3.0-beta (CASSANDRA-3781)
1.0.8
diff --git a/build.xml b/build.xml
index d382e340dd..2a46f37fae 100644
--- a/build.xml
+++ b/build.xml
@@ -202,6 +202,9 @@
+
@@ -213,9 +216,17 @@
-
+
+
+
+
+
+
-
+
diff --git a/interface/cassandra.thrift b/interface/cassandra.thrift
index e28b236a23..08b776edcb 100644
--- a/interface/cassandra.thrift
+++ b/interface/cassandra.thrift
@@ -46,7 +46,7 @@ namespace rb CassandraThrift
# for every edit that doesn't result in a change to major/minor.
#
# See the Semantic Versioning Specification (SemVer) http://semver.org.
-const string VERSION = "19.25.0"
+const string VERSION = "19.26.0"
#
@@ -415,6 +415,8 @@ struct CfDef {
32: optional map compression_options,
33: optional double bloom_filter_fp_chance,
34: optional string caching="keys_only",
+ 35: optional list column_aliases,
+ 36: optional binary value_alias,
}
/* describes a keyspace. */
@@ -714,6 +716,6 @@ service Cassandra {
2:UnavailableException ue,
3:TimedOutException te,
4:SchemaDisagreementException sde)
-
+ void set_cql_version(1: required string version) throws (1:InvalidRequestException ire)
}
diff --git a/src/avro/internode.genavro b/src/avro/internode.genavro
index c36a83f20b..36c2cba400 100644
--- a/src/avro/internode.genavro
+++ b/src/avro/internode.genavro
@@ -67,6 +67,8 @@ protocol InterNode {
union { null, map } compression_options = null;
union { null, double } bloom_filter_fp_chance = null;
union { null, string } caching = null;
+ union { null, array } column_aliases = null;
+ union { null, bytes } value_alias = null;
}
@aliases(["org.apache.cassandra.config.avro.KsDef"])
diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java
index 9bd731a002..a82639d91d 100644
--- a/src/java/org/apache/cassandra/config/CFMetaData.java
+++ b/src/java/org/apache/cassandra/config/CFMetaData.java
@@ -32,6 +32,7 @@ import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.cassandra.cql3.CFDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.compaction.AbstractCompactionStrategy;
@@ -173,6 +174,8 @@ public final class CFMetaData
// thrift compatibility
private double mergeShardsChance; // default 0.1, chance [0.0, 1.0] of merging old shards during replication
private ByteBuffer keyAlias; // default NULL
+ private List columnAliases = new ArrayList();
+ private ByteBuffer valueAlias; // default NULL
private Double bloomFilterFpChance; // default NULL
private Caching caching; // default KEYS_ONLY (possible: all, key_only, row_only, none)
@@ -182,17 +185,24 @@ public final class CFMetaData
private CompressionParameters compressionParameters;
+ // Processed infos used by CQL. This can be fully reconstructed from the CFMedata,
+ // so it's not saved on disk. It is however costlyish to recreate for each query
+ // so we cache it here (and update on each relevant CFMetadata change)
+ private CFDefinition cqlCfDef;
+
public CFMetaData comment(String prop) { comment = enforceCommentNotNull(prop); return this;}
public CFMetaData readRepairChance(double prop) {readRepairChance = prop; return this;}
public CFMetaData replicateOnWrite(boolean prop) {replicateOnWrite = prop; return this;}
public CFMetaData gcGraceSeconds(int prop) {gcGraceSeconds = prop; return this;}
- public CFMetaData defaultValidator(AbstractType> prop) {defaultValidator = prop; return this;}
- public CFMetaData keyValidator(AbstractType> prop) {keyValidator = prop; return this;}
+ public CFMetaData defaultValidator(AbstractType> prop) {defaultValidator = prop; updateCfDef(); return this;}
+ public CFMetaData keyValidator(AbstractType> prop) {keyValidator = prop; updateCfDef(); return this;}
public CFMetaData minCompactionThreshold(int prop) {minCompactionThreshold = prop; return this;}
public CFMetaData maxCompactionThreshold(int prop) {maxCompactionThreshold = prop; return this;}
public CFMetaData mergeShardsChance(double prop) {mergeShardsChance = prop; return this;}
- public CFMetaData keyAlias(ByteBuffer prop) {keyAlias = prop; return this;}
- public CFMetaData columnMetadata(Map prop) {column_metadata = prop; return this;}
+ public CFMetaData keyAlias(ByteBuffer prop) {keyAlias = prop; updateCfDef(); return this;}
+ public CFMetaData columnAliases(List prop) {columnAliases = prop; updateCfDef(); return this;}
+ public CFMetaData valueAlias(ByteBuffer prop) {valueAlias = prop; updateCfDef(); return this;}
+ public CFMetaData columnMetadata(Map prop) {column_metadata = prop; updateCfDef(); return this;}
public CFMetaData compactionStrategyClass(Class extends AbstractCompactionStrategy> prop) {compactionStrategyClass = prop; return this;}
public CFMetaData compactionStrategyOptions(Map prop) {compactionStrategyOptions = prop; return this;}
public CFMetaData compressionParameters(CompressionParameters prop) {compressionParameters = prop; return this;}
@@ -246,6 +256,7 @@ public final class CFMetaData
keyValidator = BytesType.instance;
comment = "";
keyAlias = null; // This qualifies as a 'strange default'.
+ valueAlias = null;
column_metadata = new HashMap();
try
@@ -259,6 +270,7 @@ public final class CFMetaData
compactionStrategyOptions = new HashMap();
compressionParameters = new CompressionParameters(null);
+ updateCfDef(); // init cqlCfDef
}
private static CFMetaData newSystemMetadata(String cfName, int cfId, String comment, AbstractType> comparator, AbstractType> subcc)
@@ -369,6 +381,8 @@ public final class CFMetaData
if (cf.max_compaction_threshold != null) { newCFMD.maxCompactionThreshold(cf.max_compaction_threshold); }
if (cf.merge_shards_chance != null) { newCFMD.mergeShardsChance(cf.merge_shards_chance); }
if (cf.key_alias != null) { newCFMD.keyAlias(cf.key_alias); }
+ if (cf.column_aliases != null) { newCFMD.columnAliases(fixAvroRetardation(cf.column_aliases)); }
+ if (cf.value_alias != null) { newCFMD.valueAlias(cf.value_alias); }
if (cf.compaction_strategy != null)
{
try
@@ -418,6 +432,18 @@ public final class CFMetaData
.bloomFilterFpChance(cf.bloom_filter_fp_chance)
.caching(caching);
}
+
+ /*
+ * Avro handles array with it's own class, GenericArray, that extends
+ * AbstractList but redefine equals() in a way that violate List.equals()
+ * specification (basically only a GenericArray can ever be equal to a
+ * GenericArray).
+ * (Concretely, keeping the list returned by avro breaks DefsTest.saveAndRestore())
+ */
+ private static List fixAvroRetardation(List array)
+ {
+ return new ArrayList(array);
+ }
public String getComment()
{
@@ -469,6 +495,21 @@ public final class CFMetaData
return keyAlias == null ? DEFAULT_KEY_NAME : keyAlias;
}
+ public ByteBuffer getKeyAlias()
+ {
+ return keyAlias;
+ }
+
+ public List getColumnAliases()
+ {
+ return columnAliases;
+ }
+
+ public ByteBuffer getValueAlias()
+ {
+ return valueAlias;
+ }
+
public CompressionParameters compressionParameters()
{
return compressionParameters;
@@ -524,6 +565,8 @@ public final class CFMetaData
.append(column_metadata, rhs.column_metadata)
.append(mergeShardsChance, rhs.mergeShardsChance)
.append(keyAlias, rhs.keyAlias)
+ .append(columnAliases, rhs.columnAliases)
+ .append(valueAlias, rhs.valueAlias)
.append(compactionStrategyClass, rhs.compactionStrategyClass)
.append(compactionStrategyOptions, rhs.compactionStrategyOptions)
.append(compressionParameters, rhs.compressionParameters)
@@ -552,6 +595,8 @@ public final class CFMetaData
.append(column_metadata)
.append(mergeShardsChance)
.append(keyAlias)
+ .append(columnAliases)
+ .append(valueAlias)
.append(compactionStrategyClass)
.append(compactionStrategyOptions)
.append(compressionParameters)
@@ -613,6 +658,8 @@ public final class CFMetaData
if (cf_def.isSetMax_compaction_threshold()) { newCFMD.maxCompactionThreshold(cf_def.max_compaction_threshold); }
if (cf_def.isSetMerge_shards_chance()) { newCFMD.mergeShardsChance(cf_def.merge_shards_chance); }
if (cf_def.isSetKey_alias()) { newCFMD.keyAlias(cf_def.key_alias); }
+ if (cf_def.isSetColumn_aliases() && cf_def.column_aliases != null) { newCFMD.columnAliases(cf_def.column_aliases); }
+ if (cf_def.isSetValue_alias()) { newCFMD.valueAlias(cf_def.value_alias); }
if (cf_def.isSetKey_validation_class()) { newCFMD.keyValidator(TypeParser.parse(cf_def.key_validation_class)); }
if (cf_def.isSetCompaction_strategy())
newCFMD.compactionStrategyClass = createCompactionStrategy(cf_def.compaction_strategy);
@@ -698,6 +745,8 @@ public final class CFMetaData
maxCompactionThreshold = cf_def.max_compaction_threshold;
mergeShardsChance = cf_def.merge_shards_chance;
keyAlias = cf_def.key_alias;
+ columnAliases = cf_def.column_aliases;
+ valueAlias = cf_def.value_alias;
if (cf_def.isSetBloom_filter_fp_chance())
bloomFilterFpChance = cf_def.bloom_filter_fp_chance;
caching = Caching.fromString(cf_def.caching);
@@ -1073,14 +1122,29 @@ public final class CFMetaData
if (!cfDef.isSetColumn_metadata())
return;
- AbstractType comparator = TypeParser.parse(cfDef.column_type.equals("Super")
- ? cfDef.subcomparator_type
- : cfDef.comparator_type);
+ AbstractType comparator = getColumnDefinitionComparator(cfDef);
for (ColumnDef columnDef : cfDef.column_metadata)
ColumnDefinition.addToSchema(mutation, cfDef.name, comparator, columnDef, timestamp);
}
+ public static AbstractType> getColumnDefinitionComparator(CfDef cfDef) throws ConfigurationException
+ {
+ AbstractType> cfComparator = TypeParser.parse(cfDef.column_type.equals("Super")
+ ? cfDef.subcomparator_type
+ : cfDef.comparator_type);
+
+ if (cfComparator instanceof CompositeType)
+ {
+ List> types = ((CompositeType)cfComparator).types;
+ return types.get(types.size() - 1);
+ }
+ else
+ {
+ return cfComparator;
+ }
+ }
+
/**
* Deserialize CF metadata from low-level representation
*
@@ -1130,6 +1194,17 @@ public final class CFMetaData
return cfDef;
}
+ private void updateCfDef()
+ {
+ cqlCfDef = new CFDefinition(this);
+ }
+
+ public CFDefinition getCfDef()
+ {
+ assert cqlCfDef != null;
+ return cqlCfDef;
+ }
+
@Override
public String toString()
{
@@ -1150,6 +1225,8 @@ public final class CFMetaData
.append("maxCompactionThreshold", maxCompactionThreshold)
.append("mergeShardsChance", mergeShardsChance)
.append("keyAlias", keyAlias)
+ .append("columnAliases", columnAliases)
+ .append("valueAlias", keyAlias)
.append("column_metadata", column_metadata)
.append("compactionStrategyClass", compactionStrategyClass)
.append("compactionStrategyOptions", compactionStrategyOptions)
diff --git a/src/java/org/apache/cassandra/cql/QueryProcessor.java b/src/java/org/apache/cassandra/cql/QueryProcessor.java
index 917fb92f13..c539979794 100644
--- a/src/java/org/apache/cassandra/cql/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql/QueryProcessor.java
@@ -53,6 +53,7 @@ import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
+import org.apache.cassandra.utils.SemanticVersion;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
@@ -65,7 +66,7 @@ import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;
public class QueryProcessor
{
- public static final String CQL_VERSION = "2.0.0";
+ public static final SemanticVersion CQL_VERSION = new SemanticVersion("2.0.0");
private static final Logger logger = LoggerFactory.getLogger(QueryProcessor.class);
diff --git a/src/java/org/apache/cassandra/cql3/Attributes.java b/src/java/org/apache/cassandra/cql3/Attributes.java
new file mode 100644
index 0000000000..41f78fb498
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Attributes.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import org.apache.cassandra.thrift.ConsistencyLevel;
+
+/**
+ * Utility class for the Parser to gather attributes for modification
+ * statements.
+ */
+public class Attributes
+{
+ public ConsistencyLevel cLevel;
+ public Long timestamp;
+ public int timeToLive;
+}
diff --git a/src/java/org/apache/cassandra/cql3/CFDefinition.java b/src/java/org/apache/cassandra/cql3/CFDefinition.java
new file mode 100644
index 0000000000..b3725db6d7
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/CFDefinition.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import com.google.common.collect.AbstractIterator;
+
+import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+/**
+ * Holds metadata on a CF preprocessed for use by CQL queries.
+ */
+public class CFDefinition implements Iterable
+{
+ public static final AbstractType> definitionType = UTF8Type.instance;
+
+ private static final String DEFAULT_KEY_ALIAS = "key";
+ private static final String DEFAULT_COLUMN_ALIAS = "column";
+ private static final String DEFAULT_VALUE_ALIAS = "value";
+
+ public final CFMetaData cfm;
+ public final Name key;
+ // LinkedHashMap because the order does matter (it is the order in the composite type)
+ public final LinkedHashMap columns = new LinkedHashMap();
+ public final Name value;
+ // Keep metadata lexicographically ordered so that wildcard expansion have a deterministic order
+ public final Map metadata = new TreeMap();
+
+ public final boolean isComposite;
+ public final boolean isCompact;
+
+ public CFDefinition(CFMetaData cfm)
+ {
+ this.cfm = cfm;
+ this.key = new Name(getKeyId(cfm), Name.Kind.KEY_ALIAS, cfm.getKeyValidator());
+ if (cfm.comparator instanceof CompositeType)
+ {
+ this.isComposite = true;
+ CompositeType composite = (CompositeType)cfm.comparator;
+ if (!cfm.getColumn_metadata().isEmpty())
+ {
+ // "sparse" composite
+ this.isCompact = false;
+ this.value = null;
+ assert cfm.getValueAlias() == null;
+ for (int i = 0; i < composite.types.size() - 1; i++)
+ {
+ ColumnIdentifier id = getColumnId(cfm, i);
+ this.columns.put(id, new Name(id, Name.Kind.COLUMN_ALIAS, i, composite.types.get(i)));
+ }
+
+ for (Map.Entry def : cfm.getColumn_metadata().entrySet())
+ {
+ ColumnIdentifier id = new ColumnIdentifier(def.getKey());
+ this.metadata.put(id, new Name(id, Name.Kind.COLUMN_METADATA, def.getValue().getValidator()));
+ }
+ }
+ else
+ {
+ // "dense" composite
+ this.isCompact = true;
+ for (int i = 0; i < composite.types.size(); i++)
+ {
+ ColumnIdentifier id = getColumnId(cfm, i);
+ this.columns.put(id, new Name(id, Name.Kind.COLUMN_ALIAS, i, composite.types.get(i)));
+ }
+ this.value = new Name(getValueId(cfm), Name.Kind.VALUE_ALIAS, cfm.getDefaultValidator());
+ }
+ }
+ else
+ {
+ this.isComposite = false;
+ if (!cfm.getColumn_metadata().isEmpty())
+ {
+ // static CF
+ this.isCompact = false;
+ this.value = null;
+ assert cfm.getValueAlias() == null;
+ assert cfm.getColumnAliases() == null || cfm.getColumnAliases().isEmpty();
+ for (Map.Entry def : cfm.getColumn_metadata().entrySet())
+ {
+ ColumnIdentifier id = new ColumnIdentifier(def.getKey());
+ this.metadata.put(id, new Name(id, Name.Kind.COLUMN_METADATA, def.getValue().getValidator()));
+ }
+ }
+ else
+ {
+ // dynamic CF
+ this.isCompact = true;
+ ColumnIdentifier id = getColumnId(cfm, 0);
+ Name name = new Name(id, Name.Kind.COLUMN_ALIAS, 0, cfm.comparator);
+ this.columns.put(id, name);
+ this.value = new Name(getValueId(cfm), Name.Kind.VALUE_ALIAS, cfm.getDefaultValidator());
+ }
+ }
+ assert value == null || metadata.isEmpty();
+ }
+
+ private static ColumnIdentifier getKeyId(CFMetaData cfm)
+ {
+ return cfm.getKeyAlias() == null
+ ? new ColumnIdentifier(DEFAULT_KEY_ALIAS, false)
+ : new ColumnIdentifier(cfm.getKeyAlias());
+ }
+
+ private static ColumnIdentifier getColumnId(CFMetaData cfm, int i)
+ {
+ List definedNames = cfm.getColumnAliases();
+ return definedNames == null || i >= definedNames.size()
+ ? new ColumnIdentifier(DEFAULT_COLUMN_ALIAS + (i + 1), false)
+ : new ColumnIdentifier(cfm.getColumnAliases().get(i));
+ }
+
+ private static ColumnIdentifier getValueId(CFMetaData cfm)
+ {
+ return cfm.getValueAlias() == null
+ ? new ColumnIdentifier(DEFAULT_VALUE_ALIAS, false)
+ : new ColumnIdentifier(cfm.getValueAlias());
+ }
+
+ public Name get(ColumnIdentifier name)
+ {
+ if (name.equals(key.name))
+ return key;
+ if (value != null && name.equals(value.name))
+ return value;
+ CFDefinition.Name def = columns.get(name);
+ if (def != null)
+ return def;
+ return metadata.get(name);
+ }
+
+ public Iterator iterator()
+ {
+ return new AbstractIterator()
+ {
+ private boolean keyDone;
+ private final Iterator columnIter = columns.values().iterator();
+ private boolean valueDone;
+ private final Iterator metadataIter = metadata.values().iterator();
+
+ protected Name computeNext()
+ {
+ if (!keyDone)
+ {
+ keyDone = true;
+ return key;
+ }
+
+ if (columnIter.hasNext())
+ return columnIter.next();
+
+ if (value != null && !valueDone)
+ {
+ valueDone = true;
+ return value;
+ }
+
+ if (metadataIter.hasNext())
+ return metadataIter.next();
+
+ return endOfData();
+ }
+ };
+ }
+
+ public ColumnNameBuilder getColumnNameBuilder()
+ {
+ return isComposite
+ ? new CompositeType.Builder((CompositeType)cfm.comparator)
+ : new NonCompositeBuilder(cfm.comparator);
+ }
+
+ public static class Name
+ {
+ public static enum Kind
+ {
+ KEY_ALIAS, COLUMN_ALIAS, VALUE_ALIAS, COLUMN_METADATA
+ }
+
+ private Name(ColumnIdentifier name, Kind kind, AbstractType> type)
+ {
+ this(name, kind, -1, type);
+ }
+
+ private Name(ColumnIdentifier name, Kind kind, int position, AbstractType> type)
+ {
+ this.kind = kind;
+ this.name = name;
+ this.compositePosition = position;
+ this.type = type;
+ }
+
+ public final Kind kind;
+ public final ColumnIdentifier name;
+ public final int compositePosition; // only make sense for COLUMN_ALIAS if CFDefinition.isComposite()
+ public final AbstractType> type;
+
+ @Override
+ public String toString()
+ {
+ // It is not fully conventional, but it is convenient for error messages to the user
+ return name.toString();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(key.name);
+ for (Name name : columns.values())
+ sb.append(", ").append(name.name);
+ sb.append(" => ");
+ if (value != null)
+ sb.append(value.name);
+ if (!metadata.isEmpty())
+ {
+ sb.append("{");
+ for (Name name : metadata.values())
+ sb.append(" ").append(name.name);
+ sb.append(" }");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private static class NonCompositeBuilder implements ColumnNameBuilder
+ {
+ private final AbstractType> type;
+ private ByteBuffer columnName;
+
+ private NonCompositeBuilder(AbstractType> type)
+ {
+ this.type = type;
+ }
+
+ public NonCompositeBuilder add(ByteBuffer bb)
+ {
+ if (columnName != null)
+ throw new IllegalStateException("Column name is already constructed");
+
+ columnName = bb;
+ return this;
+ }
+
+ public NonCompositeBuilder add(Term t, Relation.Type op, List variables) throws InvalidRequestException
+ {
+ if (columnName != null)
+ throw new IllegalStateException("Column name is already constructed");
+
+ // We don't support the relation type yet, i.e., there is no distinction between x > 3 and x >= 3.
+ columnName = t.getByteBuffer(type, variables);
+ return this;
+ }
+
+ public int componentCount()
+ {
+ return columnName == null ? 0 : 1;
+ }
+
+ public ByteBuffer build()
+ {
+ return columnName == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : columnName;
+ }
+
+ public ByteBuffer buildAsEndOfRange()
+ {
+ throw new IllegalStateException();
+ }
+
+ public NonCompositeBuilder copy()
+ {
+ NonCompositeBuilder newBuilder = new NonCompositeBuilder(type);
+ newBuilder.columnName = columnName;
+ return newBuilder;
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/CFName.java b/src/java/org/apache/cassandra/cql3/CFName.java
new file mode 100644
index 0000000000..cd6e6d3452
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/CFName.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.util.Locale;
+
+public class CFName
+{
+ private String ksName;
+ private String cfName;
+
+ public void setKeyspace(String ks, boolean keepCase)
+ {
+ ksName = keepCase ? ks : ks.toLowerCase(Locale.US);
+ }
+
+ public void setColumnFamily(String cf, boolean keepCase)
+ {
+ cfName = keepCase ? cf : cf.toLowerCase(Locale.US);
+ }
+
+ public boolean hasKeyspace()
+ {
+ return ksName != null;
+ }
+
+ public String getKeyspace()
+ {
+ return ksName;
+ }
+
+ public String getColumnFamily()
+ {
+ return cfName;
+ }
+
+ @Override
+ public String toString()
+ {
+ return (hasKeyspace() ? (ksName + ".") : "") + cfName;
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/CFPropDefs.java b/src/java/org/apache/cassandra/cql3/CFPropDefs.java
new file mode 100644
index 0000000000..e878e63451
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/CFPropDefs.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import com.google.common.collect.Sets;
+import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.TypeParser;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class CFPropDefs
+{
+ private static Logger logger = LoggerFactory.getLogger(CFPropDefs.class);
+
+ public static final String KW_COMMENT = "comment";
+ public static final String KW_READREPAIRCHANCE = "read_repair_chance";
+ public static final String KW_GCGRACESECONDS = "gc_grace_seconds";
+ public static final String KW_MINCOMPACTIONTHRESHOLD = "min_compaction_threshold";
+ public static final String KW_MAXCOMPACTIONTHRESHOLD = "max_compaction_threshold";
+ public static final String KW_REPLICATEONWRITE = "replicate_on_write";
+
+ public static final String KW_COMPACTION_STRATEGY_CLASS = "compaction_strategy_class";
+
+ // Maps CQL short names to the respective Cassandra comparator/validator class names
+ public static final Map comparators = new HashMap();
+ public static final Set keywords = new HashSet();
+ public static final Set obsoleteKeywords = new HashSet();
+ public static final Set allowedKeywords = new HashSet();
+
+ public static final String COMPACTION_OPTIONS_PREFIX = "compaction_strategy_options";
+ public static final String COMPRESSION_PARAMETERS_PREFIX = "compression_parameters";
+
+ static
+ {
+ comparators.put("ascii", "AsciiType");
+ comparators.put("bigint", "LongType");
+ comparators.put("blob", "BytesType");
+ comparators.put("boolean", "BooleanType");
+ comparators.put("counter", "CounterColumnType");
+ comparators.put("decimal", "DecimalType");
+ comparators.put("double", "DoubleType");
+ comparators.put("float", "FloatType");
+ comparators.put("int", "Int32Type");
+ comparators.put("text", "UTF8Type");
+ comparators.put("timestamp", "DateType");
+ comparators.put("uuid", "UUIDType");
+ comparators.put("varchar", "UTF8Type");
+ comparators.put("varint", "IntegerType");
+
+ keywords.add(KW_COMMENT);
+ keywords.add(KW_READREPAIRCHANCE);
+ keywords.add(KW_GCGRACESECONDS);
+ keywords.add(KW_MINCOMPACTIONTHRESHOLD);
+ keywords.add(KW_MAXCOMPACTIONTHRESHOLD);
+ keywords.add(KW_REPLICATEONWRITE);
+ keywords.add(KW_COMPACTION_STRATEGY_CLASS);
+
+ obsoleteKeywords.add("row_cache_size");
+ obsoleteKeywords.add("key_cache_size");
+ obsoleteKeywords.add("row_cache_save_period_in_seconds");
+ obsoleteKeywords.add("key_cache_save_period_in_seconds");
+ obsoleteKeywords.add("memtable_throughput_in_mb");
+ obsoleteKeywords.add("memtable_operations_in_millions");
+ obsoleteKeywords.add("memtable_flush_after_mins");
+ obsoleteKeywords.add("row_cache_provider");
+ obsoleteKeywords.add("comparator");
+ obsoleteKeywords.add("default_validation");
+
+ allowedKeywords.addAll(keywords);
+ allowedKeywords.addAll(obsoleteKeywords);
+ }
+
+ public final Map properties = new HashMap();
+ public final Map compactionStrategyOptions = new HashMap();
+ public final Map compressionParameters = new HashMap();
+
+ public static AbstractType> parseType(String type) throws InvalidRequestException
+ {
+ try
+ {
+ String className = comparators.get(type);
+ if (className == null)
+ className = type;
+ return TypeParser.parse(className);
+ }
+ catch (ConfigurationException e)
+ {
+ InvalidRequestException ex = new InvalidRequestException(e.toString());
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ /* If not comparator/validator is not specified, default to text (BytesType is the wrong default for CQL
+ * since it uses hex terms). If the value specified is not found in the comparators map, assume the user
+ * knows what they are doing (a custom comparator/validator for example), and pass it on as-is.
+ */
+
+ public void validate() throws InvalidRequestException
+ {
+ // Catch the case where someone passed a kwarg that is not recognized.
+ for (String bogus : Sets.difference(properties.keySet(), allowedKeywords))
+ throw new InvalidRequestException(bogus + " is not a valid keyword argument for CREATE COLUMNFAMILY");
+ for (String obsolete : Sets.intersection(properties.keySet(), obsoleteKeywords))
+ logger.warn("Ignoring obsolete property {}", obsolete);
+
+ // Validate min/max compaction thresholds
+ Integer minCompaction = getInt(KW_MINCOMPACTIONTHRESHOLD, null);
+ Integer maxCompaction = getInt(KW_MAXCOMPACTIONTHRESHOLD, null);
+
+ if ((minCompaction != null) && (maxCompaction != null)) // Both min and max are set
+ {
+ if ((minCompaction > maxCompaction) && (maxCompaction != 0))
+ throw new InvalidRequestException(String.format("%s cannot be larger than %s",
+ KW_MINCOMPACTIONTHRESHOLD,
+ KW_MAXCOMPACTIONTHRESHOLD));
+ }
+ else if (minCompaction != null) // Only the min threshold is set
+ {
+ if (minCompaction > CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD)
+ throw new InvalidRequestException(String.format("%s cannot be larger than %s, (default %s)",
+ KW_MINCOMPACTIONTHRESHOLD,
+ KW_MAXCOMPACTIONTHRESHOLD,
+ CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD));
+ }
+ else if (maxCompaction != null) // Only the max threshold is set
+ {
+ if ((maxCompaction < CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD) && (maxCompaction != 0))
+ throw new InvalidRequestException(String.format("%s cannot be smaller than %s, (default %s)",
+ KW_MAXCOMPACTIONTHRESHOLD,
+ KW_MINCOMPACTIONTHRESHOLD,
+ CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD));
+ }
+ }
+
+ /** Map a keyword to the corresponding value */
+ public void addProperty(String name, String value)
+ {
+ String[] composite = name.split(":");
+ if (composite.length > 1)
+ {
+ if (composite[0].equals(COMPACTION_OPTIONS_PREFIX))
+ {
+ compactionStrategyOptions.put(composite[1], value);
+ return;
+ }
+ else if (composite[0].equals(COMPRESSION_PARAMETERS_PREFIX))
+ {
+ compressionParameters.put(composite[1], value);
+ return;
+ }
+ }
+ properties.put(name, value);
+ }
+
+ public void addAll(Map propertyMap)
+ {
+ for (Map.Entry entry : propertyMap.entrySet())
+ addProperty(entry.getKey(), entry.getValue());
+ }
+
+ public Boolean hasProperty(String name)
+ {
+ return properties.containsKey(name);
+ }
+
+ public String get(String name)
+ {
+ return properties.get(name);
+ }
+
+ public String getString(String key, String defaultValue)
+ {
+ String value = properties.get(key);
+ return value != null ? value : defaultValue;
+ }
+
+ // Return a property value, typed as a Boolean
+ public Boolean getBoolean(String key, Boolean defaultValue) throws InvalidRequestException
+ {
+ String value = properties.get(key);
+ return (value == null) ? defaultValue : value.toLowerCase().matches("(1|true|yes)");
+ }
+
+ // Return a property value, typed as a Double
+ public Double getDouble(String key, Double defaultValue) throws InvalidRequestException
+ {
+ Double result;
+ String value = properties.get(key);
+
+ if (value == null)
+ result = defaultValue;
+ else
+ {
+ try
+ {
+ result = Double.parseDouble(value);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new InvalidRequestException(String.format("%s not valid for \"%s\"", value, key));
+ }
+ }
+ return result;
+ }
+
+ // Return a property value, typed as an Integer
+ public Integer getInt(String key, Integer defaultValue) throws InvalidRequestException
+ {
+ Integer result;
+ String value = properties.get(key);
+
+ if (value == null)
+ result = defaultValue;
+ else
+ {
+ try
+ {
+ result = Integer.parseInt(value);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new InvalidRequestException(String.format("%s not valid for \"%s\"", value, key));
+ }
+ }
+ return result;
+ }
+
+ public String toString()
+ {
+ return String.format("CFPropDefs(%s, compaction: %s, compression: %s)",
+ properties.toString(),
+ compactionStrategyOptions.toString(),
+ compressionParameters.toString());
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/CQLStatement.java b/src/java/org/apache/cassandra/cql3/CQLStatement.java
new file mode 100644
index 0000000000..90277c0db3
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/CQLStatement.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.CqlResult;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.SchemaDisagreementException;
+import org.apache.cassandra.thrift.TimedOutException;
+import org.apache.cassandra.thrift.UnavailableException;
+
+public abstract class CQLStatement
+{
+ private int boundTerms;
+
+ public int getBoundsTerms()
+ {
+ return boundTerms;
+ }
+
+ // Used by the parser and preparable statement
+ public void setBoundTerms(int boundTerms)
+ {
+ this.boundTerms = boundTerms;
+ }
+
+ /**
+ * Perform any access verification necessary for the statement.
+ *
+ * @param state the current client state
+ */
+ public abstract void checkAccess(ClientState state) throws InvalidRequestException;
+
+ /**
+ * Perform additional validation required by the statment.
+ * To be overriden by subclasses if needed.
+ *
+ * @param state the current client state
+ */
+ public void validate(ClientState state) throws InvalidRequestException, SchemaDisagreementException
+ {}
+
+ /**
+ * Execute the statement and return the resulting result or null if there is no result.
+ *
+ * @param state the current client state
+ * @param variables the values for bounded variables. The implementation
+ * can assume that each bound term have a corresponding value.
+ */
+ public abstract CqlResult execute(ClientState state, List variables) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException;
+}
diff --git a/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
new file mode 100644
index 0000000000..60f3ecb55b
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/ColumnIdentifier.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.util.Locale;
+import java.nio.charset.CharacterCodingException;
+import java.nio.ByteBuffer;
+
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+/**
+ * Represents an identifer for a CQL column definition.
+ */
+public class ColumnIdentifier implements Comparable
+{
+ public final ByteBuffer key;
+ private final String text;
+
+ public ColumnIdentifier(String rawText, boolean keepCase)
+ {
+ this.text = keepCase ? rawText : rawText.toLowerCase(Locale.US);
+ this.key = ByteBufferUtil.bytes(this.text);
+ }
+
+ public ColumnIdentifier(ByteBuffer key)
+ {
+ try
+ {
+ this.key = key;
+ this.text = ByteBufferUtil.string(key);
+ }
+ catch (CharacterCodingException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public final int hashCode()
+ {
+ return key.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object o)
+ {
+ if(!(o instanceof ColumnIdentifier))
+ return false;
+ ColumnIdentifier that = (ColumnIdentifier)o;
+ return key.equals(that.key);
+ }
+
+ @Override
+ public String toString()
+ {
+ return text;
+ }
+
+ public int compareTo(ColumnIdentifier other)
+ {
+ return key.compareTo(other.key);
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java b/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java
new file mode 100644
index 0000000000..d9ca9023a0
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.cassandra.thrift.InvalidRequestException;
+
+/**
+ * Build a potentially composite column name.
+ */
+public interface ColumnNameBuilder
+{
+ /**
+ * Add a new ByteBuffer as the next component for this name.
+ * @param bb the ByteBuffer to add
+ * @throws IllegalStateException if the builder if full, i.e. if enough component has been added.
+ * @return this builder
+ */
+ public ColumnNameBuilder add(ByteBuffer bb);
+
+ /**
+ * Add a new Term as the next component for this name.
+ * @param t the Term to add
+ * @param op the relationship this component should respect.
+ * @param variables the variables corresponding to prepared markers
+ * @throws IllegalStateException if the builder if full, i.e. if enough component has been added.
+ * @return this builder
+ */
+ public ColumnNameBuilder add(Term t, Relation.Type op, List variables) throws InvalidRequestException;
+
+ /**
+ * Returns the number of component already added to this builder.
+ * @return the number of component in this Builder
+ */
+ public int componentCount();
+
+ /**
+ * Build the column name.
+ * @return the built column name
+ */
+ public ByteBuffer build();
+
+ /**
+ * Build the column name so that the result sorts at the end of the range
+ * represented by this (uncomplete) column name.
+ * @throws IllegalStateException if the builder is empty or full.
+ */
+ public ByteBuffer buildAsEndOfRange();
+
+ /**
+ * Clone this builder.
+ * @return the cloned builder.
+ */
+ public ColumnNameBuilder copy();
+}
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
new file mode 100644
index 0000000000..13b2956d28
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -0,0 +1,633 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+grammar Cql;
+
+options {
+ language = Java;
+}
+
+@header {
+ package org.apache.cassandra.cql3;
+
+ import java.util.Map;
+ import java.util.HashMap;
+ import java.util.Collections;
+ import java.util.List;
+ import java.util.ArrayList;
+
+ import org.apache.cassandra.cql3.statements.*;
+ import org.apache.cassandra.utils.Pair;
+ import org.apache.cassandra.thrift.ConsistencyLevel;
+ import org.apache.cassandra.thrift.InvalidRequestException;
+}
+
+@members {
+ private List recognitionErrors = new ArrayList();
+ private int currentBindMarkerIdx = -1;
+
+ public void displayRecognitionError(String[] tokenNames, RecognitionException e)
+ {
+ String hdr = getErrorHeader(e);
+ String msg = getErrorMessage(e, tokenNames);
+ recognitionErrors.add(hdr + " " + msg);
+ }
+
+ public List getRecognitionErrors()
+ {
+ return recognitionErrors;
+ }
+
+ public void throwLastRecognitionError() throws InvalidRequestException
+ {
+ if (recognitionErrors.size() > 0)
+ throw new InvalidRequestException(recognitionErrors.get((recognitionErrors.size()-1)));
+ }
+
+ // used by UPDATE of the counter columns to validate if '-' was supplied by user
+ public void validateMinusSupplied(Object op, final Term value, IntStream stream) throws MissingTokenException
+ {
+ if (op == null && (value.isBindMarker() || Long.parseLong(value.getText()) > 0))
+ throw new MissingTokenException(102, stream, value);
+ }
+
+}
+
+@lexer::header {
+ package org.apache.cassandra.cql3;
+
+ import org.apache.cassandra.thrift.InvalidRequestException;
+}
+
+@lexer::members {
+ List tokens = new ArrayList();
+
+ public void emit(Token token)
+ {
+ state.token = token;
+ tokens.add(token);
+ }
+
+ public Token nextToken()
+ {
+ super.nextToken();
+ if (tokens.size() == 0)
+ return Token.EOF_TOKEN;
+ return tokens.remove(0);
+ }
+
+ private List recognitionErrors = new ArrayList();
+
+ public void displayRecognitionError(String[] tokenNames, RecognitionException e)
+ {
+ String hdr = getErrorHeader(e);
+ String msg = getErrorMessage(e, tokenNames);
+ recognitionErrors.add(hdr + " " + msg);
+ }
+
+ public List getRecognitionErrors()
+ {
+ return recognitionErrors;
+ }
+
+ public void throwLastRecognitionError() throws InvalidRequestException
+ {
+ if (recognitionErrors.size() > 0)
+ throw new InvalidRequestException(recognitionErrors.get((recognitionErrors.size()-1)));
+ }
+}
+
+/** STATEMENTS **/
+
+query returns [CQLStatement stmnt]
+ : st=cqlStatement (';')* EOF { $stmnt = st; }
+ ;
+
+cqlStatement returns [CQLStatement stmt]
+ @after{ if (stmt != null) stmt.setBoundTerms(currentBindMarkerIdx + 1); }
+ : st1= selectStatement { $stmt = st1; }
+ | st2= insertStatement { $stmt = st2; }
+ | st3= updateStatement { $stmt = st3; }
+ | st4= batchStatement { $stmt = st4; }
+ | st5= deleteStatement { $stmt = st5; }
+ | st6= useStatement { $stmt = st6; }
+ | st7= truncateStatement { $stmt = st7; }
+ | st8= createKeyspaceStatement { $stmt = st8; }
+ | st9= createColumnFamilyStatement { $stmt = st9; }
+ | st10=createIndexStatement { $stmt = st10; }
+ | st11=dropKeyspaceStatement { $stmt = st11; }
+ | st12=dropColumnFamilyStatement { $stmt = st12; }
+ | st13=dropIndexStatement { $stmt = st13; }
+ | st14=alterTableStatement { $stmt = st14; }
+ ;
+
+/*
+ * USE ;
+ */
+useStatement returns [UseStatement stmt]
+ : K_USE ks=keyspaceName { $stmt = new UseStatement(ks); }
+ ;
+
+/**
+ * SELECT
+ * FROM
+ * USING CONSISTENCY
+ * WHERE KEY = "key1" AND COL > 1 AND COL < 100
+ * LIMIT ;
+ */
+selectStatement returns [SelectStatement.RawStatement expr]
+ @init {
+ boolean isCount = false;
+ ConsistencyLevel cLevel = ConsistencyLevel.ONE;
+ int limit = 10000;
+ boolean reversed = false;
+ }
+ : K_SELECT ( sclause=selectClause | (K_COUNT '(' sclause=selectClause ')' { isCount = true; }) )
+ K_FROM cf=columnFamilyName
+ ( K_USING K_CONSISTENCY K_LEVEL { cLevel = ConsistencyLevel.valueOf($K_LEVEL.text.toUpperCase()); } )?
+ ( K_WHERE wclause=whereClause )?
+ ( K_ORDER (K_ASC | K_DESC { reversed = true; }) )?
+ ( K_LIMIT rows=INTEGER { limit = Integer.parseInt($rows.text); } )?
+ {
+ SelectStatement.Parameters params = new SelectStatement.Parameters(cLevel,
+ limit,
+ reversed,
+ isCount);
+ $expr = new SelectStatement.RawStatement(cf, params, sclause, wclause);
+ }
+ ;
+
+selectClause returns [List expr]
+ : ids=cidentList { $expr = ids; }
+ | '\*' { $expr = Collections.emptyList();}
+ ;
+
+whereClause returns [List clause]
+ @init{ $clause = new ArrayList(); }
+ : first=relation { $clause.add(first); } (K_AND next=relation { $clause.add(next); })*
+ ;
+
+/**
+ * INSERT INTO (, , , ...)
+ * VALUES (, , , ...)
+ * USING CONSISTENCY AND TIMESTAMP ;
+ *
+ * Consistency level is set to ONE by default
+ */
+insertStatement returns [UpdateStatement expr]
+ @init {
+ Attributes attrs = new Attributes();
+ List columnNames = new ArrayList();
+ List columnValues = new ArrayList();
+ }
+ : K_INSERT K_INTO cf=columnFamilyName
+ '(' c1=cident { columnNames.add(c1); } ( ',' cn=cident { columnNames.add(cn); } )+ ')'
+ K_VALUES
+ '(' v1=term { columnValues.add(v1); } ( ',' vn=term { columnValues.add(vn); } )+ ')'
+ ( usingClause[attrs] )?
+ {
+ $expr = new UpdateStatement(cf, columnNames, columnValues, attrs);
+ }
+ ;
+
+usingClause[Attributes attrs]
+ : K_USING usingClauseObjective[attrs] ( K_AND? usingClauseObjective[attrs] )*
+ ;
+
+usingClauseDelete[Attributes attrs]
+ : K_USING usingClauseDeleteObjective[attrs] ( K_AND? usingClauseDeleteObjective[attrs] )*
+ ;
+
+usingClauseDeleteObjective[Attributes attrs]
+ : K_CONSISTENCY K_LEVEL { attrs.cLevel = ConsistencyLevel.valueOf($K_LEVEL.text.toUpperCase()); }
+ | K_TIMESTAMP ts=INTEGER { attrs.timestamp = Long.valueOf($ts.text); }
+ ;
+
+usingClauseObjective[Attributes attrs]
+ : usingClauseDeleteObjective[attrs]
+ | K_TTL t=INTEGER { attrs.timeToLive = Integer.valueOf($t.text); }
+ ;
+
+/**
+ * UPDATE
+ * USING CONSISTENCY AND TIMESTAMP
+ * SET name1 = value1, name2 = value2
+ * WHERE key = value;
+ */
+updateStatement returns [UpdateStatement expr]
+ @init {
+ Attributes attrs = new Attributes();
+ Map columns = new HashMap();
+ }
+ : K_UPDATE cf=columnFamilyName
+ ( usingClause[attrs] )?
+ K_SET termPairWithOperation[columns] (',' termPairWithOperation[columns])*
+ K_WHERE wclause=whereClause
+ {
+ return new UpdateStatement(cf, columns, wclause, attrs);
+ }
+ ;
+
+/**
+ * DELETE name1, name2
+ * FROM
+ * USING CONSISTENCY AND TIMESTAMP
+ * WHERE KEY = keyname;
+ */
+deleteStatement returns [DeleteStatement expr]
+ @init {
+ Attributes attrs = new Attributes();
+ List columnsList = Collections.emptyList();
+ }
+ : K_DELETE ( ids=cidentList { columnsList = ids; } )?
+ K_FROM cf=columnFamilyName
+ ( usingClauseDelete[attrs] )?
+ K_WHERE wclause=whereClause
+ {
+ return new DeleteStatement(cf, columnsList, wclause, attrs);
+ }
+ ;
+
+
+/**
+ * BEGIN BATCH [USING CONSISTENCY ]
+ * UPDATE SET name1 = value1 WHERE KEY = keyname1;
+ * UPDATE SET name2 = value2 WHERE KEY = keyname2;
+ * UPDATE SET name3 = value3 WHERE KEY = keyname3;
+ * ...
+ * APPLY BATCH
+ *
+ * OR
+ *
+ * BEGIN BATCH [USING CONSISTENCY ]
+ * INSERT INTO (KEY, ) VALUES ('', '');
+ * INSERT INTO (KEY, ) VALUES ('', '');
+ * ...
+ * APPLY BATCH
+ *
+ * OR
+ *
+ * BEGIN BATCH [USING CONSISTENCY ]
+ * DELETE name1, name2 FROM WHERE key =
+ * DELETE name3, name4 FROM WHERE key =
+ * ...
+ * APPLY BATCH
+ */
+batchStatement returns [BatchStatement expr]
+ @init {
+ Attributes attrs = new Attributes();
+ List statements = new ArrayList();
+ }
+ : K_BEGIN K_BATCH ( usingClause[attrs] )?
+ s1=batchStatementObjective ';'? { statements.add(s1); } ( sN=batchStatementObjective ';'? { statements.add(sN); } )*
+ K_APPLY K_BATCH
+ {
+ return new BatchStatement(statements, attrs);
+ }
+ ;
+
+batchStatementObjective returns [ModificationStatement statement]
+ : i=insertStatement { $statement = i; }
+ | u=updateStatement { $statement = u; }
+ | d=deleteStatement { $statement = d; }
+ ;
+
+/**
+ * CREATE KEYSPACE WITH attr1 = value1 AND attr2 = value2;
+ */
+createKeyspaceStatement returns [CreateKeyspaceStatement expr]
+ : K_CREATE K_KEYSPACE ks=keyspaceName
+ K_WITH props=properties { $expr = new CreateKeyspaceStatement(ks, props); }
+ ;
+
+/**
+ * CREATE COLUMNFAMILY (
+ * ,
+ * ,
+ *
+ * ) WITH = AND ...;
+ */
+createColumnFamilyStatement returns [CreateColumnFamilyStatement.RawStatement expr]
+ : K_CREATE K_COLUMNFAMILY cf=columnFamilyName { $expr = new CreateColumnFamilyStatement.RawStatement(cf); }
+ cfamDefinition[expr]
+ ;
+
+cfamDefinition[CreateColumnFamilyStatement.RawStatement expr]
+ : '(' cfamColumns[expr] ( ',' cfamColumns[expr]? )* ')'
+ ( K_WITH cfamProperty[expr] ( K_AND cfamProperty[expr] )*)?
+ ;
+
+cfamColumns[CreateColumnFamilyStatement.RawStatement expr]
+ : k=cident v=comparatorType { $expr.addDefinition(k, v); } (K_PRIMARY K_KEY { $expr.setKeyAlias(k); })?
+ | K_PRIMARY K_KEY '(' k=cident { $expr.setKeyAlias(k); } (',' c=cident { $expr.addColumnAlias(c); } )* ')'
+ ;
+
+cfamProperty[CreateColumnFamilyStatement.RawStatement expr]
+ : k=property '=' v=propertyValue { $expr.addProperty(k, v); }
+ | K_COMPACT K_STORAGE { $expr.setCompactStorage(); }
+ ;
+
+/**
+ * CREATE INDEX [indexName] ON columnFamily (columnName);
+ */
+createIndexStatement returns [CreateIndexStatement expr]
+ : K_CREATE K_INDEX (idxName=IDENT)? K_ON cf=columnFamilyName '(' id=cident ')'
+ { $expr = new CreateIndexStatement(cf, $idxName.text, id); }
+ ;
+
+/**
+ * ALTER COLUMN FAMILY ALTER TYPE ;
+ * ALTER COLUMN FAMILY ADD ;
+ * ALTER COLUMN FAMILY DROP ;
+ * ALTER COLUMN FAMILY WITH = ;
+ */
+alterTableStatement returns [AlterTableStatement expr]
+ @init {
+ AlterTableStatement.Type type = null;
+ String validator = null;
+ ColumnIdentifier columnName = null;
+ Map propertyMap = null;
+ }
+ : K_ALTER K_COLUMNFAMILY cf=columnFamilyName
+ ( K_ALTER id=cident K_TYPE v=comparatorType { type = AlterTableStatement.Type.ALTER; }
+ | K_ADD id=cident v=comparatorType { type = AlterTableStatement.Type.ADD; }
+ | K_DROP id=cident { type = AlterTableStatement.Type.DROP; }
+ | K_WITH props=properties { type = AlterTableStatement.Type.OPTS; }
+ )
+ {
+ $expr = new AlterTableStatement(cf, type, id, v, props);
+ }
+ ;
+
+/**
+ * DROP KEYSPACE ;
+ */
+dropKeyspaceStatement returns [DropKeyspaceStatement ksp]
+ : K_DROP K_KEYSPACE ks=keyspaceName { $ksp = new DropKeyspaceStatement(ks); }
+ ;
+
+/**
+ * DROP COLUMNFAMILY ;
+ */
+dropColumnFamilyStatement returns [DropColumnFamilyStatement stmt]
+ : K_DROP K_COLUMNFAMILY cf=columnFamilyName { $stmt = new DropColumnFamilyStatement(cf); }
+ ;
+
+/**
+ * DROP INDEX
+ */
+dropIndexStatement returns [DropIndexStatement expr]
+ :
+ K_DROP K_INDEX index=IDENT
+ { $expr = new DropIndexStatement($index.text); }
+ ;
+
+/**
+ * TRUNCATE ;
+ */
+truncateStatement returns [TruncateStatement stmt]
+ : K_TRUNCATE cf=columnFamilyName { $stmt = new TruncateStatement(cf); }
+ ;
+
+
+/** DEFINITIONS **/
+
+// Column Identifiers
+cident returns [ColumnIdentifier id]
+ : t=( IDENT | UUID | INTEGER ) { $id = new ColumnIdentifier($t.text, false); }
+ | t=QUOTED_NAME { $id = new ColumnIdentifier($t.text, true); }
+ ;
+
+// Keyspace & Column family names
+keyspaceName returns [String id]
+ @init { CFName name = new CFName(); }
+ : cfOrKsName[name, true] { $id = name.getKeyspace(); }
+ ;
+
+columnFamilyName returns [CFName name]
+ @init { $name = new CFName(); }
+ : (cfOrKsName[name, true] '.')? cfOrKsName[name, false]
+ ;
+
+cfOrKsName[CFName name, boolean isKs]
+ : t=IDENT { if (isKs) $name.setKeyspace($t.text, false); else $name.setColumnFamily($t.text, false); }
+ | t=QUOTED_NAME { if (isKs) $name.setKeyspace($t.text, true); else $name.setColumnFamily($t.text, true); }
+ ;
+
+cidentList returns [List items]
+ @init{ $items = new ArrayList(); }
+ : t1=cident { $items.add(t1); } (',' tN=cident { $items.add(tN); })*
+ ;
+
+// Values (includes prepared statement markers)
+term returns [Term term]
+ : t=(STRING_LITERAL | UUID | IDENT | INTEGER | FLOAT ) { $term = new Term($t.text, $t.type); }
+ | t=QMARK { $term = new Term($t.text, $t.type, ++currentBindMarkerIdx); }
+ ;
+
+intTerm returns [Term integer]
+ : t=INTEGER { $integer = new Term($t.text, $t.type); }
+ | t=QMARK { $integer = new Term($t.text, $t.type, ++currentBindMarkerIdx); }
+ ;
+
+termPairWithOperation[Map columns]
+ : key=cident '='
+ ( value=term { columns.put(key, new Operation(value)); }
+ | c=cident ( '+' v=intTerm { columns.put(key, new Operation(c, Operation.Type.PLUS, v)); }
+ | op='-'? v=intTerm
+ {
+ validateMinusSupplied(op, v, input);
+ if (op == null)
+ v = new Term(-(Long.valueOf(v.getText())), v.getType());
+ columns.put(key, new Operation(c, Operation.Type.MINUS, v));
+ }
+ )
+ )
+ ;
+
+property returns [String str]
+ : p=(COMPIDENT | IDENT) { $str = $p.text; }
+ ;
+
+propertyValue returns [String str]
+ : v=(STRING_LITERAL | IDENT | INTEGER | FLOAT) { $str = $v.text; }
+ ;
+
+properties returns [Map props]
+ @init{ $props = new HashMap(); }
+ : k1=property '=' v1=propertyValue { $props.put(k1, v1); } (K_AND kn=property '=' vn=propertyValue { $props.put(kn, vn); } )*
+ ;
+
+relation returns [Relation rel]
+ : name=cident type=('=' | '<' | '<=' | '>=' | '>') t=term { $rel = new Relation($name.id, $type.text, $t.term); }
+ | name=cident K_IN { $rel = Relation.createInRelation($name.id); }
+ '(' f1=term { $rel.addInValue(f1); } (',' fN=term { $rel.addInValue(fN); } )* ')'
+ ;
+
+comparatorType returns [String str]
+ : c=(IDENT | STRING_LITERAL) { $str = $c.text; }
+ ;
+
+
+// Case-insensitive keywords
+K_SELECT: S E L E C T;
+K_FROM: F R O M;
+K_WHERE: W H E R E;
+K_AND: A N D;
+K_KEY: K E Y;
+K_INSERT: I N S E R T;
+K_UPDATE: U P D A T E;
+K_WITH: W I T H;
+K_LIMIT: L I M I T;
+K_USING: U S I N G;
+K_CONSISTENCY: C O N S I S T E N C Y;
+K_LEVEL: ( O N E
+ | Q U O R U M
+ | A L L
+ | A N Y
+ | L O C A L '_' Q U O R U M
+ | E A C H '_' Q U O R U M
+ )
+ ;
+K_USE: U S E;
+K_COUNT: C O U N T;
+K_SET: S E T;
+K_BEGIN: B E G I N;
+K_APPLY: A P P L Y;
+K_BATCH: B A T C H;
+K_TRUNCATE: T R U N C A T E;
+K_DELETE: D E L E T E;
+K_IN: I N;
+K_CREATE: C R E A T E;
+K_KEYSPACE: ( K E Y S P A C E
+ | S C H E M A );
+K_COLUMNFAMILY:( C O L U M N F A M I L Y
+ | T A B L E );
+K_INDEX: I N D E X;
+K_ON: O N;
+K_DROP: D R O P;
+K_PRIMARY: P R I M A R Y;
+K_INTO: I N T O;
+K_VALUES: V A L U E S;
+K_TIMESTAMP: T I M E S T A M P;
+K_TTL: T T L;
+K_ALTER: A L T E R;
+K_ADD: A D D;
+K_TYPE: T Y P E;
+K_COMPACT: C O M P A C T;
+K_STORAGE: S T O R A G E;
+K_ORDER: O R D E R;
+K_ASC: A S C;
+K_DESC: D E S C;
+
+// Case-insensitive alpha characters
+fragment A: ('a'|'A');
+fragment B: ('b'|'B');
+fragment C: ('c'|'C');
+fragment D: ('d'|'D');
+fragment E: ('e'|'E');
+fragment F: ('f'|'F');
+fragment G: ('g'|'G');
+fragment H: ('h'|'H');
+fragment I: ('i'|'I');
+fragment J: ('j'|'J');
+fragment K: ('k'|'K');
+fragment L: ('l'|'L');
+fragment M: ('m'|'M');
+fragment N: ('n'|'N');
+fragment O: ('o'|'O');
+fragment P: ('p'|'P');
+fragment Q: ('q'|'Q');
+fragment R: ('r'|'R');
+fragment S: ('s'|'S');
+fragment T: ('t'|'T');
+fragment U: ('u'|'U');
+fragment V: ('v'|'V');
+fragment W: ('w'|'W');
+fragment X: ('x'|'X');
+fragment Y: ('y'|'Y');
+fragment Z: ('z'|'Z');
+
+STRING_LITERAL
+ @init{ StringBuilder b = new StringBuilder(); }
+ @after{ setText(b.toString()); }
+ : '\'' (c=~('\'') { b.appendCodePoint(c);} | '\'' '\'' { b.appendCodePoint('\''); })* '\''
+ ;
+
+QUOTED_NAME
+ @init{ StringBuilder b = new StringBuilder(); }
+ @after{ setText(b.toString()); }
+ : '\"' (c=~('\"') { b.appendCodePoint(c); } | '\"' '\"' { b.appendCodePoint('\"'); })* '\"'
+ ;
+
+fragment DIGIT
+ : '0'..'9'
+ ;
+
+fragment LETTER
+ : ('A'..'Z' | 'a'..'z')
+ ;
+
+fragment HEX
+ : ('A'..'F' | 'a'..'f' | '0'..'9')
+ ;
+
+INTEGER
+ : '-'? DIGIT+
+ ;
+
+QMARK
+ : '?'
+ ;
+
+/*
+ * Normally a lexer only emits one token at a time, but ours is tricked out
+ * to support multiple (see @lexer::members near the top of the grammar).
+ */
+FLOAT
+ : INTEGER '.' INTEGER
+ ;
+
+IDENT
+ : LETTER (LETTER | DIGIT | '_')*
+ ;
+
+COMPIDENT
+ : IDENT ( ':' (IDENT | INTEGER))+
+ ;
+
+UUID
+ : HEX HEX HEX HEX HEX HEX HEX HEX '-'
+ HEX HEX HEX HEX '-'
+ HEX HEX HEX HEX '-'
+ HEX HEX HEX HEX '-'
+ HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX
+ ;
+
+WS
+ : (' ' | '\t' | '\n' | '\r')+ { $channel = HIDDEN; }
+ ;
+
+COMMENT
+ : ('--' | '//') .* ('\n'|'\r') { $channel = HIDDEN; }
+ ;
+
+MULTILINE_COMMENT
+ : '/*' .* '*/' { $channel = HIDDEN; }
+ ;
diff --git a/src/java/org/apache/cassandra/cql3/Operation.java b/src/java/org/apache/cassandra/cql3/Operation.java
new file mode 100644
index 0000000000..63097b9f35
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Operation.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+public class Operation
+{
+ public static enum Type { PLUS, MINUS }
+
+ public final Type type;
+ public final ColumnIdentifier ident;
+ public final Term value;
+
+ // unary operation
+ public Operation(Term a)
+ {
+ this(null, null, a);
+ }
+
+ // binary operation
+ public Operation(ColumnIdentifier a, Type type, Term b)
+ {
+ this.ident = a;
+ this.type = type;
+ this.value = b;
+ }
+
+ public boolean isUnary()
+ {
+ return type == null && ident == null;
+ }
+
+ public String toString()
+ {
+ return (isUnary())
+ ? String.format("UnaryOperation(%s)", value)
+ : String.format("BinaryOperation(%s, %s, %s)", ident, type, value);
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
new file mode 100644
index 0000000000..7c82616f19
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Maps;
+import org.antlr.runtime.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.cql3.statements.*;
+import org.apache.cassandra.config.*;
+import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.*;
+import org.apache.cassandra.thrift.Column;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.Pair;
+import org.apache.cassandra.utils.SemanticVersion;
+
+public class QueryProcessor
+{
+ public static final SemanticVersion CQL_VERSION = new SemanticVersion("3.0.0-beta1");
+
+ private static final Logger logger = LoggerFactory.getLogger(QueryProcessor.class);
+
+ public static void validateKey(ByteBuffer key) throws InvalidRequestException
+ {
+ if (key == null || key.remaining() == 0)
+ {
+ throw new InvalidRequestException("Key may not be empty");
+ }
+
+ // check that key can be handled by FBUtilities.writeShortByteArray
+ if (key.remaining() > FBUtilities.MAX_UNSIGNED_SHORT)
+ {
+ throw new InvalidRequestException("Key length of " + key.remaining() +
+ " is longer than maximum of " + FBUtilities.MAX_UNSIGNED_SHORT);
+ }
+ }
+
+ public static void validateColumnNames(Iterable columns)
+ throws InvalidRequestException
+ {
+ for (ByteBuffer name : columns)
+ {
+ if (name.remaining() > IColumn.MAX_NAME_LENGTH)
+ throw new InvalidRequestException(String.format("column name is too long (%s > %s)",
+ name.remaining(),
+ IColumn.MAX_NAME_LENGTH));
+ if (name.remaining() == 0)
+ throw new InvalidRequestException("zero-length column name");
+ }
+ }
+
+ public static void validateColumnName(ByteBuffer column)
+ throws InvalidRequestException
+ {
+ validateColumnNames(Collections.singletonList(column));
+ }
+
+ public static void validateSlicePredicate(CFMetaData metadata, SlicePredicate predicate)
+ throws InvalidRequestException
+ {
+ if (predicate.slice_range != null)
+ validateSliceRange(metadata, predicate.slice_range);
+ else
+ validateColumnNames(predicate.column_names);
+ }
+
+ public static void validateSliceRange(CFMetaData metadata, SliceRange range)
+ throws InvalidRequestException
+ {
+ validateSliceRange(metadata, range.start, range.finish, range.reversed);
+ }
+
+ public static void validateSliceRange(CFMetaData metadata, ByteBuffer start, ByteBuffer finish, boolean reversed)
+ throws InvalidRequestException
+ {
+ AbstractType> comparator = metadata.getComparatorFor(null);
+ Comparator orderedComparator = reversed ? comparator.reverseComparator: comparator;
+ if (start.remaining() > 0 && finish.remaining() > 0 && orderedComparator.compare(start, finish) > 0)
+ throw new InvalidRequestException("Range finish must come after start in traversal order");
+ }
+
+ private static CqlResult processStatement(CQLStatement statement, ClientState clientState, List variables)
+ throws UnavailableException, InvalidRequestException, TimedOutException, SchemaDisagreementException
+ {
+ statement.checkAccess(clientState);
+ statement.validate(clientState);
+ CqlResult result = statement.execute(clientState, variables);
+ if (result == null)
+ {
+ result = new CqlResult();
+ result.type = CqlResultType.VOID;
+ }
+ return result;
+ }
+
+ public static CqlResult process(String queryString, ClientState clientState)
+ throws RecognitionException, UnavailableException, InvalidRequestException, TimedOutException, SchemaDisagreementException
+ {
+ logger.trace("CQL QUERY: {}", queryString);
+ return processStatement(getStatement(queryString, clientState), clientState, Collections.emptyList());
+ }
+
+ public static CqlPreparedResult prepare(String queryString, ClientState clientState)
+ throws RecognitionException, InvalidRequestException
+ {
+ logger.trace("CQL QUERY: {}", queryString);
+
+ CQLStatement statement = getStatement(queryString, clientState);
+ int statementId = makeStatementId(queryString);
+ logger.trace("Discovered "+ statement.getBoundsTerms() + " bound variables.");
+
+ clientState.getCQL3Prepared().put(statementId, statement);
+ logger.trace(String.format("Stored prepared statement #%d with %d bind markers",
+ statementId,
+ statement.getBoundsTerms()));
+
+ return new CqlPreparedResult(statementId, statement.getBoundsTerms());
+ }
+
+ public static CqlResult processPrepared(CQLStatement statement, ClientState clientState, List variables)
+ throws UnavailableException, InvalidRequestException, TimedOutException, SchemaDisagreementException
+ {
+ // Check to see if there are any bound variables to verify
+ if (!(variables.isEmpty() && (statement.getBoundsTerms() == 0)))
+ {
+ if (variables.size() != statement.getBoundsTerms())
+ throw new InvalidRequestException(String.format("there were %d markers(?) in CQL but %d bound variables",
+ statement.getBoundsTerms(),
+ variables.size()));
+
+ // at this point there is a match in count between markers and variables that is non-zero
+
+ if (logger.isTraceEnabled())
+ for (int i = 0; i < variables.size(); i++)
+ logger.trace("[{}] '{}'", i+1, variables.get(i));
+ }
+
+ return processStatement(statement, clientState, variables);
+ }
+
+ private static final int makeStatementId(String cql)
+ {
+ // use the hash of the string till something better is provided
+ return cql.hashCode();
+ }
+
+ private static CQLStatement getStatement(String queryStr, ClientState clientState) throws InvalidRequestException, RecognitionException
+ {
+ CQLStatement statement = parseStatement(queryStr);
+
+ // Set keyspace for statement that require login
+ if (statement instanceof CFStatement)
+ ((CFStatement)statement).prepareKeyspace(clientState);
+
+ if (statement instanceof Preprocessable)
+ statement = ((Preprocessable)statement).preprocess();
+
+ return statement;
+ }
+
+ private static CQLStatement parseStatement(String queryStr) throws InvalidRequestException, RecognitionException
+ {
+ // Lexer and parser
+ CharStream stream = new ANTLRStringStream(queryStr);
+ CqlLexer lexer = new CqlLexer(stream);
+ TokenStream tokenStream = new CommonTokenStream(lexer);
+ CqlParser parser = new CqlParser(tokenStream);
+
+ // Parse the query string to a statement instance
+ CQLStatement statement = parser.query();
+
+ // The lexer and parser queue up any errors they may have encountered
+ // along the way, if necessary, we turn them into exceptions here.
+ lexer.throwLastRecognitionError();
+ parser.throwLastRecognitionError();
+
+ return statement;
+ }
+
+}
diff --git a/src/java/org/apache/cassandra/cql3/Relation.java b/src/java/org/apache/cassandra/cql3/Relation.java
new file mode 100644
index 0000000000..0724abdc97
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Relation.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Relations encapsulate the relationship between an entity of some kind, and
+ * a value (term). For example, > "start" or "colname1" = "somevalue".
+ *
+ */
+public class Relation
+{
+ private final ColumnIdentifier entity;
+ private final Type relationType;
+ private final Term value;
+ private final List inValues;
+
+ public static enum Type
+ {
+ EQ, LT, LTE, GTE, GT, IN;
+
+ public static Type forString(String s)
+ {
+ if (s.equals("="))
+ return EQ;
+ else if (s.equals("<"))
+ return LT;
+ else if (s.equals("<="))
+ return LTE;
+ else if (s.equals(">="))
+ return GTE;
+ else if (s.equals(">"))
+ return GT;
+
+ return null;
+ }
+ }
+
+ private Relation(ColumnIdentifier entity, Type type, Term value, List inValues)
+ {
+ this.entity = entity;
+ this.relationType = type;
+ this.value = value;
+ this.inValues = inValues;
+ }
+
+ /**
+ * Creates a new relation.
+ *
+ * @param entity the kind of relation this is; what the term is being compared to.
+ * @param type the type that describes how this entity relates to the value.
+ * @param value the value being compared.
+ */
+ public Relation(ColumnIdentifier entity, String type, Term value)
+ {
+ this(entity, Type.forString(type), value, null);
+ }
+
+ public static Relation createInRelation(ColumnIdentifier entity)
+ {
+ return new Relation(entity, Type.IN, null, new ArrayList());
+ }
+
+ public Type operator()
+ {
+ return relationType;
+ }
+
+ public ColumnIdentifier getEntity()
+ {
+ return entity;
+ }
+
+ public Term getValue()
+ {
+ assert relationType != Type.IN;
+ return value;
+ }
+
+ public List getInValues()
+ {
+ assert relationType == Type.IN;
+ return inValues;
+ }
+
+ public void addInValue(Term t)
+ {
+ inValues.add(t);
+ }
+
+ @Override
+ public String toString()
+ {
+ if (relationType == Type.IN)
+ return String.format("%s IN %s", entity, inValues);
+ else
+ return String.format("%s %s %s", entity, relationType, value);
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java
new file mode 100644
index 0000000000..8648012f0d
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/Term.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.AsciiType;
+import org.apache.cassandra.db.marshal.FloatType;
+import org.apache.cassandra.db.marshal.IntegerType;
+import org.apache.cassandra.db.marshal.LexicalUUIDType;
+import org.apache.cassandra.db.marshal.MarshalException;
+import org.apache.cassandra.thrift.InvalidRequestException;
+
+/** A term parsed from a CQL statement. */
+public class Term
+{
+ private final String text;
+ private final TermType type;
+ private int bindIndex = -1;
+
+ public Term(String text, TermType type)
+ {
+ this.text = text == null ? "" : text;
+ this.type = type;
+ }
+
+ /**
+ * Create new Term instance from a string, and an integer that corresponds
+ * with the token ID from CQLParser.
+ *
+ * @param text the text representation of the term.
+ * @param type the term's type as an integer token ID.
+ */
+ public Term(String text, int type)
+ {
+ this(text == null ? "" : text, TermType.forInt(type));
+ }
+
+ public Term(long value, TermType type)
+ {
+ this(String.valueOf(value), type);
+ }
+
+ public Term(String text, int type, int index)
+ {
+ this(text, type);
+ this.bindIndex = index;
+ }
+
+ /**
+ * Returns the text parsed to create this term.
+ *
+ * @return the string term acquired from a CQL statement.
+ */
+ public String getText()
+ {
+ return text;
+ }
+
+ /**
+ * Returns the typed value, serialized to a ByteBuffer according to a
+ * comparator/validator.
+ *
+ * @return a ByteBuffer of the value.
+ * @throws InvalidRequestException if unable to coerce the string to its type.
+ */
+ public ByteBuffer getByteBuffer(AbstractType> validator, List variables) throws InvalidRequestException
+ {
+ try
+ {
+ if (!isBindMarker())
+ return validator.fromString(text);
+
+ // must be a marker term so check for a CqlBindValue stored in the term
+ if (bindIndex == -1)
+ throw new AssertionError("a marker Term was encountered with no index value");
+
+ ByteBuffer value = variables.get(bindIndex);
+ validator.validate(value);
+ return value;
+ }
+ catch (MarshalException e)
+ {
+ throw new InvalidRequestException(e.getMessage());
+ }
+ }
+
+ /**
+ * Returns the typed value, serialized to a ByteBuffer.
+ *
+ * @return a ByteBuffer of the value.
+ * @throws InvalidRequestException if unable to coerce the string to its type.
+ */
+ public ByteBuffer getByteBuffer() throws InvalidRequestException
+ {
+ switch (type)
+ {
+ case STRING:
+ return AsciiType.instance.fromString(text);
+ case INTEGER:
+ return IntegerType.instance.fromString(text);
+ case UUID:
+ // we specifically want the Lexical class here, not "UUIDType," because we're supposed to have
+ // a uuid-shaped string here, and UUIDType also accepts integer or date strings (and turns them into version 1 uuids).
+ return LexicalUUIDType.instance.fromString(text);
+ case FLOAT:
+ return FloatType.instance.fromString(text);
+ }
+
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Obtain the term's type.
+ *
+ * @return the type
+ */
+ public TermType getType()
+ {
+ return type;
+ }
+
+ public boolean isBindMarker()
+ {
+ return type == TermType.QMARK;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("Term(%s, type=%s)", getText(), type);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((text == null) ? 0 : text.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Term other = (Term) obj;
+ if (type==TermType.QMARK) return false; // markers are never equal
+ if (text == null)
+ {
+ if (other.text != null)
+ return false;
+ } else if (!text.equals(other.text))
+ return false;
+ if (type != other.type)
+ return false;
+ return true;
+ }
+}
+
+enum TermType
+{
+ STRING, INTEGER, UUID, FLOAT, QMARK;
+
+ static TermType forInt(int type)
+ {
+ if ((type == CqlParser.STRING_LITERAL) || (type == CqlParser.IDENT))
+ return STRING;
+ else if (type == CqlParser.INTEGER)
+ return INTEGER;
+ else if (type == CqlParser.UUID)
+ return UUID;
+ else if (type == CqlParser.FLOAT)
+ return FLOAT;
+ else if (type == CqlParser.QMARK)
+ return QMARK;
+
+ // FIXME: handled scenario that should never occur.
+ return null;
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
new file mode 100644
index 0000000000..8a1ab42915
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.config.*;
+import org.apache.cassandra.db.migration.Migration;
+import org.apache.cassandra.db.migration.UpdateColumnFamily;
+import org.apache.cassandra.thrift.CfDef;
+import org.apache.cassandra.thrift.ColumnDef;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;
+
+public class AlterTableStatement extends SchemaAlteringStatement
+{
+ public static enum Type
+ {
+ ADD, ALTER, DROP, OPTS
+ }
+
+ public final Type oType;
+ public final String validator;
+ public final ColumnIdentifier columnName;
+ private final CFPropDefs cfProps = new CFPropDefs();
+
+ public AlterTableStatement(CFName name, Type type, ColumnIdentifier columnName, String validator, Map propertyMap)
+ {
+ super(name);
+ this.oType = type;
+ this.columnName = null;
+ this.validator = validator; // used only for ADD/ALTER commands
+ this.cfProps.addAll(propertyMap);
+ }
+
+ public Migration getMigration() throws InvalidRequestException, IOException
+ {
+ try
+ {
+ CFMetaData meta = validateColumnFamily(keyspace(), columnFamily());
+ CfDef thriftDef = meta.toThrift();
+
+ CFDefinition cfDef = meta.getCfDef();
+ CFDefinition.Name name = this.oType == Type.OPTS ? null : cfDef.get(columnName);
+ switch (oType)
+ {
+ case ADD:
+ if (cfDef.isCompact)
+ throw new InvalidRequestException("Cannot add new column to a compact CF");
+ if (name != null)
+ {
+ switch (name.kind)
+ {
+ case KEY_ALIAS:
+ case COLUMN_ALIAS:
+ throw new InvalidRequestException(String.format("Invalid column name %s because it conflicts with a PRIMARY KEY part", columnName));
+ case COLUMN_METADATA:
+ throw new InvalidRequestException(String.format("Invalid column name %s because it conflicts with an existing column", columnName));
+ }
+ }
+ thriftDef.column_metadata.add(new ColumnDefinition(columnName.key,
+ CFPropDefs.parseType(validator),
+ null,
+ null,
+ null).toThrift());
+ break;
+
+ case ALTER:
+ if (name == null)
+ throw new InvalidRequestException(String.format("Column %s was not found in CF %s", columnName, columnFamily()));
+
+ switch (name.kind)
+ {
+ case KEY_ALIAS:
+ case COLUMN_ALIAS:
+ throw new InvalidRequestException(String.format("Cannot alter PRIMARY KEY part %s", columnName));
+ case VALUE_ALIAS:
+ thriftDef.default_validation_class = CFPropDefs.parseType(validator).toString();
+ break;
+ case COLUMN_METADATA:
+ ColumnDefinition column = meta.getColumnDefinition(columnName.key);
+ column.setValidator(CFPropDefs.parseType(validator));
+ thriftDef.column_metadata.add(column.toThrift());
+ break;
+ }
+ break;
+
+ case DROP:
+ if (cfDef.isCompact)
+ throw new InvalidRequestException("Cannot drop columns from a compact CF");
+ if (name == null)
+ throw new InvalidRequestException(String.format("Column %s was not found in CF %s", columnName, columnFamily()));
+
+ switch (name.kind)
+ {
+ case KEY_ALIAS:
+ case COLUMN_ALIAS:
+ throw new InvalidRequestException(String.format("Cannot drop PRIMARY KEY part %s", columnName));
+ case COLUMN_METADATA:
+ ColumnDef toDelete = null;
+ for (ColumnDef columnDef : thriftDef.column_metadata)
+ {
+ if (columnDef.name.equals(columnName.key))
+ toDelete = columnDef;
+ }
+ assert toDelete != null;
+ thriftDef.column_metadata.remove(toDelete);
+ break;
+ }
+ break;
+ case OPTS:
+ if (cfProps == null)
+ throw new InvalidRequestException(String.format("ALTER COLUMNFAMILY WITH invoked, but no parameters found"));
+
+ cfProps.validate();
+ applyPropertiesToCfDef(thriftDef, cfProps);
+ break;
+ }
+ return new UpdateColumnFamily(thriftDef);
+ }
+ catch (ConfigurationException e)
+ {
+ InvalidRequestException ex = new InvalidRequestException(e.toString());
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ public static void applyPropertiesToCfDef(CfDef cfDef, CFPropDefs cfProps) throws InvalidRequestException
+ {
+ if (cfProps.hasProperty(CFPropDefs.KW_COMMENT))
+ {
+ cfDef.comment = cfProps.get(CFPropDefs.KW_COMMENT);
+ }
+
+ cfDef.read_repair_chance = cfProps.getDouble(CFPropDefs.KW_READREPAIRCHANCE, cfDef.read_repair_chance);
+ cfDef.gc_grace_seconds = cfProps.getInt(CFPropDefs.KW_GCGRACESECONDS, cfDef.gc_grace_seconds);
+ cfDef.replicate_on_write = cfProps.getBoolean(CFPropDefs.KW_REPLICATEONWRITE, cfDef.replicate_on_write);
+ cfDef.min_compaction_threshold = cfProps.getInt(CFPropDefs.KW_MINCOMPACTIONTHRESHOLD, cfDef.min_compaction_threshold);
+ cfDef.max_compaction_threshold = cfProps.getInt(CFPropDefs.KW_MAXCOMPACTIONTHRESHOLD, cfDef.max_compaction_threshold);
+
+ if (!cfProps.compactionStrategyOptions.isEmpty())
+ {
+ cfDef.compaction_strategy_options = new HashMap(cfProps.compactionStrategyOptions);
+ }
+
+ if (!cfProps.compressionParameters.isEmpty())
+ {
+ cfDef.compression_options = new HashMap(cfProps.compressionParameters);
+ }
+ }
+
+ public String toString()
+ {
+ return String.format("AlterTableStatement(name=%s, type=%s, column=%s, validator=%s)",
+ cfName,
+ oType,
+ columnName,
+ validator);
+ }
+
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
new file mode 100644
index 0000000000..077d76d3fc
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.db.RowMutation;
+import org.apache.cassandra.db.CounterMutation;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.RequestType;
+import org.apache.cassandra.thrift.ThriftValidation;
+import org.apache.cassandra.utils.Pair;
+
+/**
+ * A BATCH statement parsed from a CQL query.
+ *
+ */
+public class BatchStatement extends ModificationStatement
+{
+ // statements to execute
+ protected final List statements;
+
+ /**
+ * Creates a new BatchStatement from a list of statements and a
+ * Thrift consistency level.
+ *
+ * @param statements a list of UpdateStatements
+ * @param attrs additional attributes for statement (CL, timestamp, timeToLive)
+ */
+ public BatchStatement(List statements, Attributes attrs)
+ {
+ super(null, attrs);
+ this.statements = statements;
+ }
+
+ @Override
+ public void prepareKeyspace(ClientState state) throws InvalidRequestException
+ {
+ for (ModificationStatement statement : statements)
+ statement.prepareKeyspace(state);
+ }
+
+ @Override
+ public void checkAccess(ClientState state) throws InvalidRequestException
+ {
+ Set cfamsSeen = new HashSet();
+ for (ModificationStatement statement : statements)
+ {
+ // Avoid unnecessary authorizations.
+ if (!(cfamsSeen.contains(statement.columnFamily())))
+ {
+ state.hasColumnFamilyAccess(statement.keyspace(), statement.columnFamily(), Permission.WRITE);
+ cfamsSeen.add(statement.columnFamily());
+ }
+ }
+ }
+
+ @Override
+ public void validate(ClientState state) throws InvalidRequestException
+ {
+ if (getTimeToLive() != 0)
+ throw new InvalidRequestException("Global TTL on the BATCH statement is not supported.");
+
+ for (ModificationStatement statement : statements)
+ {
+ if (statement.isSetConsistencyLevel())
+ throw new InvalidRequestException("Consistency level must be set on the BATCH, not individual statements");
+
+ if (isSetTimestamp() && statement.isSetTimestamp())
+ throw new InvalidRequestException("Timestamp must be set either on BATCH or individual statements");
+
+ if (statement.getTimeToLive() < 0)
+ throw new InvalidRequestException("A TTL must be greater or equal to 0");
+
+ ThriftValidation.validateConsistencyLevel(statement.keyspace(), getConsistencyLevel(), RequestType.WRITE);
+ }
+ }
+
+ public List getMutations(ClientState clientState, List variables)
+ throws InvalidRequestException
+ {
+
+ Map, RowAndCounterMutation> mutations = new HashMap, RowAndCounterMutation>();
+ for (ModificationStatement statement : statements)
+ {
+ if (isSetTimestamp())
+ statement.timestamp = timestamp;
+
+ List lm = statement.getMutations(clientState, variables);
+ // Group mutation together, otherwise they won't get applied atomically
+ for (IMutation m : lm)
+ {
+ Pair key = Pair.create(m.getTable(), m.key());
+ RowAndCounterMutation racm = mutations.get(key);
+ if (racm == null)
+ {
+ racm = new RowAndCounterMutation();
+ mutations.put(key, racm);
+ }
+
+ if (m instanceof CounterMutation)
+ {
+ if (racm.cm == null)
+ racm.cm = (CounterMutation)m;
+ else
+ racm.cm.addAll(m);
+ }
+ else
+ {
+ assert m instanceof RowMutation;
+ if (racm.rm == null)
+ racm.rm = (RowMutation)m;
+ else
+ racm.rm.addAll(m);
+ }
+ }
+ }
+
+ List batch = new LinkedList();
+ for (RowAndCounterMutation racm : mutations.values())
+ {
+ if (racm.rm != null)
+ batch.add(racm.rm);
+ if (racm.cm != null)
+ batch.add(racm.cm);
+ }
+ return batch;
+ }
+
+ public String toString()
+ {
+ return String.format("BatchStatement(statements=%s, consistency=%s)", statements, cLevel);
+ }
+
+ private static class RowAndCounterMutation
+ {
+ public RowMutation rm;
+ public CounterMutation cm;
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/CFStatement.java b/src/java/org/apache/cassandra/cql3/statements/CFStatement.java
new file mode 100644
index 0000000000..15e88d72f4
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/CFStatement.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import org.apache.cassandra.cql3.CFName;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.InvalidRequestException;
+
+/**
+ * Abstract class for statements that apply on a given column family.
+ */
+public abstract class CFStatement extends CQLStatement
+{
+ protected final CFName cfName;
+
+ protected CFStatement(CFName cfName)
+ {
+ this.cfName = cfName;
+ }
+
+ public void prepareKeyspace(ClientState state) throws InvalidRequestException
+ {
+ if (!cfName.hasKeyspace())
+ {
+ cfName.setKeyspace(state.getKeyspace(), true);
+ }
+ }
+
+ public String keyspace()
+ {
+ assert cfName.hasKeyspace() : "The statement hasn't be prepared correctly";
+ return cfName.getKeyspace();
+ }
+
+ public String columnFamily()
+ {
+ return cfName.getColumnFamily();
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java
new file mode 100644
index 0000000000..f652a778b8
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang.StringUtils;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.db.ColumnFamilyType;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.migration.AddColumnFamily;
+import org.apache.cassandra.db.migration.Migration;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.CqlResult;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.ThriftValidation;
+import org.apache.cassandra.io.compress.CompressionParameters;
+
+/** A CREATE COLUMNFAMILY parsed from a CQL query statement. */
+public class CreateColumnFamilyStatement extends SchemaAlteringStatement
+{
+ private AbstractType> comparator;
+ private AbstractType> defaultValidator;
+ private AbstractType> keyValidator;
+
+ private ByteBuffer keyAlias;
+ private List columnAliases = new ArrayList();
+ private ByteBuffer valueAlias;
+
+ private final Map columns = new HashMap();
+ private final CFPropDefs properties;
+
+ public CreateColumnFamilyStatement(CFName name, CFPropDefs properties)
+ {
+ super(name);
+ this.properties = properties;
+ }
+
+ // Column definitions
+ private Map getColumns() throws InvalidRequestException
+ {
+ Map columnDefs = new HashMap();
+
+ for (Map.Entry col : columns.entrySet())
+ {
+ try
+ {
+ AbstractType> validator = CFPropDefs.parseType(col.getValue());
+ columnDefs.put(col.getKey().key, new ColumnDefinition(col.getKey().key, validator, null, null, null));
+ }
+ catch (ConfigurationException e)
+ {
+ InvalidRequestException ex = new InvalidRequestException(e.toString());
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ return columnDefs;
+ }
+
+ public Migration getMigration() throws InvalidRequestException, ConfigurationException, IOException
+ {
+ CFMetaData cfmd = getCFMetaData();
+ ThriftValidation.validateCfDef(cfmd.toThrift(), null);
+ return new AddColumnFamily(cfmd);
+ }
+
+ /**
+ * Returns a CFMetaData instance based on the parameters parsed from this
+ * CREATE statement, or defaults where applicable.
+ *
+ * @param keyspace keyspace to apply this column family to
+ * @param variables list of bound variables (for prepared statement)
+ * @return a CFMetaData instance corresponding to the values parsed from this statement
+ * @throws InvalidRequestException on failure to validate parsed parameters
+ */
+ public CFMetaData getCFMetaData() throws InvalidRequestException
+ {
+ CFMetaData newCFMD;
+ try
+ {
+ newCFMD = new CFMetaData(keyspace(),
+ columnFamily(),
+ ColumnFamilyType.Standard,
+ comparator,
+ null);
+
+ newCFMD.comment(properties.get(CFPropDefs.KW_COMMENT))
+ .readRepairChance(properties.getDouble(CFPropDefs.KW_READREPAIRCHANCE, CFMetaData.DEFAULT_READ_REPAIR_CHANCE))
+ .replicateOnWrite(properties.getBoolean(CFPropDefs.KW_REPLICATEONWRITE, CFMetaData.DEFAULT_REPLICATE_ON_WRITE))
+ .gcGraceSeconds(properties.getInt(CFPropDefs.KW_GCGRACESECONDS, CFMetaData.DEFAULT_GC_GRACE_SECONDS))
+ .defaultValidator(defaultValidator)
+ .minCompactionThreshold(properties.getInt(CFPropDefs.KW_MINCOMPACTIONTHRESHOLD, CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD))
+ .maxCompactionThreshold(properties.getInt(CFPropDefs.KW_MAXCOMPACTIONTHRESHOLD, CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD))
+ .mergeShardsChance(0.0)
+ .columnMetadata(getColumns())
+ .keyValidator(keyValidator)
+ .keyAlias(keyAlias)
+ .columnAliases(columnAliases)
+ .valueAlias(valueAlias)
+ .compactionStrategyOptions(properties.compactionStrategyOptions)
+ .compressionParameters(CompressionParameters.create(properties.compressionParameters))
+ .validate();
+ }
+ catch (ConfigurationException e)
+ {
+ throw new InvalidRequestException(e.toString());
+ }
+ return newCFMD;
+ }
+
+ public static class RawStatement extends CFStatement implements Preprocessable
+ {
+ private final Map definitions = new HashMap();
+ private final CFPropDefs properties = new CFPropDefs();
+
+ private final List keyAliases = new ArrayList();
+ private List columnAliases = new ArrayList();
+
+ private boolean useCompactStorage;
+ private Multiset definedNames = HashMultiset.create(1);
+
+ public RawStatement(CFName name)
+ {
+ super(name);
+ }
+
+ /**
+ * Transform this raw statement into a CreateColumnFamilyStatement.
+ */
+ public CreateColumnFamilyStatement preprocess() throws InvalidRequestException
+ {
+ try
+ {
+ // Column family name
+ if (!columnFamily().matches("\\w+"))
+ throw new InvalidRequestException(String.format("\"%s\" is not a valid column family name (must be alphanumeric character only: [0-9A-Za-z]+)", columnFamily()));
+ if (columnFamily().length() > 32)
+ throw new InvalidRequestException(String.format("Column family names shouldn't be more than 32 character long (got \"%s\")", columnFamily()));
+
+ for (Multiset.Entry entry : definedNames.entrySet())
+ if (entry.getCount() > 1)
+ throw new InvalidRequestException(String.format("Multiple definition of identifier %s", entry.getElement()));
+
+ properties.validate();
+
+ CreateColumnFamilyStatement stmt = new CreateColumnFamilyStatement(cfName, properties);
+ stmt.setBoundTerms(getBoundsTerms());
+ stmt.columns.putAll(definitions); // we'll remove what is not a column below
+
+ // Ensure that exactly one key has been specified.
+ if (keyAliases.size() == 0)
+ throw new InvalidRequestException("You must specify a PRIMARY KEY");
+ else if (keyAliases.size() > 1)
+ throw new InvalidRequestException("You may only specify one PRIMARY KEY");
+
+ stmt.keyAlias = keyAliases.get(0).key;
+ stmt.keyValidator = getTypeAndRemove(stmt.columns, keyAliases.get(0));
+
+ // Handle column aliases
+ if (columnAliases != null && !columnAliases.isEmpty())
+ {
+ // If we use compact storage and have only one alias, it is a
+ // standard "dynamic" CF, otherwise it's a composite
+ if (useCompactStorage && columnAliases.size() == 1)
+ {
+ stmt.columnAliases.add(columnAliases.get(0).key);
+ stmt.comparator = getTypeAndRemove(stmt.columns, columnAliases.get(0));
+ }
+ else
+ {
+ List> types = new ArrayList>();
+ for (ColumnIdentifier t : columnAliases)
+ {
+ stmt.columnAliases.add(t.key);
+ types.add(getTypeAndRemove(stmt.columns, t));
+ }
+ // For sparse, we must add the last UTF8 component
+ if (!useCompactStorage)
+ types.add(CFDefinition.definitionType);
+
+ stmt.comparator = CompositeType.getInstance(types);
+ }
+ }
+ else
+ {
+ stmt.comparator = CFDefinition.definitionType;
+ }
+
+ if (useCompactStorage)
+ {
+ // There should be only one column definition remaining, which gives us the default validator.
+ if (stmt.columns.isEmpty())
+ throw new InvalidRequestException("COMPACT STORAGE requires one definition not part of the PRIMARY KEY, none found");
+ if (stmt.columns.size() > 1)
+ throw new InvalidRequestException(String.format("COMPACT STORAGE allows only one column not part of the PRIMARY KEY (got: %s)", StringUtils.join(stmt.columns.keySet(), ", ")));
+
+ Map.Entry lastEntry = stmt.columns.entrySet().iterator().next();
+ stmt.defaultValidator = CFPropDefs.parseType(lastEntry.getValue());
+ stmt.valueAlias = lastEntry.getKey().key;
+ stmt.columns.remove(lastEntry.getKey());
+ }
+ else
+ {
+ if (stmt.columns.isEmpty())
+ throw new InvalidRequestException("No definition found that is not part of the PRIMARY KEY");
+
+ // There is no way to insert/access a column that is not defined for non-compact
+ // storage, so the actual validator don't matter much.
+ stmt.defaultValidator = CFDefinition.definitionType;
+ }
+
+ return stmt;
+ }
+ catch (ConfigurationException e)
+ {
+ throw new InvalidRequestException(e.getMessage());
+ }
+ }
+
+ private static AbstractType> getTypeAndRemove(Map columns, ColumnIdentifier t) throws InvalidRequestException, ConfigurationException
+ {
+ String typeStr = columns.get(t);
+ if (typeStr == null)
+ throw new InvalidRequestException(String.format("Unkown definition %s referenced in PRIMARY KEY", t));
+ columns.remove(t);
+ return CFPropDefs.parseType(typeStr);
+ }
+
+ public void addDefinition(ColumnIdentifier def, String type)
+ {
+ definedNames.add(def);
+ definitions.put(def, type);
+ }
+
+ public void setKeyAlias(ColumnIdentifier alias)
+ {
+ keyAliases.add(alias);
+ }
+
+ public void addColumnAlias(ColumnIdentifier alias)
+ {
+ columnAliases.add(alias);
+ }
+
+ public void addProperty(String name, String value)
+ {
+ properties.addProperty(name, value);
+ }
+
+ public void setCompactStorage()
+ {
+ useCompactStorage = true;
+ }
+
+ public void checkAccess(ClientState state)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public CqlResult execute(ClientState state, List variables)
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
new file mode 100644
index 0000000000..6be4e63011
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.db.migration.Migration;
+import org.apache.cassandra.db.migration.UpdateColumnFamily;
+import org.apache.cassandra.thrift.CfDef;
+import org.apache.cassandra.thrift.ColumnDef;
+import org.apache.cassandra.thrift.IndexType;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.ThriftValidation;
+
+/** A CREATE INDEX statement parsed from a CQL query. */
+public class CreateIndexStatement extends SchemaAlteringStatement
+{
+ private static final Logger logger = LoggerFactory.getLogger(CreateIndexStatement.class);
+
+ private final String indexName;
+ private final ColumnIdentifier columnName;
+
+ public CreateIndexStatement(CFName name, String indexName, ColumnIdentifier columnName)
+ {
+ super(name);
+ this.indexName = indexName;
+ this.columnName = columnName;
+ }
+
+ public Migration getMigration() throws InvalidRequestException, ConfigurationException
+ {
+ try
+ {
+ CFMetaData oldCfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
+ boolean columnExists = false;
+ // mutating oldCfm directly would be bad, but mutating a Thrift copy is fine. This also
+ // sets us up to use validateCfDef to check for index name collisions.
+ CfDef cf_def = oldCfm.toThrift();
+ for (ColumnDef cd : cf_def.column_metadata)
+ {
+ if (cd.name.equals(columnName.key))
+ {
+ if (cd.index_type != null)
+ throw new InvalidRequestException("Index already exists");
+ if (logger.isDebugEnabled())
+ logger.debug("Updating column {} definition for index {}", columnName, indexName);
+ cd.setIndex_type(IndexType.KEYS);
+ cd.setIndex_name(indexName);
+ columnExists = true;
+ break;
+ }
+ }
+ if (!columnExists)
+ {
+ CFDefinition cfDef = oldCfm.getCfDef();
+ CFDefinition.Name name = cfDef.get(columnName);
+ if (name != null)
+ {
+ switch (name.kind)
+ {
+ case KEY_ALIAS:
+ case COLUMN_ALIAS:
+ throw new InvalidRequestException(String.format("Cannot create index on PRIMARY KEY part %s", columnName));
+ case VALUE_ALIAS:
+ throw new InvalidRequestException(String.format("Cannot create index on column %s of compact CF", columnName));
+ }
+ }
+ throw new InvalidRequestException("No column definition found for column " + columnName);
+ }
+
+ CFMetaData.addDefaultIndexNames(cf_def);
+ ThriftValidation.validateCfDef(cf_def, oldCfm);
+ return new UpdateColumnFamily(cf_def);
+ }
+ catch (InvalidRequestException e)
+ {
+ logger.error("oups", e);
+ throw e;
+ }
+ catch (ConfigurationException e)
+ {
+ logger.error("oups", e);
+ throw e;
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java
new file mode 100644
index 0000000000..1e9349c52f
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.KSMetaData;
+import org.apache.cassandra.db.migration.AddKeyspace;
+import org.apache.cassandra.db.migration.Migration;
+import org.apache.cassandra.locator.AbstractReplicationStrategy;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.thrift.CfDef;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.KsDef;
+import org.apache.cassandra.thrift.SchemaDisagreementException;
+import org.apache.cassandra.thrift.ThriftValidation;
+
+/** A CREATE KEYSPACE statement parsed from a CQL query. */
+public class CreateKeyspaceStatement extends SchemaAlteringStatement
+{
+ private final String name;
+ private final Map attrs;
+ private String strategyClass;
+ private Map strategyOptions = new HashMap();
+
+ /**
+ * Creates a new CreateKeyspaceStatement instance for a given
+ * keyspace name and keyword arguments.
+ *
+ * @param name the name of the keyspace to create
+ * @param attrs map of the raw keyword arguments that followed the WITH keyword.
+ */
+ public CreateKeyspaceStatement(String name, Map attrs)
+ {
+ super();
+ this.name = name;
+ this.attrs = attrs;
+ }
+
+ /**
+ * The CqlParser only goes as far as extracting the keyword arguments
+ * from these statements, so this method is responsible for processing and
+ * validating.
+ *
+ * @throws InvalidRequestException if arguments are missing or unacceptable
+ */
+ @Override
+ public void validate(ClientState state) throws InvalidRequestException, SchemaDisagreementException
+ {
+ super.validate(state);
+ ThriftValidation.validateKeyspaceNotSystem(name);
+
+ // keyspace name
+ if (!name.matches("\\w+"))
+ throw new InvalidRequestException(String.format("\"%s\" is not a valid keyspace name", name));
+ if (name.length() > 32)
+ throw new InvalidRequestException(String.format("Keyspace names shouldn't be more than 32 character long (got \"%s\")", name));
+
+ // required
+ if (!attrs.containsKey("strategy_class"))
+ throw new InvalidRequestException("missing required argument \"strategy_class\"");
+ strategyClass = attrs.get("strategy_class");
+
+ // optional
+ for (String key : attrs.keySet())
+ if ((key.contains(":")) && (key.startsWith("strategy_options")))
+ strategyOptions.put(key.split(":")[1], attrs.get(key));
+
+ // trial run to let ARS validate class + per-class options
+ try
+ {
+ AbstractReplicationStrategy.createReplicationStrategy(name,
+ AbstractReplicationStrategy.getClass(strategyClass),
+ StorageService.instance.getTokenMetadata(),
+ DatabaseDescriptor.getEndpointSnitch(),
+ strategyOptions);
+ }
+ catch (ConfigurationException e)
+ {
+ throw new InvalidRequestException(e.getMessage());
+ }
+ }
+
+ public Migration getMigration() throws InvalidRequestException, ConfigurationException, IOException
+ {
+ KsDef ksd = new KsDef(name, strategyClass, Collections.emptyList());
+ ksd.setStrategy_options(strategyOptions);
+ ThriftValidation.validateKsDef(ksd);
+ ThriftValidation.validateKeyspaceNotYetExisting(name);
+ return new AddKeyspace(KSMetaData.fromThrift(ksd));
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
new file mode 100644
index 0000000000..25ae4c5748
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.db.RowMutation;
+import org.apache.cassandra.db.filter.QueryPath;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.ThriftValidation;
+
+/**
+ * A DELETE parsed from a CQL query statement.
+ */
+public class DeleteStatement extends ModificationStatement
+{
+ private final List columns;
+ private final List whereClause;
+
+ private final Map> processedKeys = new HashMap>();
+
+ public DeleteStatement(CFName name, List columns, List whereClause, Attributes attrs)
+ {
+ super(name, attrs);
+
+ this.columns = columns;
+ this.whereClause = whereClause;
+ }
+
+ public List getMutations(ClientState clientState, List variables) throws InvalidRequestException
+ {
+ clientState.hasColumnFamilyAccess(columnFamily(), Permission.WRITE);
+ CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
+ CFDefinition cfDef = metadata.getCfDef();
+
+ preprocess(cfDef);
+
+ // Check key
+ List keys = processedKeys.get(cfDef.key.name);
+ if (keys == null || keys.isEmpty())
+ throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", cfDef.key.name));
+
+ ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
+ CFDefinition.Name firstEmpty = null;
+ for (CFDefinition.Name name : cfDef.columns.values())
+ {
+ List values = processedKeys.get(name.name);
+ if (values == null || values.isEmpty())
+ {
+ firstEmpty = name;
+ // For sparse, we must either have all component or none
+ if (cfDef.isComposite && !cfDef.isCompact && builder.componentCount() != 0)
+ throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
+ }
+ else if (firstEmpty != null)
+ {
+ throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s since %s is set", firstEmpty, name));
+ }
+ else
+ {
+ assert values.size() == 1; // We only allow IN for keys so far
+ builder.add(values.get(0), Relation.Type.EQ, variables);
+ }
+ }
+
+ List rowMutations = new ArrayList();
+
+ for (Term key : keys)
+ {
+ ByteBuffer rawKey = key.getByteBuffer(cfDef.key.type, variables);
+ rowMutations.add(mutationForKey(cfDef, clientState, rawKey, builder, variables));
+ }
+
+ return rowMutations;
+ }
+
+ public RowMutation mutationForKey(CFDefinition cfDef, ClientState clientState, ByteBuffer key, ColumnNameBuilder builder, List variables)
+ throws InvalidRequestException
+ {
+ RowMutation rm = new RowMutation(cfDef.cfm.ksName, key);
+
+ if (columns.isEmpty() && builder.componentCount() == 0)
+ {
+ // No columns, delete the row
+ rm.delete(new QueryPath(columnFamily()), getTimestamp(clientState));
+ }
+ else
+ {
+ for (ColumnIdentifier column : columns)
+ {
+ CFDefinition.Name name = cfDef.get(column);
+ if (name == null)
+ throw new InvalidRequestException(String.format("Unknown identifier %s", column));
+
+ // For compact, we only have one value except the key, so the only form of DELETE that make sense is without a column
+ // list. However, we support having the value name for coherence with the static/sparse case
+ if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS)
+ throw new InvalidRequestException(String.format("Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", column));
+ }
+
+ if (cfDef.isCompact)
+ {
+ ByteBuffer columnName = builder.build();
+ QueryProcessor.validateColumnName(columnName);
+ rm.delete(new QueryPath(columnFamily(), null, columnName), getTimestamp(clientState));
+ }
+ else
+ {
+ // Delete specific columns
+ Iterator iter = columns.iterator();
+ while (iter.hasNext())
+ {
+ ColumnIdentifier column = iter.next();
+ ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
+ ByteBuffer columnName = b.add(column.key).build();
+ QueryProcessor.validateColumnName(columnName);
+ rm.delete(new QueryPath(columnFamily(), null, columnName), getTimestamp(clientState));
+ }
+ }
+ }
+
+ return rm;
+ }
+
+ private void preprocess(CFDefinition cfDef) throws InvalidRequestException
+ {
+ UpdateStatement.processKeys(cfDef, whereClause, processedKeys);
+ }
+
+ public String toString()
+ {
+ return String.format("DeleteStatement(name=%s, columns=%s, consistency=%s keys=%s)",
+ cfName,
+ columns,
+ cLevel,
+ whereClause);
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropColumnFamilyStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropColumnFamilyStatement.java
new file mode 100644
index 0000000000..58e10b82cb
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/DropColumnFamilyStatement.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.cql3.CFName;
+import org.apache.cassandra.db.migration.DropColumnFamily;
+import org.apache.cassandra.db.migration.Migration;
+
+public class DropColumnFamilyStatement extends SchemaAlteringStatement
+{
+ public DropColumnFamilyStatement(CFName name)
+ {
+ super(name);
+ }
+
+ public Migration getMigration() throws ConfigurationException, IOException
+ {
+ return new DropColumnFamily(keyspace(), columnFamily());
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java
new file mode 100644
index 0000000000..4e896f0331
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/DropIndexStatement.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.config.*;
+import org.apache.cassandra.db.migration.Migration;
+import org.apache.cassandra.db.migration.UpdateColumnFamily;
+import org.apache.cassandra.thrift.CfDef;
+import org.apache.cassandra.thrift.ColumnDef;
+import org.apache.cassandra.thrift.InvalidRequestException;
+
+public class DropIndexStatement extends SchemaAlteringStatement
+{
+ public final CharSequence index;
+
+ public DropIndexStatement(String indexName)
+ {
+ super(new CFName());
+ index = indexName;
+ }
+
+ public Migration getMigration() throws InvalidRequestException, ConfigurationException, IOException
+ {
+ CfDef cfDef = null;
+
+ KSMetaData ksm = Schema.instance.getTableDefinition(keyspace());
+
+ for (CFMetaData cfm : ksm.cfMetaData().values())
+ {
+ cfDef = getUpdatedCFDef(cfm.toThrift());
+ if (cfDef != null)
+ break;
+ }
+
+ if (cfDef == null)
+ throw new InvalidRequestException("Index '" + index + "' could not be found in any of the column families of keyspace '" + keyspace() + "'");
+
+ return new UpdateColumnFamily(cfDef);
+ }
+
+ private CfDef getUpdatedCFDef(CfDef cfDef) throws InvalidRequestException
+ {
+ for (ColumnDef column : cfDef.column_metadata)
+ {
+ if (column.index_type != null && column.index_name != null && column.index_name.equals(index))
+ {
+ column.index_name = null;
+ column.index_type = null;
+ return cfDef;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java
new file mode 100644
index 0000000000..1ed057d6ae
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/DropKeyspaceStatement.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.db.migration.DropKeyspace;
+import org.apache.cassandra.db.migration.Migration;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.SchemaDisagreementException;
+import org.apache.cassandra.thrift.ThriftValidation;
+
+public class DropKeyspaceStatement extends SchemaAlteringStatement
+{
+ private final String keyspace;
+
+ public DropKeyspaceStatement(String keyspace)
+ {
+ super();
+ this.keyspace = keyspace;
+ }
+
+ @Override
+ public void validate(ClientState state) throws InvalidRequestException, SchemaDisagreementException
+ {
+ super.validate(state);
+ ThriftValidation.validateKeyspaceNotSystem(keyspace);
+ }
+
+ public Migration getMigration() throws ConfigurationException, IOException
+ {
+ return new DropKeyspace(keyspace);
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
new file mode 100644
index 0000000000..c21f5ac9ab
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.StorageProxy;
+import org.apache.cassandra.thrift.ConsistencyLevel;
+import org.apache.cassandra.thrift.CqlResult;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.RequestType;
+import org.apache.cassandra.thrift.ThriftValidation;
+import org.apache.cassandra.thrift.TimedOutException;
+import org.apache.cassandra.thrift.UnavailableException;
+
+/**
+ * Abstract class for statements that apply on a given column family.
+ */
+public abstract class ModificationStatement extends CFStatement
+{
+ public static final ConsistencyLevel defaultConsistency = ConsistencyLevel.ONE;
+
+ protected final ConsistencyLevel cLevel;
+ protected Long timestamp;
+ protected final int timeToLive;
+
+ public ModificationStatement(CFName name, Attributes attrs)
+ {
+ this(name, attrs.cLevel, attrs.timestamp, attrs.timeToLive);
+ }
+
+ public ModificationStatement(CFName name, ConsistencyLevel cLevel, Long timestamp, int timeToLive)
+ {
+ super(name);
+ this.cLevel = cLevel;
+ this.timestamp = timestamp;
+ this.timeToLive = timeToLive;
+ }
+
+ public void checkAccess(ClientState state) throws InvalidRequestException
+ {
+ state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.WRITE);
+ }
+
+ @Override
+ public void validate(ClientState state) throws InvalidRequestException
+ {
+ if (timeToLive < 0)
+ throw new InvalidRequestException("A TTL must be greater or equal to 0");
+
+ ThriftValidation.validateConsistencyLevel(keyspace(), getConsistencyLevel(), RequestType.WRITE);
+ }
+
+ public CqlResult execute(ClientState state, List variables) throws InvalidRequestException, UnavailableException, TimedOutException
+ {
+ try
+ {
+ StorageProxy.mutate(getMutations(state, variables), getConsistencyLevel());
+ }
+ catch (TimeoutException e)
+ {
+ throw new TimedOutException();
+ }
+ return null;
+ }
+
+ public ConsistencyLevel getConsistencyLevel()
+ {
+ return (cLevel != null) ? cLevel : defaultConsistency;
+ }
+
+ /**
+ * True if an explicit consistency level was parsed from the statement.
+ *
+ * @return true if a consistency was parsed, false otherwise.
+ */
+ public boolean isSetConsistencyLevel()
+ {
+ return cLevel != null;
+ }
+
+ public long getTimestamp(ClientState clientState)
+ {
+ return timestamp == null ? clientState.getTimestamp() : timestamp;
+ }
+
+ public boolean isSetTimestamp()
+ {
+ return timestamp != null;
+ }
+
+ public int getTimeToLive()
+ {
+ return timeToLive;
+ }
+
+ /**
+ * Convert statement into a list of mutations to apply on the server
+ *
+ * @param clientState current client status
+ * @param variables value for prepared statement markers
+ *
+ * @return list of the mutations
+ * @throws InvalidRequestException on invalid requests
+ */
+ public abstract List getMutations(ClientState clientState, List variables) throws InvalidRequestException;
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/Preprocessable.java b/src/java/org/apache/cassandra/cql3/statements/Preprocessable.java
new file mode 100644
index 0000000000..5b5bb2ecee
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/Preprocessable.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.thrift.InvalidRequestException;
+
+public interface Preprocessable
+{
+ public CQLStatement preprocess() throws InvalidRequestException;
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java
new file mode 100644
index 0000000000..6142488bfa
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.db.migration.*;
+import org.apache.cassandra.concurrent.Stage;
+import org.apache.cassandra.concurrent.StageManager;
+import org.apache.cassandra.config.ConfigurationException;
+import org.apache.cassandra.config.Schema;
+import org.apache.cassandra.cql3.CFName;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.StorageProxy;
+import org.apache.cassandra.thrift.CqlResult;
+import org.apache.cassandra.thrift.InvalidRequestException;
+import org.apache.cassandra.thrift.SchemaDisagreementException;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Maps;
+
+/**
+ * Abstract class for statements that alter the schema.
+ */
+public abstract class SchemaAlteringStatement extends CFStatement
+{
+ private static final long timeLimitForSchemaAgreement = 10 * 1000;
+
+ private final boolean isColumnFamilyLevel;
+
+ protected SchemaAlteringStatement()
+ {
+ super(null);
+ this.isColumnFamilyLevel = false;
+ }
+
+ protected SchemaAlteringStatement(CFName name)
+ {
+ super(name);
+ this.isColumnFamilyLevel = true;
+ }
+
+ @Override
+ public void prepareKeyspace(ClientState state) throws InvalidRequestException
+ {
+ if (isColumnFamilyLevel)
+ super.prepareKeyspace(state);
+ }
+
+ public abstract Migration getMigration() throws InvalidRequestException, IOException, ConfigurationException;
+
+ public void checkAccess(ClientState state) throws InvalidRequestException
+ {
+ if (isColumnFamilyLevel)
+ state.hasColumnFamilySchemaAccess(Permission.WRITE);
+ else
+ state.hasKeyspaceSchemaAccess(Permission.WRITE);
+ }
+
+ @Override
+ public void validate(ClientState state) throws InvalidRequestException, SchemaDisagreementException
+ {
+ validateSchemaAgreement();
+ }
+
+ public CqlResult execute(ClientState state, List variables) throws InvalidRequestException, SchemaDisagreementException
+ {
+ try
+ {
+ applyMigrationOnStage(getMigration());
+ }
+ catch (ConfigurationException e)
+ {
+ InvalidRequestException ex = new InvalidRequestException(e.toString());
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (IOException e)
+ {
+ InvalidRequestException ex = new InvalidRequestException(e.toString());
+ ex.initCause(e);
+ throw ex;
+ }
+ return null;
+ }
+
+ // Copypasta from CassandraServer (where it is private).
+ private static void validateSchemaAgreement() throws SchemaDisagreementException
+ {
+ if (describeSchemaVersions().size() > 1)
+ throw new SchemaDisagreementException();
+ }
+
+ // Copypasta from o.a.c.thrift.CassandraDaemon
+ private static void applyMigrationOnStage(final Migration m) throws SchemaDisagreementException, InvalidRequestException
+ {
+ Future> f = StageManager.getStage(Stage.MIGRATION).submit(new Callable