CQL 3.0 beta

patch by slebresne; reviewed by jbellis and urandom for CASSANDRA-3761
This commit is contained in:
Sylvain Lebresne 2012-01-16 18:50:53 +01:00
parent 86637d43cb
commit 655ccc3abf
47 changed files with 5829 additions and 32 deletions

View File

@ -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

View File

@ -202,6 +202,9 @@
<uptodate property="cqlcurrent"
srcfile="${build.src.java}/org/apache/cassandra/cql/Cql.g"
targetfile="${build.src.gen-java}/org/apache/cassandra/cql/Cql.tokens"/>
<uptodate property="cqlcurrent"
srcfile="${build.src.java}/org/apache/cassandra/cql3/Cql.g"
targetfile="${build.src.gen-java}/org/apache/cassandra/cql3/Cql.tokens"/>
</target>
<target name="gen-cql-grammar" depends="check-gen-cql-grammar" unless="cqlcurrent">
@ -213,9 +216,17 @@
<arg value="${build.src.java}/org/apache/cassandra/cql/Cql.g" />
<arg value="-fo" />
<arg value="${build.src.gen-java}/org/apache/cassandra/cql/" />
</java>
</java>
<java classname="org.antlr.Tool"
classpath="${build.lib}/antlr-3.2.jar"
fork="true"
failonerror="true">
<arg value="${build.src.java}/org/apache/cassandra/cql3/Cql.g" />
<arg value="-fo" />
<arg value="${build.src.gen-java}/org/apache/cassandra/cql3/" />
</java>
</target>
<target name="generate-cql-html" depends="maven-ant-tasks-init" description="Generate HTML from textile source">
<artifact:dependencies pathId="wikitext.classpath">
<dependency groupId="com.datastax.wikitext" artifactId="wikitext-core-ant" version="1.3"/>

View File

@ -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<string,string> compression_options,
33: optional double bloom_filter_fp_chance,
34: optional string caching="keys_only",
35: optional list<binary> 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)
}

View File

@ -67,6 +67,8 @@ protocol InterNode {
union { null, map<string> } compression_options = null;
union { null, double } bloom_filter_fp_chance = null;
union { null, string } caching = null;
union { null, array<bytes> } column_aliases = null;
union { null, bytes } value_alias = null;
}
@aliases(["org.apache.cassandra.config.avro.KsDef"])

View File

@ -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<ByteBuffer> columnAliases = new ArrayList<ByteBuffer>();
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<ByteBuffer,ColumnDefinition> prop) {column_metadata = prop; return this;}
public CFMetaData keyAlias(ByteBuffer prop) {keyAlias = prop; updateCfDef(); return this;}
public CFMetaData columnAliases(List<ByteBuffer> prop) {columnAliases = prop; updateCfDef(); return this;}
public CFMetaData valueAlias(ByteBuffer prop) {valueAlias = prop; updateCfDef(); return this;}
public CFMetaData columnMetadata(Map<ByteBuffer,ColumnDefinition> prop) {column_metadata = prop; updateCfDef(); return this;}
public CFMetaData compactionStrategyClass(Class<? extends AbstractCompactionStrategy> prop) {compactionStrategyClass = prop; return this;}
public CFMetaData compactionStrategyOptions(Map<String, String> 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<ByteBuffer,ColumnDefinition>();
try
@ -259,6 +270,7 @@ public final class CFMetaData
compactionStrategyOptions = new HashMap<String, String>();
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 <T> List<T> fixAvroRetardation(List<T> array)
{
return new ArrayList<T>(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<ByteBuffer> 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<AbstractType<?>> 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)

View File

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

View File

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

View File

@ -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<CFDefinition.Name>
{
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<ColumnIdentifier, Name> columns = new LinkedHashMap<ColumnIdentifier, Name>();
public final Name value;
// Keep metadata lexicographically ordered so that wildcard expansion have a deterministic order
public final Map<ColumnIdentifier, Name> metadata = new TreeMap<ColumnIdentifier, Name>();
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<ByteBuffer, ColumnDefinition> 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<ByteBuffer, ColumnDefinition> 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<ByteBuffer> 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<Name> iterator()
{
return new AbstractIterator<Name>()
{
private boolean keyDone;
private final Iterator<Name> columnIter = columns.values().iterator();
private boolean valueDone;
private final Iterator<Name> 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<ByteBuffer> 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;
}
}
}

View File

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

View File

@ -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<String, String> comparators = new HashMap<String, String>();
public static final Set<String> keywords = new HashSet<String>();
public static final Set<String> obsoleteKeywords = new HashSet<String>();
public static final Set<String> allowedKeywords = new HashSet<String>();
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<String, String> properties = new HashMap<String, String>();
public final Map<String, String> compactionStrategyOptions = new HashMap<String, String>();
public final Map<String, String> compressionParameters = new HashMap<String, String>();
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<String, String> propertyMap)
{
for (Map.Entry<String, String> 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());
}
}

View File

@ -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<ByteBuffer> variables) throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException;
}

View File

@ -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<ColumnIdentifier>
{
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);
}
}

View File

@ -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<ByteBuffer> 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();
}

View File

@ -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<String> recognitionErrors = new ArrayList<String>();
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<String> 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<Token> tokens = new ArrayList<Token>();
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<String> recognitionErrors = new ArrayList<String>();
public void displayRecognitionError(String[] tokenNames, RecognitionException e)
{
String hdr = getErrorHeader(e);
String msg = getErrorMessage(e, tokenNames);
recognitionErrors.add(hdr + " " + msg);
}
public List<String> 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 <KEYSPACE>;
*/
useStatement returns [UseStatement stmt]
: K_USE ks=keyspaceName { $stmt = new UseStatement(ks); }
;
/**
* SELECT <expression>
* FROM <CF>
* USING CONSISTENCY <LEVEL>
* WHERE KEY = "key1" AND COL > 1 AND COL < 100
* LIMIT <NUMBER>;
*/
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<ColumnIdentifier> expr]
: ids=cidentList { $expr = ids; }
| '\*' { $expr = Collections.<ColumnIdentifier>emptyList();}
;
whereClause returns [List<Relation> clause]
@init{ $clause = new ArrayList<Relation>(); }
: first=relation { $clause.add(first); } (K_AND next=relation { $clause.add(next); })*
;
/**
* INSERT INTO <CF> (<column>, <column>, <column>, ...)
* VALUES (<value>, <value>, <value>, ...)
* USING CONSISTENCY <level> AND TIMESTAMP <long>;
*
* Consistency level is set to ONE by default
*/
insertStatement returns [UpdateStatement expr]
@init {
Attributes attrs = new Attributes();
List<ColumnIdentifier> columnNames = new ArrayList<ColumnIdentifier>();
List<Term> columnValues = new ArrayList<Term>();
}
: 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 <CF>
* USING CONSISTENCY <level> AND TIMESTAMP <long>
* SET name1 = value1, name2 = value2
* WHERE key = value;
*/
updateStatement returns [UpdateStatement expr]
@init {
Attributes attrs = new Attributes();
Map<ColumnIdentifier, Operation> columns = new HashMap<ColumnIdentifier, Operation>();
}
: 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 <CF>
* USING CONSISTENCY <level> AND TIMESTAMP <long>
* WHERE KEY = keyname;
*/
deleteStatement returns [DeleteStatement expr]
@init {
Attributes attrs = new Attributes();
List<ColumnIdentifier> 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 <LVL>]
* UPDATE <CF> SET name1 = value1 WHERE KEY = keyname1;
* UPDATE <CF> SET name2 = value2 WHERE KEY = keyname2;
* UPDATE <CF> SET name3 = value3 WHERE KEY = keyname3;
* ...
* APPLY BATCH
*
* OR
*
* BEGIN BATCH [USING CONSISTENCY <LVL>]
* INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
* INSERT INTO <CF> (KEY, <name>) VALUES ('<key>', '<value>');
* ...
* APPLY BATCH
*
* OR
*
* BEGIN BATCH [USING CONSISTENCY <LVL>]
* DELETE name1, name2 FROM <CF> WHERE key = <key>
* DELETE name3, name4 FROM <CF> WHERE key = <key>
* ...
* APPLY BATCH
*/
batchStatement returns [BatchStatement expr]
@init {
Attributes attrs = new Attributes();
List<ModificationStatement> statements = new ArrayList<ModificationStatement>();
}
: 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 <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 <CF> (
* <name1> <type>,
* <name2> <type>,
* <name3> <type>
* ) WITH <property> = <value> 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 <CF> ALTER <column> TYPE <newtype>;
* ALTER COLUMN FAMILY <CF> ADD <column> <newtype>;
* ALTER COLUMN FAMILY <CF> DROP <column>;
* ALTER COLUMN FAMILY <CF> WITH <property> = <value>;
*/
alterTableStatement returns [AlterTableStatement expr]
@init {
AlterTableStatement.Type type = null;
String validator = null;
ColumnIdentifier columnName = null;
Map<String, String> 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 <KSP>;
*/
dropKeyspaceStatement returns [DropKeyspaceStatement ksp]
: K_DROP K_KEYSPACE ks=keyspaceName { $ksp = new DropKeyspaceStatement(ks); }
;
/**
* DROP COLUMNFAMILY <CF>;
*/
dropColumnFamilyStatement returns [DropColumnFamilyStatement stmt]
: K_DROP K_COLUMNFAMILY cf=columnFamilyName { $stmt = new DropColumnFamilyStatement(cf); }
;
/**
* DROP INDEX <INDEX_NAME>
*/
dropIndexStatement returns [DropIndexStatement expr]
:
K_DROP K_INDEX index=IDENT
{ $expr = new DropIndexStatement($index.text); }
;
/**
* TRUNCATE <CF>;
*/
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<ColumnIdentifier> items]
@init{ $items = new ArrayList<ColumnIdentifier>(); }
: 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<ColumnIdentifier, Operation> 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<String, String> props]
@init{ $props = new HashMap<String, String>(); }
: 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; }
;

View File

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

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> 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.<ByteBuffer>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<ByteBuffer> 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;
}
}

View File

@ -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, <key> > "start" or "colname1" = "somevalue".
*
*/
public class Relation
{
private final ColumnIdentifier entity;
private final Type relationType;
private final Term value;
private final List<Term> 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<Term> 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<Term>());
}
public Type operator()
{
return relationType;
}
public ColumnIdentifier getEntity()
{
return entity;
}
public Term getValue()
{
assert relationType != Type.IN;
return value;
}
public List<Term> 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);
}
}

View File

@ -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<ByteBuffer> 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;
}
}

View File

@ -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<String, String> 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<String, String>(cfProps.compactionStrategyOptions);
}
if (!cfProps.compressionParameters.isEmpty())
{
cfDef.compression_options = new HashMap<String, String>(cfProps.compressionParameters);
}
}
public String toString()
{
return String.format("AlterTableStatement(name=%s, type=%s, column=%s, validator=%s)",
cfName,
oType,
columnName,
validator);
}
}

View File

@ -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 <code>BATCH</code> statement parsed from a CQL query.
*
*/
public class BatchStatement extends ModificationStatement
{
// statements to execute
protected final List<ModificationStatement> 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<ModificationStatement> 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<String> cfamsSeen = new HashSet<String>();
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<IMutation> getMutations(ClientState clientState, List<ByteBuffer> variables)
throws InvalidRequestException
{
Map<Pair<String, ByteBuffer>, RowAndCounterMutation> mutations = new HashMap<Pair<String, ByteBuffer>, RowAndCounterMutation>();
for (ModificationStatement statement : statements)
{
if (isSetTimestamp())
statement.timestamp = timestamp;
List<IMutation> lm = statement.getMutations(clientState, variables);
// Group mutation together, otherwise they won't get applied atomically
for (IMutation m : lm)
{
Pair<String, ByteBuffer> 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<IMutation> batch = new LinkedList<IMutation>();
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;
}
}

View File

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

View File

@ -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 <code>CREATE COLUMNFAMILY</code> 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<ByteBuffer> columnAliases = new ArrayList<ByteBuffer>();
private ByteBuffer valueAlias;
private final Map<ColumnIdentifier, String> columns = new HashMap<ColumnIdentifier, String>();
private final CFPropDefs properties;
public CreateColumnFamilyStatement(CFName name, CFPropDefs properties)
{
super(name);
this.properties = properties;
}
// Column definitions
private Map<ByteBuffer, ColumnDefinition> getColumns() throws InvalidRequestException
{
Map<ByteBuffer, ColumnDefinition> columnDefs = new HashMap<ByteBuffer, ColumnDefinition>();
for (Map.Entry<ColumnIdentifier, String> 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
* <code>CREATE</code> 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<ColumnIdentifier, String> definitions = new HashMap<ColumnIdentifier, String>();
private final CFPropDefs properties = new CFPropDefs();
private final List<ColumnIdentifier> keyAliases = new ArrayList<ColumnIdentifier>();
private List<ColumnIdentifier> columnAliases = new ArrayList<ColumnIdentifier>();
private boolean useCompactStorage;
private Multiset<ColumnIdentifier> 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<ColumnIdentifier> 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<AbstractType<?>> types = new ArrayList<AbstractType<?>>();
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<ColumnIdentifier, String> 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<ColumnIdentifier, String> 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<ByteBuffer> variables)
{
throw new UnsupportedOperationException();
}
}
}

View File

@ -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 <code>CREATE INDEX</code> 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;
}
}
}

View File

@ -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 <code>CREATE KEYSPACE</code> statement parsed from a CQL query. */
public class CreateKeyspaceStatement extends SchemaAlteringStatement
{
private final String name;
private final Map<String, String> attrs;
private String strategyClass;
private Map<String, String> strategyOptions = new HashMap<String, String>();
/**
* Creates a new <code>CreateKeyspaceStatement</code> 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 <code>WITH</code> keyword.
*/
public CreateKeyspaceStatement(String name, Map<String, String> attrs)
{
super();
this.name = name;
this.attrs = attrs;
}
/**
* The <code>CqlParser</code> 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.<CfDef>emptyList());
ksd.setStrategy_options(strategyOptions);
ThriftValidation.validateKsDef(ksd);
ThriftValidation.validateKeyspaceNotYetExisting(name);
return new AddKeyspace(KSMetaData.fromThrift(ksd));
}
}

View File

@ -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 <code>DELETE</code> parsed from a CQL query statement.
*/
public class DeleteStatement extends ModificationStatement
{
private final List<ColumnIdentifier> columns;
private final List<Relation> whereClause;
private final Map<ColumnIdentifier, List<Term>> processedKeys = new HashMap<ColumnIdentifier, List<Term>>();
public DeleteStatement(CFName name, List<ColumnIdentifier> columns, List<Relation> whereClause, Attributes attrs)
{
super(name, attrs);
this.columns = columns;
this.whereClause = whereClause;
}
public List<IMutation> getMutations(ClientState clientState, List<ByteBuffer> variables) throws InvalidRequestException
{
clientState.hasColumnFamilyAccess(columnFamily(), Permission.WRITE);
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
CFDefinition cfDef = metadata.getCfDef();
preprocess(cfDef);
// Check key
List<Term> 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<Term> 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<IMutation> rowMutations = new ArrayList<IMutation>();
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<ByteBuffer> 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<ColumnIdentifier> 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);
}
}

View File

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

View File

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

View File

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

View File

@ -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<ByteBuffer> 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<IMutation> getMutations(ClientState clientState, List<ByteBuffer> variables) throws InvalidRequestException;
}

View File

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

View File

@ -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<ByteBuffer> 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<Object>()
{
public Object call() throws Exception
{
m.apply();
m.announce();
return null;
}
});
try
{
f.get();
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
catch (ExecutionException e)
{
// this means call() threw an exception. deal with it directly.
if (e.getCause() != null)
{
InvalidRequestException ex = new InvalidRequestException(e.getCause().getMessage());
ex.initCause(e.getCause());
throw ex;
}
else
{
InvalidRequestException ex = new InvalidRequestException(e.getMessage());
ex.initCause(e);
throw ex;
}
}
validateSchemaIsSettled();
}
private static Map<String, List<String>> describeSchemaVersions()
{
// unreachable hosts don't count towards disagreement
return Maps.filterKeys(StorageProxy.describeSchemaVersions(),
Predicates.not(Predicates.equalTo(StorageProxy.UNREACHABLE)));
}
private static void validateSchemaIsSettled() throws SchemaDisagreementException
{
long limit = System.currentTimeMillis() + timeLimitForSchemaAgreement;
outer:
while (limit - System.currentTimeMillis() >= 0)
{
String currentVersionId = Schema.instance.getVersion().toString();
for (String version : describeSchemaVersions().keySet())
{
if (!version.equals(currentVersionId))
continue outer;
}
// schemas agree
return;
}
throw new SchemaDisagreementException();
}
}

View File

@ -0,0 +1,972 @@
/*
* 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.*;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.IColumn;
import org.apache.cassandra.db.CounterColumn;
import org.apache.cassandra.db.RangeSliceCommand;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.Row;
import org.apache.cassandra.db.RowPosition;
import org.apache.cassandra.db.SliceByNamesReadCommand;
import org.apache.cassandra.db.SliceFromReadCommand;
import org.apache.cassandra.db.Table;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.TypeParser;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.ExcludingBounds;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.IncludingExcludingBounds;
import org.apache.cassandra.dht.RandomPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.CqlMetadata;
import org.apache.cassandra.thrift.CqlResult;
import org.apache.cassandra.thrift.CqlResultType;
import org.apache.cassandra.thrift.CqlRow;
import org.apache.cassandra.thrift.IndexExpression;
import org.apache.cassandra.thrift.IndexOperator;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.RequestType;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.apache.cassandra.thrift.ThriftValidation;
import org.apache.cassandra.thrift.TimedOutException;
import org.apache.cassandra.thrift.UnavailableException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
/**
* Encapsulates a completely parsed SELECT query, including the target
* column family, expression, result count, and ordering clause.
*
*/
public class SelectStatement extends CQLStatement
{
private static final Logger logger = LoggerFactory.getLogger(SelectStatement.class);
private final static ByteBuffer countColumn = ByteBufferUtil.bytes("count");
public final CFDefinition cfDef;
public final Parameters parameters;
private final List<Pair<CFDefinition.Name, ColumnIdentifier>> selectedNames = new ArrayList<Pair<CFDefinition.Name, ColumnIdentifier>>(); // empty => wildcard
private final Map<ColumnIdentifier, Restriction> restrictions = new HashMap<ColumnIdentifier, Restriction>();
private boolean hasIndexedExpression;
public SelectStatement(CFDefinition cfDef, Parameters parameters)
{
this.cfDef = cfDef;
this.parameters = parameters;
}
public void checkAccess(ClientState state) throws InvalidRequestException
{
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.READ);
}
public CqlResult execute(ClientState state, List<ByteBuffer> variables) throws InvalidRequestException, UnavailableException, TimedOutException
{
List<Row> rows;
if (isKeyRange())
{
rows = multiRangeSlice(variables);
}
else
{
rows = getSlice(variables);
}
CqlResult result = new CqlResult();
result.type = CqlResultType.ROWS;
// count resultset is a single column named "count"
if (parameters.isCount)
{
result.schema = new CqlMetadata(Collections.<ByteBuffer, String>emptyMap(),
Collections.<ByteBuffer, String>emptyMap(),
"AsciiType",
"LongType");
List<Column> columns = Collections.singletonList(new Column(countColumn).setValue(ByteBufferUtil.bytes((long) rows.size())));
result.rows = Collections.singletonList(new CqlRow(countColumn, columns));
return result;
}
else
{
// otherwise create resultset from query results
result.schema = new CqlMetadata(new HashMap<ByteBuffer, String>(),
new HashMap<ByteBuffer, String>(),
TypeParser.getShortName(cfDef.cfm.comparator),
TypeParser.getShortName(cfDef.cfm.getDefaultValidator()));
result.rows = process(rows, result.schema);
return result;
}
}
public String keyspace()
{
return cfDef.cfm.ksName;
}
public String columnFamily()
{
return cfDef.cfm.cfName;
}
private List<Row> getSlice(List<ByteBuffer> variables) throws InvalidRequestException, TimedOutException, UnavailableException
{
QueryPath queryPath = new QueryPath(columnFamily());
List<ReadCommand> commands = new ArrayList<ReadCommand>();
// ...a range (slice) of column names
if (isColumnRange())
{
ByteBuffer start =getRequestedBound(true, variables);
ByteBuffer finish = getRequestedBound(false, variables);
// Note that we use the total limit for every key. This is
// potentially inefficient, but then again, IN + LIMIT is not a
// very sensible choice
for (ByteBuffer key : getKeys(variables))
{
QueryProcessor.validateKey(key);
QueryProcessor.validateSliceRange(cfDef.cfm, start, finish, parameters.isColumnsReversed);
commands.add(new SliceFromReadCommand(keyspace(),
key,
queryPath,
start,
finish,
parameters.isColumnsReversed,
getLimit()));
}
}
// ...of a list of column names
else
{
Collection<ByteBuffer> columnNames = getRequestedColumns(variables);
QueryProcessor.validateColumnNames(columnNames);
for (ByteBuffer key: getKeys(variables))
{
QueryProcessor.validateKey(key);
commands.add(new SliceByNamesReadCommand(keyspace(), key, queryPath, columnNames));
}
}
try
{
return StorageProxy.read(commands, parameters.consistencyLevel);
}
catch (TimeoutException e)
{
throw new TimedOutException();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
private List<Row> multiRangeSlice(List<ByteBuffer> variables) throws InvalidRequestException, TimedOutException, UnavailableException
{
List<Row> rows;
IPartitioner<?> p = StorageService.getPartitioner();
ByteBuffer startKeyBytes = getKeyStart(variables);
ByteBuffer finishKeyBytes = getKeyFinish(variables);
RowPosition startKey = RowPosition.forKey(startKeyBytes, p);
RowPosition finishKey = RowPosition.forKey(finishKeyBytes, p);
if (startKey.compareTo(finishKey) > 0 && !finishKey.isMinimum(p))
{
if (p instanceof RandomPartitioner)
throw new InvalidRequestException("Start key sorts after end key. This is not allowed; you probably should not specify end key at all, under RandomPartitioner");
else
throw new InvalidRequestException("Start key must sort before (or equal to) finish key in your partitioner!");
}
AbstractBounds<RowPosition> bounds;
if (includeStartKey())
{
bounds = includeFinishKey()
? new Bounds<RowPosition>(startKey, finishKey)
: new IncludingExcludingBounds<RowPosition>(startKey, finishKey);
}
else
{
bounds = includeFinishKey()
? new Range<RowPosition>(startKey, finishKey)
: new ExcludingBounds<RowPosition>(startKey, finishKey);
}
// XXX: Our use of Thrift structs internally makes me Sad. :(
SlicePredicate thriftSlicePredicate = makeSlicePredicate(variables);
QueryProcessor.validateSlicePredicate(cfDef.cfm, thriftSlicePredicate);
List<IndexExpression> expressions = getIndexExpressions(variables);
try
{
rows = StorageProxy.getRangeSlice(new RangeSliceCommand(keyspace(),
columnFamily(),
null,
thriftSlicePredicate,
bounds,
expressions,
getLimit(),
true), // limit by columns, not keys
parameters.consistencyLevel);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
catch (TimeoutException e)
{
throw new TimedOutException();
}
return rows;
}
private SlicePredicate makeSlicePredicate(List<ByteBuffer> variables)
throws InvalidRequestException
{
SlicePredicate thriftSlicePredicate = new SlicePredicate();
if (isColumnRange())
{
SliceRange sliceRange = new SliceRange();
sliceRange.start = getRequestedBound(true, variables);
sliceRange.finish = getRequestedBound(false, variables);
sliceRange.reversed = parameters.isColumnsReversed;
sliceRange.count = -1; // We use this for range slices, where the count is ignored in favor of the global column count
thriftSlicePredicate.slice_range = sliceRange;
}
else
{
thriftSlicePredicate.column_names = getRequestedColumns(variables);
}
return thriftSlicePredicate;
}
private int getLimit()
{
// For sparse, we'll end up merging all defined colums into the same CqlRow. Thus we should query up
// to 'defined columns' * 'asked limit' to be sure to have enough columns. We'll trim after query if
// this end being too much.
return cfDef.isCompact ? parameters.limit : cfDef.metadata.size() * parameters.limit;
}
private boolean isKeyRange()
{
if (hasIndexedExpression)
return true;
Restriction r = restrictions.get(cfDef.key.name);
return r == null || !r.isEquality();
}
private Collection<ByteBuffer> getKeys(final List<ByteBuffer> variables) throws InvalidRequestException
{
final Restriction r = restrictions.get(cfDef.key.name);
if (r == null || !r.isEquality())
throw new IllegalStateException();
List<ByteBuffer> keys = new ArrayList<ByteBuffer>(r.eqValues.size());
for (Term t : r.eqValues)
keys.add(t.getByteBuffer(cfDef.key.type, variables));
return keys;
}
private ByteBuffer getKeyStart(List<ByteBuffer> variables) throws InvalidRequestException
{
Restriction r = restrictions.get(cfDef.key.name);
if (r == null)
{
return null;
}
else if (r.isEquality())
{
assert r.eqValues.size() == 1;
return r.eqValues.get(0).getByteBuffer(cfDef.key.type, variables);
}
else
{
return r.start == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : r.start.getByteBuffer(cfDef.key.type, variables);
}
}
private boolean includeStartKey()
{
Restriction r = restrictions.get(cfDef.key.name);
if (r == null || r.isEquality())
return true;
else
return r.startInclusive;
}
private ByteBuffer getKeyFinish(List<ByteBuffer> variables) throws InvalidRequestException
{
Restriction r = restrictions.get(cfDef.key.name);
if (r == null)
{
return null;
}
else if (r.isEquality())
{
assert r.eqValues.size() == 1;
return r.eqValues.get(0).getByteBuffer(cfDef.key.type, variables);
}
else
{
return r.end == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : r.end.getByteBuffer(cfDef.key.type, variables);
}
}
private boolean includeFinishKey()
{
Restriction r = restrictions.get(cfDef.key.name);
if (r == null || r.isEquality())
return true;
else
return r.endInclusive;
}
private boolean isColumnRange()
{
// Static CF never entails a column slice
if (!cfDef.isCompact && !cfDef.isComposite)
return false;
// Otherwise, it is a range query if it has at least one the column alias
// for which no relation is defined or is not EQ.
for (CFDefinition.Name name : cfDef.columns.values())
{
Restriction r = restrictions.get(name.name);
if (r == null || !r.isEquality())
return true;
}
return false;
}
private boolean isWildcard()
{
return selectedNames.isEmpty();
}
private List<ByteBuffer> getRequestedColumns(List<ByteBuffer> variables) throws InvalidRequestException
{
assert !isColumnRange();
ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
for (CFDefinition.Name name : cfDef.columns.values())
{
Restriction r = restrictions.get(name.name);
assert r != null && r.isEquality() && r.eqValues.size() == 1;
builder.add(r.eqValues.get(0), Relation.Type.EQ, variables);
}
if (cfDef.isCompact)
{
return Collections.singletonList(builder.build());
}
else
{
List<ByteBuffer> columns = new ArrayList<ByteBuffer>();
// Adds all (requested) columns
Iterator<Pair<CFDefinition.Name, ColumnIdentifier>> iter = getExpandedSelection().iterator();
while (iter.hasNext())
{
CFDefinition.Name name = iter.next().left;
// Skip everything that is not a 'metadata' column
if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA)
continue;
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
ByteBuffer cname = b.add(name.name.key).build();
columns.add(cname);
}
return columns;
}
}
private ByteBuffer getRequestedBound(boolean isStart, List<ByteBuffer> variables) throws InvalidRequestException
{
assert isColumnRange();
ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
for (CFDefinition.Name name : cfDef.columns.values())
{
Restriction r = restrictions.get(name.name);
if (r == null)
{
// There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix.
// For composites, if there was preceding component and we're computing the end, we must change the last component
// End-Of-Component, otherwise we would be selecting only one record.
if (builder.componentCount() > 0 && !isStart)
return builder.buildAsEndOfRange();
else
return builder.build();
}
if (r.isEquality())
{
assert r.eqValues.size() == 1;
builder.add(r.eqValues.get(0), Relation.Type.EQ, variables);
}
else
{
Term t = isStart ? r.start : r.end;
Relation.Type op = isStart
? (r.startInclusive ? Relation.Type.GTE : Relation.Type.GT)
: (r.endInclusive ? Relation.Type.LTE : Relation.Type.LT);
if (t == null)
return ByteBufferUtil.EMPTY_BYTE_BUFFER;
else
return builder.add(t, op, variables).build();
}
}
// Means no relation at all or everything was an equal
return builder.build();
}
private List<IndexExpression> getIndexExpressions(List<ByteBuffer> variables) throws InvalidRequestException
{
if (!hasIndexedExpression)
return Collections.<IndexExpression>emptyList();
List<IndexExpression> expressions = new ArrayList<IndexExpression>();
for (CFDefinition.Name name : cfDef.metadata.values())
{
Restriction restriction = restrictions.get(name.name);
if (restriction == null)
continue;
if (restriction.isEquality())
{
for (Term t : restriction.eqValues)
{
ByteBuffer value = t.getByteBuffer(name.type, variables);
expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
}
}
else
{
if (restriction.start != null)
{
ByteBuffer value = restriction.start.getByteBuffer(name.type, variables);
expressions.add(new IndexExpression(name.name.key, restriction.startInclusive ? IndexOperator.GTE : IndexOperator.GT, value));
}
if (restriction.end != null)
{
ByteBuffer value = restriction.end.getByteBuffer(name.type, variables);
expressions.add(new IndexExpression(name.name.key, restriction.endInclusive ? IndexOperator.GTE : IndexOperator.GT, value));
}
}
}
return expressions;
}
private List<Pair<CFDefinition.Name, ColumnIdentifier>> getExpandedSelection()
{
if (selectedNames.isEmpty())
{
List<Pair<CFDefinition.Name, ColumnIdentifier>> selection = new ArrayList<Pair<CFDefinition.Name, ColumnIdentifier>>();
for (CFDefinition.Name name : cfDef)
selection.add(Pair.create(name, name.name));
return selection;
}
else
{
return selectedNames;
}
}
private ByteBuffer value(IColumn c)
{
return (c instanceof CounterColumn)
? ByteBufferUtil.bytes(CounterContext.instance().total(c.value()))
: c.value();
}
private void addToSchema(CqlMetadata schema, Pair<CFDefinition.Name, ColumnIdentifier> p)
{
ByteBuffer nameAsRequested = p.right.key;
schema.name_types.put(nameAsRequested, TypeParser.getShortName(cfDef.cfm.comparator));
schema.value_types.put(nameAsRequested, TypeParser.getShortName(p.left.type));
}
private List<CqlRow> process(List<Row> rows, CqlMetadata schema)
{
List<CqlRow> cqlRows = new ArrayList<CqlRow>();
List<Pair<CFDefinition.Name, ColumnIdentifier>> selection = getExpandedSelection();
List<Column> thriftColumns = null;
for (org.apache.cassandra.db.Row row : rows)
{
if (cfDef.isCompact)
{
// One cqlRow per column
if (row.cf == null)
continue;
for (IColumn c : row.cf.getSortedColumns())
{
if (c.isMarkedForDelete())
continue;
thriftColumns = new ArrayList<Column>();
ByteBuffer[] components = cfDef.isComposite
? ((CompositeType)cfDef.cfm.comparator).split(c.name())
: null;
// Respect selection order
for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection)
{
CFDefinition.Name name = p.left;
ByteBuffer nameAsRequested = p.right.key;
addToSchema(schema, p);
Column col = new Column(nameAsRequested);
switch (name.kind)
{
case KEY_ALIAS:
col.setValue(row.key.key).setTimestamp(-1L);
break;
case COLUMN_ALIAS:
col.setTimestamp(c.timestamp());
if (cfDef.isComposite)
{
if (name.compositePosition < components.length)
col.setValue(components[name.compositePosition]);
else
col.setValue(ByteBufferUtil.EMPTY_BYTE_BUFFER);
}
else
{
col.setValue(c.name());
}
break;
case VALUE_ALIAS:
col.setValue(value(c)).setTimestamp(c.timestamp());
break;
case COLUMN_METADATA:
// This should not happen for compact CF
throw new AssertionError();
}
thriftColumns.add(col);
}
cqlRows.add(new CqlRow(row.key.key, thriftColumns));
}
}
else if (cfDef.isComposite)
{
// Sparse case: group column in cqlRow when composite prefix is equal
if (row.cf == null)
continue;
CompositeType composite = (CompositeType)cfDef.cfm.comparator;
int last = composite.types.size() - 1;
ByteBuffer[] previous = null;
Map<ByteBuffer, IColumn> group = new HashMap<ByteBuffer, IColumn>();
for (IColumn c : row.cf)
{
if (c.isMarkedForDelete())
continue;
ByteBuffer[] current = composite.split(c.name());
// If current differs from previous, we've just finished a group
if (previous != null && !isSameRow(previous, current))
{
cqlRows.add(handleGroup(selection, row.key.key, previous, group, schema));
group = new HashMap<ByteBuffer, IColumn>();
}
// Accumulate the current column
group.put(current[last], c);
previous = current;
}
// Handle the last group
if (previous != null)
cqlRows.add(handleGroup(selection, row.key.key, previous, group, schema));
}
else
{
// Static case: One cqlRow for all columns
thriftColumns = new ArrayList<Column>();
// Respect selection order
for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection)
{
CFDefinition.Name name = p.left;
ByteBuffer nameAsRequested = p.right.key;
if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS)
{
addToSchema(schema, p);
thriftColumns.add(new Column(nameAsRequested).setValue(row.key.key).setTimestamp(-1L));
continue;
}
if (row.cf == null)
continue;
addToSchema(schema, p);
IColumn c = row.cf.getColumn(name.name.key);
Column col = new Column(name.name.key);
if (c != null && !c.isMarkedForDelete())
col.setValue(value(c)).setTimestamp(c.timestamp());
thriftColumns.add(col);
}
cqlRows.add(new CqlRow(row.key.key, thriftColumns));
}
}
// We don't allow reversed on range scan, but we do on multiget (IN (...)), so let's reverse the rows there too.
if (parameters.isColumnsReversed)
Collections.reverse(cqlRows);
cqlRows = cqlRows.size() > parameters.limit ? cqlRows.subList(0, parameters.limit) : cqlRows;
// Trim result if needed to respect the limit
return cqlRows;
}
/**
* For sparse composite, returns wheter two columns belong to the same
* cqlRow base on the full list of component in the name.
* Two columns do belong together if they differ only by the last
* component.
*/
private static boolean isSameRow(ByteBuffer[] c1, ByteBuffer[] c2)
{
// Cql don't allow to insert columns who doesn't have all component of
// the composite set for sparse composite. Someone coming from thrift
// could hit that though. But since we have no way to handle this
// correctly, better fail here and tell whomever may hit that (if
// someone ever do) to change the definition to a dense composite
assert c1.length == c2.length : "Sparse composite should not have partial column names";
for (int i = 0; i < c1.length - 1; i++)
{
if (!c1[i].equals(c2[i]))
return false;
}
return true;
}
private CqlRow handleGroup(List<Pair<CFDefinition.Name, ColumnIdentifier>> selection, ByteBuffer key, ByteBuffer[] components, Map<ByteBuffer, IColumn> columns, CqlMetadata schema)
{
List<Column> thriftColumns = new ArrayList<Column>();
// Respect requested order
for (Pair<CFDefinition.Name, ColumnIdentifier> p : selection)
{
CFDefinition.Name name = p.left;
ByteBuffer nameAsRequested = p.right.key;
addToSchema(schema, p);
Column col = new Column(nameAsRequested);
switch (name.kind)
{
case KEY_ALIAS:
col.setValue(key).setTimestamp(-1L);
break;
case COLUMN_ALIAS:
col.setValue(components[name.compositePosition]);
col.setTimestamp(-1L);
break;
case VALUE_ALIAS:
// This should not happen for SPARSE
throw new AssertionError();
case COLUMN_METADATA:
IColumn c = columns.get(name.name.key);
if (c != null && !c.isMarkedForDelete())
col.setValue(value(c)).setTimestamp(c.timestamp());
break;
}
thriftColumns.add(col);
}
return new CqlRow(key, thriftColumns);
}
public static class RawStatement extends CFStatement implements Preprocessable
{
private final Parameters parameters;
private final List<ColumnIdentifier> selectClause;
private final List<Relation> whereClause;
public RawStatement(CFName cfName, Parameters parameters, List<ColumnIdentifier> selectClause, List<Relation> whereClause)
{
super(cfName);
this.parameters = parameters;
this.selectClause = selectClause;
this.whereClause = whereClause == null ? Collections.<Relation>emptyList() : whereClause;
}
public SelectStatement preprocess() throws InvalidRequestException
{
CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
ThriftValidation.validateConsistencyLevel(keyspace(), parameters.consistencyLevel, RequestType.READ);
if (parameters.limit <= 0)
throw new InvalidRequestException("LIMIT must be strictly positive");
CFDefinition cfDef = cfm.getCfDef();
SelectStatement stmt = new SelectStatement(cfDef, parameters);
stmt.setBoundTerms(getBoundsTerms());
// Select clause
if (parameters.isCount)
{
if (selectClause.size() != 1 || (!selectClause.get(0).equals("*") && !selectClause.get(0).equals("1")))
throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported.");
}
else
{
for (ColumnIdentifier t : selectClause)
{
CFDefinition.Name name = cfDef.get(t);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", t));
// Keeping the case (as in 'case sensitive') of the input name for the resultSet
stmt.selectedNames.add(Pair.create(name, t));
}
}
/*
* WHERE clause. For a given entity, rules are:
* - EQ relation conflicts with anything else (including a 2nd EQ)
* - Can't have more than one LT(E) relation (resp. GT(E) relation)
* - IN relation are restricted to row keys (for now) and conflics with anything else
* (we could allow two IN for the same entity but that doesn't seem very useful)
* - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value in CQL so far)
*/
for (Relation rel : whereClause)
{
CFDefinition.Name name = cfDef.get(rel.getEntity());
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in where clause ('%s')", rel.getEntity(), rel));
if (name.kind == CFDefinition.Name.Kind.VALUE_ALIAS)
throw new InvalidRequestException(String.format("Restricting the value of a compact CF (%s) is not supported", name.name));
Restriction restriction = stmt.restrictions.get(name.name);
switch (rel.operator())
{
case EQ:
if (restriction != null)
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one relation if it includes an Equal", name));
stmt.restrictions.put(name.name, new Restriction(Collections.singletonList(rel.getValue())));
break;
case GT:
case GTE:
if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && !StorageService.getPartitioner().preservesOrder())
throw new InvalidRequestException("Only EQ and IN relation are supported on first component of the PRIMARY KEY for RandomPartitioner");
if (restriction == null)
{
restriction = new Restriction();
stmt.restrictions.put(name.name, restriction);
}
if (restriction.start != null)
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one Greater-Than relation", name));
restriction.start = rel.getValue();
if (rel.operator() == Relation.Type.GTE)
restriction.startInclusive = true;
break;
case LT:
case LTE:
if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && !StorageService.getPartitioner().preservesOrder())
throw new InvalidRequestException("Only EQ and IN relation are supported on first component of the PRIMARY KEY for RandomPartitioner");
if (restriction == null)
{
restriction = new Restriction();
stmt.restrictions.put(name.name, restriction);
}
if (restriction.end != null)
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one Lesser-Than relation", name));
restriction.end = rel.getValue();
if (rel.operator() == Relation.Type.LTE)
restriction.endInclusive = true;
break;
case IN:
if (restriction != null)
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one reation if it includes a IN", name));
if (name.kind != CFDefinition.Name.Kind.KEY_ALIAS)
throw new InvalidRequestException("IN relation can only be applied to the first component of the PRIMARY KEY");
stmt.restrictions.put(name.name, new Restriction(rel.getInValues()));
break;
}
}
/*
* At this point, the select statement if fully constructed, but we still have a few things to validate
*/
// If a component of the PRIMARY KEY is restricted by a non-EQ relation, all preceding
// components must have a EQ, and all following must have no restriction
boolean shouldBeDone = false;
CFDefinition.Name previous = null;
for (CFDefinition.Name cname : cfDef.columns.values())
{
Restriction restriction = stmt.restrictions.get(cname.name);
if (restriction == null)
shouldBeDone = true;
else if (shouldBeDone)
throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted (preceding part %s is either not restricted or by a non-EQ relation)", cname, previous));
else if (!restriction.isEquality())
shouldBeDone = true;
// We could support IN for the last name, we don't yet
else if (restriction.eqValues.size() > 1)
throw new InvalidRequestException(String.format("PRIMARY KEY part %s cannot be restricted by IN relation", cname));
previous = cname;
}
// Deal with indexed columns
if (!cfDef.metadata.values().isEmpty())
{
boolean hasEq = false;
Set<ByteBuffer> indexed = Table.open(keyspace()).getColumnFamilyStore(columnFamily()).indexManager.getIndexedColumns();
for (CFDefinition.Name name : cfDef.metadata.values())
{
Restriction restriction = stmt.restrictions.get(name.name);
if (restriction == null)
continue;
stmt.hasIndexedExpression = true;
if (restriction.isEquality() && indexed.contains(name.name.key))
{
hasEq = true;
break;
}
}
if (stmt.hasIndexedExpression && !hasEq)
throw new InvalidRequestException("No indexed columns present in by-columns clause with Equal operator");
// If we have indexed columns and the key = X clause, we transform it into a key >= X AND key <= X clause.
// If it's a IN relation however, we reject it.
Restriction r = stmt.restrictions.get(cfDef.key.name);
if (r != null && r.isEquality())
{
if (r.eqValues.size() > 1)
throw new InvalidRequestException("Select on indexed columns and with IN clause for the PRIMARY KEY are not supported");
r.start = r.eqValues.get(0);
r.startInclusive = true;
r.end = r.eqValues.get(0);
r.endInclusive = true;
r.eqValues = null;
}
}
// Only allow reversed if the row key restriction is an equality,
// since we don't know how to reverse otherwise
if (stmt.parameters.isColumnsReversed)
{
Restriction r = stmt.restrictions.get(cfDef.key.name);
if (r == null || !r.isEquality())
throw new InvalidRequestException("Descending order is only supported is the first part of the PRIMARY KEY is restricted by an Equal or a IN");
}
return stmt;
}
@Override
public String toString()
{
return String.format("SelectRawStatement[name=%s, selectClause=%s, whereClause=%s, isCount=%s, cLevel=%s, limit=%s]",
cfName,
selectClause,
whereClause,
parameters.isCount,
parameters.consistencyLevel,
parameters.limit);
}
public void checkAccess(ClientState state)
{
throw new UnsupportedOperationException();
}
public CqlResult execute(ClientState state, List<ByteBuffer> variables)
{
throw new UnsupportedOperationException();
}
}
// A rather raw class that simplify validation and query for select
// Don't made public as this can be easily badly used
private static class Restriction
{
// for equality
List<Term> eqValues; // if null, it's a restriction by bounds
Restriction(List<Term> values)
{
this.eqValues = values;
}
// for bounds
Term start;
boolean startInclusive;
Term end;
boolean endInclusive;
Restriction()
{
this(null);
}
boolean isEquality()
{
return eqValues != null;
}
}
public static class Parameters
{
private final int limit;
private final ConsistencyLevel consistencyLevel;
private final boolean isColumnsReversed;
private final boolean isCount;
public Parameters(ConsistencyLevel consistency, int limit, boolean reversed, boolean isCount)
{
this.consistencyLevel = consistency;
this.limit = limit;
this.isColumnsReversed = reversed;
this.isCount = isCount;
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.concurrent.TimeoutException;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.*;
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.ThriftValidation;
import org.apache.cassandra.thrift.UnavailableException;
public class TruncateStatement extends CFStatement
{
public TruncateStatement(CFName name)
{
super(name);
}
public void checkAccess(ClientState state) throws InvalidRequestException
{
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.WRITE);
}
public void validate(ClientState state) throws InvalidRequestException
{
ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
}
public CqlResult execute(ClientState state, List<ByteBuffer> variables) throws InvalidRequestException, UnavailableException
{
try
{
StorageProxy.truncateBlocking(keyspace(), columnFamily());
}
catch (TimeoutException e)
{
throw (UnavailableException) new UnavailableException().initCause(e);
}
catch (IOException e)
{
throw (UnavailableException) new UnavailableException().initCause(e);
}
return null;
}
}

View File

@ -0,0 +1,359 @@
/*
* 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.*;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.CounterMutation;
import org.apache.cassandra.db.IMutation;
import org.apache.cassandra.db.RowMutation;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql.QueryProcessor.validateColumnName;
import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;
import static org.apache.cassandra.thrift.ThriftValidation.validateCommutativeForWrite;
/**
* An <code>UPDATE</code> statement parsed from a CQL query statement.
*
*/
public class UpdateStatement extends ModificationStatement
{
private final Map<ColumnIdentifier, Operation> columns;
private final List<ColumnIdentifier> columnNames;
private final List<Term> columnValues;
private final List<Relation> whereClause;
private final Map<ColumnIdentifier, Operation> processedColumns = new HashMap<ColumnIdentifier, Operation>();
private final Map<ColumnIdentifier, List<Term>> processedKeys = new HashMap<ColumnIdentifier, List<Term>>();
/**
* Creates a new UpdateStatement from a column family name, columns map, consistency
* level, and key term.
*
* @param keyspace Keyspace (optional)
* @param columnFamily column family name
* @param columns a map of column name/values pairs
* @param whereClause the where clause
* @param attrs additional attributes for statement (CL, timestamp, timeToLive)
*/
public UpdateStatement(CFName name,
Map<ColumnIdentifier, Operation> columns,
List<Relation> whereClause,
Attributes attrs)
{
super(name, attrs);
this.columns = columns;
this.whereClause = whereClause;
this.columnNames = null;
this.columnValues = null;
}
/**
* Creates a new UpdateStatement from a column family name, a consistency level,
* key, and lists of column names and values. It is intended for use with the
* alternate update format, <code>INSERT</code>.
*
* @param keyspace Keyspace (optional)
* @param columnFamily column family name
* @param columnNames list of column names
* @param columnValues list of column values (corresponds to names)
* @param attrs additional attributes for statement (CL, timestamp, timeToLive)
*/
public UpdateStatement(CFName name,
List<ColumnIdentifier> columnNames,
List<Term> columnValues,
Attributes attrs)
{
super(name, attrs);
this.columnNames = columnNames;
this.columnValues = columnValues;
this.whereClause = null;
this.columns = null;
}
/** {@inheritDoc} */
public List<IMutation> getMutations(ClientState clientState, List<ByteBuffer> variables) throws InvalidRequestException
{
boolean hasCommutativeOperation = false;
if (columns != null)
{
for (Map.Entry<ColumnIdentifier, Operation> column : columns.entrySet())
{
if (!column.getValue().isUnary())
hasCommutativeOperation = true;
if (hasCommutativeOperation && column.getValue().isUnary())
throw new InvalidRequestException("Mix of commutative and non-commutative operations is not allowed.");
}
}
// Deal here with the keyspace overwrite thingy to avoid mistake
CFMetaData metadata = validateColumnFamily(keyspace(), columnFamily(), hasCommutativeOperation);
if (hasCommutativeOperation)
validateCommutativeForWrite(metadata, cLevel);
CFDefinition cfDef = metadata.getCfDef();
preprocess(cfDef);
// Check key
List<Term> keys = processedKeys.get(cfDef.key.name);
if (keys == null || keys.isEmpty())
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", cfDef.key));
ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
CFDefinition.Name firstEmpty = null;
for (CFDefinition.Name name : cfDef.columns.values())
{
List<Term> values = processedKeys.get(name.name);
if (values == null || values.isEmpty())
{
firstEmpty = name;
// For sparse, we must have all components
if (cfDef.isComposite && !cfDef.isCompact)
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, name.name));
}
else
{
assert values.size() == 1; // We only allow IN for row keys so far
builder.add(values.get(0), Relation.Type.EQ, variables);
}
}
List<IMutation> rowMutations = new LinkedList<IMutation>();
for (Term key: keys)
{
ByteBuffer rawKey = key.getByteBuffer(cfDef.key.type, variables);
rowMutations.add(mutationForKey(cfDef, clientState, rawKey, builder, variables));
}
return rowMutations;
}
/**
* Compute a row mutation for a single key
*
*
* @param keyspace working keyspace
* @param key key to change
* @param metadata information about CF
*
* @param clientState
* @return row mutation
*
* @throws InvalidRequestException on the wrong request
*/
private IMutation mutationForKey(CFDefinition cfDef, ClientState clientState, ByteBuffer key, ColumnNameBuilder builder, List<ByteBuffer> variables)
throws InvalidRequestException
{
// if true we need to wrap RowMutation into CounterMutation
boolean hasCounterColumn = false;
RowMutation rm = new RowMutation(cfDef.cfm.ksName, key);
if (cfDef.isCompact)
{
if (builder.componentCount() == 0)
throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfDef.columns.values().iterator().next()));
Operation value = processedColumns.get(cfDef.value.name);
if (value == null)
throw new InvalidRequestException(String.format("Missing mandatory column %s", cfDef.value));
hasCounterColumn = addToMutation(clientState, rm, builder.build(), cfDef.value, value, variables);
}
else
{
for (CFDefinition.Name name : cfDef.metadata.values())
{
Operation value = processedColumns.get(name.name);
if (value == null)
continue;
ByteBuffer colName = builder.copy().add(name.name.key).build();
hasCounterColumn |= addToMutation(clientState, rm, colName, name, value, variables);
}
}
return (hasCounterColumn) ? new CounterMutation(rm, getConsistencyLevel()) : rm;
}
private boolean addToMutation(ClientState clientState,
RowMutation rm,
ByteBuffer colName,
CFDefinition.Name valueDef,
Operation value,
List<ByteBuffer> variables) throws InvalidRequestException
{
if (value.isUnary())
{
ByteBuffer valueBytes = value.value.getByteBuffer(valueDef.type, variables);
validateColumnName(colName);
rm.add(new QueryPath(columnFamily(), null, colName),
valueBytes,
getTimestamp(clientState),
getTimeToLive());
return false;
}
else
{
if (!valueDef.name.equals(value.ident))
throw new InvalidRequestException("Only expressions like X = X + <long> are supported.");
long val;
try
{
val = ByteBufferUtil.toLong(value.value.getByteBuffer(LongType.instance, variables));
}
catch (NumberFormatException e)
{
throw new InvalidRequestException(String.format("'%s' is an invalid value, should be a long.",
value.value.getText()));
}
if (value.type == Operation.Type.MINUS)
{
if (val == Long.MIN_VALUE)
throw new InvalidRequestException("The negation of " + val + " overflows supported integer precision (signed 8 bytes integer)");
else
val = -val;
}
rm.addCounter(new QueryPath(columnFamily(), null, colName), val);
return true;
}
}
private void preprocess(CFDefinition cfDef) throws InvalidRequestException
{
if (columns == null)
{
// Created from an INSERT
// Don't hate, validate.
if (columnNames.size() != columnValues.size())
throw new InvalidRequestException("unmatched column names/values");
if (columnNames.size() < 1)
throw new InvalidRequestException("no columns specified for INSERT");
for (int i = 0; i < columnNames.size(); i++)
{
CFDefinition.Name name = cfDef.get(columnNames.get(i));
if (name == null)
throw new InvalidRequestException(String.format("Unknown identifier %s", columnNames.get(i)));
switch (name.kind)
{
case KEY_ALIAS:
case COLUMN_ALIAS:
if (processedKeys.containsKey(name.name))
throw new InvalidRequestException(String.format("Multiple definition found for PRIMARY KEY part %s", name));
processedKeys.put(name.name, Collections.singletonList(columnValues.get(i)));
break;
case VALUE_ALIAS:
case COLUMN_METADATA:
if (processedColumns.containsKey(name.name))
throw new InvalidRequestException(String.format("Multiple definition found for column %s", name));
processedColumns.put(name.name, new Operation(columnValues.get(i)));
break;
}
}
}
else
{
// Created from an UPDATE
for (Map.Entry<ColumnIdentifier, Operation> entry : columns.entrySet())
{
CFDefinition.Name name = cfDef.get(entry.getKey());
if (name == null)
throw new InvalidRequestException(String.format("Unknown identifier %s", entry.getKey()));
switch (name.kind)
{
case KEY_ALIAS:
case COLUMN_ALIAS:
throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", entry.getKey()));
case VALUE_ALIAS:
case COLUMN_METADATA:
if (processedColumns.containsKey(name.name))
throw new InvalidRequestException(String.format("Multiple definition found for column %s", name));
processedColumns.put(name.name, entry.getValue());
break;
}
}
processKeys(cfDef, whereClause, processedKeys);
}
}
// Reused by DeleteStatement
static void processKeys(CFDefinition cfDef, List<Relation> keys, Map<ColumnIdentifier, List<Term>> processed) throws InvalidRequestException
{
for (Relation rel : keys)
{
CFDefinition.Name name = cfDef.get(rel.getEntity());
if (name == null)
throw new InvalidRequestException(String.format("Unknown key identifier %s", rel.getEntity()));
switch (name.kind)
{
case KEY_ALIAS:
case COLUMN_ALIAS:
List<Term> values;
if (rel.operator() == Relation.Type.EQ)
values = Collections.singletonList(rel.getValue());
else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN)
values = rel.getInValues();
else
throw new InvalidRequestException(String.format("Invalid operator %s for key %s", rel.operator(), rel.getEntity()));
if (processed.containsKey(name.name))
throw new InvalidRequestException(String.format("Multiple definition found for PRIMARY KEY part %s", name));
processed.put(name.name, values);
break;
case VALUE_ALIAS:
case COLUMN_METADATA:
throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", rel.getEntity()));
}
}
}
public String toString()
{
return String.format("UpdateStatement(name=%s, keys=%s, columns=%s, consistency=%s, timestamp=%s, timeToLive=%s)",
cfName,
whereClause,
columns,
getConsistencyLevel(),
timestamp,
timeToLive);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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 org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.thrift.CqlResult;
import org.apache.cassandra.thrift.InvalidRequestException;
public class UseStatement extends CQLStatement
{
private final String keyspace;
public UseStatement(String keyspace)
{
this.keyspace = keyspace;
}
public void checkAccess(ClientState state)
{
// No specific access
}
public CqlResult execute(ClientState state, List<ByteBuffer> variables) throws InvalidRequestException
{
state.setKeyspace(keyspace);
return null;
}
}

View File

@ -147,6 +147,15 @@ public class CounterMutation implements IMutation
rm.apply();
}
public void addAll(IMutation m)
{
if (!(m instanceof CounterMutation))
throw new IllegalArgumentException();
CounterMutation cm = (CounterMutation)m;
rowMutation.addAll(cm.rowMutation);
}
@Override
public String toString()
{

View File

@ -29,4 +29,5 @@ public interface IMutation
public ByteBuffer key();
public void apply() throws IOException;
public String toString(boolean shallow);
public void addAll(IMutation m);
}

View File

@ -245,6 +245,25 @@ public class RowMutation implements IMutation, MessageProducer
}
}
public void addAll(IMutation m)
{
if (!(m instanceof RowMutation))
throw new IllegalArgumentException();
RowMutation rm = (RowMutation)m;
if (!table_.equals(rm.table_) || !key_.equals(rm.key_))
throw new IllegalArgumentException();
for (Map.Entry<Integer, ColumnFamily> entry : rm.modifications_.entrySet())
{
// It's slighty faster to assume the key wasn't present and fix if
// not in the case where it wasn't there indeed.
ColumnFamily cf = modifications_.put(entry.getKey(), entry.getValue());
if (cf != null)
entry.getValue().resolve(cf);
}
}
/*
* This is equivalent to calling commit. Applies the changes to
* to the table that is obtained by calling Table.open().

View File

@ -94,7 +94,7 @@ public class SystemTable
rm = new RowMutation(Table.SYSTEM_TABLE, ByteBufferUtil.bytes("cql"));
cf = ColumnFamily.create(Table.SYSTEM_TABLE, VERSION_CF);
cf.addColumn(new Column(ByteBufferUtil.bytes("version"), ByteBufferUtil.bytes(QueryProcessor.CQL_VERSION)));
cf.addColumn(new Column(ByteBufferUtil.bytes("version"), ByteBufferUtil.bytes(QueryProcessor.CQL_VERSION.toString())));
rm.add(cf);
rm.apply();

View File

@ -108,6 +108,23 @@ public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
return 1;
}
/**
* Split a composite column names into it's components.
*/
public ByteBuffer[] split(ByteBuffer name)
{
List<ByteBuffer> l = new ArrayList<ByteBuffer>();
ByteBuffer bb = name.duplicate();
int i = 0;
while (bb.remaining() > 0)
{
getNextComparator(i++, bb);
l.add(getWithShortLength(bb));
bb.get(); // skip end-of-component
}
return l.toArray(new ByteBuffer[l.size()]);
}
public String getString(ByteBuffer bytes)
{
StringBuilder sb = new StringBuilder();

View File

@ -18,12 +18,17 @@
*/
package org.apache.cassandra.db.marshal;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.apache.cassandra.cql3.ColumnNameBuilder;
import org.apache.cassandra.cql3.Relation;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.io.util.FastByteArrayOutputStream;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.thrift.InvalidRequestException;
/*
* The encoding of a CompositeType column name should be:
@ -47,8 +52,7 @@ import org.apache.cassandra.config.ConfigurationException;
*/
public class CompositeType extends AbstractCompositeType
{
// package protected for unit tests sake
final List<AbstractType<?>> types;
public final List<AbstractType<?>> types;
// interning instances
private static final Map<List<AbstractType<?>>, CompositeType> instances = new HashMap<List<AbstractType<?>>, CompositeType>();
@ -138,4 +142,118 @@ public class CompositeType extends AbstractCompositeType
{
return getClass().getName() + TypeParser.stringifyTypeParameters(types);
}
public static class Builder implements ColumnNameBuilder
{
private final CompositeType composite;
private int current;
private final FastByteArrayOutputStream baos = new FastByteArrayOutputStream();
private final DataOutput out = new DataOutputStream(baos);
public Builder(CompositeType composite)
{
this.composite = composite;
}
private Builder(Builder b)
{
this(b.composite);
this.current = b.current;
try
{
out.write(b.baos.toByteArray());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public Builder add(Term t, Relation.Type op, List<ByteBuffer> variables) throws InvalidRequestException
{
if (current >= composite.types.size())
throw new IllegalStateException("Composite column is already fully constructed");
AbstractType currentType = composite.types.get(current++);
ByteBuffer buffer = t.getByteBuffer(currentType, variables);
try
{
ByteBufferUtil.writeWithShortLength(buffer, out);
/*
* Given the rules for eoc (end-of-component, see AbstractCompositeType.compare()),
* We can select:
* - = 'a' by using <'a'><0>
* - < 'a' by using <'a'><-1>
* - <= 'a' by using <'a'><1>
* - > 'a' by using <'a'><1>
* - >= 'a' by using <'a'><0>
*/
switch (op)
{
case LT:
out.write((byte) -1);
break;
case GT:
case LTE:
out.write((byte) 1);
break;
default:
out.write((byte) 0);
break;
}
return this;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public Builder add(ByteBuffer bb)
{
if (current >= composite.types.size())
throw new IllegalStateException("Composite column is already fully constructed");
try
{
ByteBufferUtil.writeWithShortLength(bb, out);
out.write((byte) 0);
return this;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public int componentCount()
{
return current;
}
public ByteBuffer build()
{
return ByteBuffer.wrap(baos.toByteArray());
}
public ByteBuffer buildAsEndOfRange()
{
if (current >= composite.types.size())
throw new IllegalStateException("Composite column is already fully constructed");
if (current == 0)
return ByteBufferUtil.EMPTY_BYTE_BUFFER;
ByteBuffer bb = build();
bb.put(bb.remaining() - 1, (byte)1);
return bb;
}
public Builder copy()
{
return new Builder(this);
}
}
}

View File

@ -20,6 +20,8 @@ package org.apache.cassandra.service;
import java.util.*;
import com.google.common.collect.Sets;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,6 +34,7 @@ import org.apache.cassandra.cql.CQLStatement;
import org.apache.cassandra.db.Table;
import org.apache.cassandra.thrift.AuthenticationException;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.utils.SemanticVersion;
/**
* A container for per-client, thread-local state that Avro/Thrift threads must hold.
@ -41,12 +44,14 @@ public class ClientState
{
private static final int MAX_CACHE_PREPARED = 10000; // Enough to keep buggy clients from OOM'ing us
private static Logger logger = LoggerFactory.getLogger(ClientState.class);
public static final SemanticVersion DEFAULT_CQL_VERSION = org.apache.cassandra.cql.QueryProcessor.CQL_VERSION;
// Current user for the session
private AuthenticatedUser user;
private String keyspace;
// Reusable array for authorization
private final List<Object> resource = new ArrayList<Object>();
private SemanticVersion cqlVersion = DEFAULT_CQL_VERSION;
// An LRU map of prepared statements
private Map<Integer, CQLStatement> prepared = new LinkedHashMap<Integer, CQLStatement>(16, 0.75f, true) {
@ -55,6 +60,12 @@ public class ClientState
}
};
private Map<Integer, org.apache.cassandra.cql3.CQLStatement> cql3Prepared = new LinkedHashMap<Integer, org.apache.cassandra.cql3.CQLStatement>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<Integer, org.apache.cassandra.cql3.CQLStatement> eldest) {
return size() > MAX_CACHE_PREPARED;
}
};
private long clock;
/**
@ -69,7 +80,12 @@ public class ClientState
{
return prepared;
}
public Map<Integer, org.apache.cassandra.cql3.CQLStatement> getCQL3Prepared()
{
return cql3Prepared;
}
public String getRawKeyspace()
{
return keyspace;
@ -129,6 +145,7 @@ public class ClientState
keyspace = null;
resourceClear();
prepared.clear();
cql3Prepared.clear();
}
/**
@ -219,4 +236,42 @@ public class ClientState
clock = clock >= current ? clock + 1 : current;
return clock;
}
public void setCQLVersion(String str) throws InvalidRequestException
{
SemanticVersion version;
try
{
version = new SemanticVersion(str);
}
catch (IllegalArgumentException e)
{
throw new InvalidRequestException(e.getMessage());
}
SemanticVersion cql = org.apache.cassandra.cql.QueryProcessor.CQL_VERSION;
SemanticVersion cql3 = org.apache.cassandra.cql3.QueryProcessor.CQL_VERSION;
if (version.isSupportedBy(cql))
cqlVersion = cql;
else if (version.isSupportedBy(cql3))
cqlVersion = cql3;
else
throw new InvalidRequestException(String.format("Provided version %s is not supported by this server (supported: %s)",
version,
StringUtils.join(getCQLSupportedVersion(), ", ")));
}
public SemanticVersion getCQLVersion()
{
return cqlVersion;
}
public static SemanticVersion[] getCQLSupportedVersion()
{
SemanticVersion cql = org.apache.cassandra.cql.QueryProcessor.CQL_VERSION;
SemanticVersion cql3 = org.apache.cassandra.cql3.QueryProcessor.CQL_VERSION;
return new SemanticVersion[]{ cql, cql3 };
}
}

View File

@ -413,6 +413,7 @@ public class StorageService implements IEndpointStateChangeSubscriber, StorageSe
{
logger_.info("Cassandra version: " + FBUtilities.getReleaseVersionString());
logger_.info("Thrift API version: " + Constants.VERSION);
logger_.info("CQL supported versions: " + StringUtils.join(ClientState.getCQLSupportedVersion(), ",") + " (default: " + ClientState.DEFAULT_CQL_VERSION + ")");
if (initialized)
{

View File

@ -1182,7 +1182,10 @@ public class CassandraServer implements Cassandra.Iface
try
{
return QueryProcessor.process(queryString, state());
if (state().getCQLVersion().major == 2)
return QueryProcessor.process(queryString, state());
else
return org.apache.cassandra.cql3.QueryProcessor.process(queryString, state());
}
catch (RecognitionException e)
{
@ -1201,7 +1204,10 @@ public class CassandraServer implements Cassandra.Iface
try
{
return QueryProcessor.prepare(queryString, state());
if (state().getCQLVersion().major == 2)
return QueryProcessor.prepare(queryString, state());
else
return org.apache.cassandra.cql3.QueryProcessor.prepare(queryString, state());
}
catch (RecognitionException e)
{
@ -1215,14 +1221,34 @@ public class CassandraServer implements Cassandra.Iface
throws InvalidRequestException, UnavailableException, TimedOutException, SchemaDisagreementException, TException
{
if (logger.isDebugEnabled()) logger.debug("execute_prepared_cql_query");
CQLStatement statement = state().getPrepared().get(itemId);
if (statement == null)
throw new InvalidRequestException(String.format("Prepared query with ID %d not found", itemId));
logger.trace("Retrieved prepared statement #{} with {} bind markers", itemId, state().getPrepared().size());
if (state().getCQLVersion().major == 2)
{
CQLStatement statement = state().getPrepared().get(itemId);
return QueryProcessor.processPrepared(statement, state(), bindVariables);
if (statement == null)
throw new InvalidRequestException(String.format("Prepared query with ID %d not found", itemId));
logger.trace("Retrieved prepared statement #{} with {} bind markers", itemId, statement.boundTerms);
return QueryProcessor.processPrepared(statement, state(), bindVariables);
}
else
{
org.apache.cassandra.cql3.CQLStatement statement = state().getCQL3Prepared().get(itemId);
if (statement == null)
throw new InvalidRequestException(String.format("Prepared query with ID %d not found", itemId));
logger.trace("Retrieved prepared statement #{} with {} bind markers", itemId, statement.getBoundsTerms());
return org.apache.cassandra.cql3.QueryProcessor.processPrepared(statement, state(), bindVariables);
}
}
public void set_cql_version(String version) throws InvalidRequestException
{
logger.debug("set_cql_version: " + version);
state().setCQLVersion(version);
}
// main method moved to CassandraDaemon

View File

@ -617,10 +617,6 @@ public class ThriftValidation
if (cf_def.column_metadata == null)
return;
AbstractType<?> comparator = cfType == ColumnFamilyType.Standard
? TypeParser.parse(cf_def.comparator_type)
: TypeParser.parse(cf_def.subcomparator_type);
if (cf_def.key_alias != null)
{
// check if any of the columns has name equal to the cf.key_alias
@ -642,6 +638,8 @@ public class ThriftValidation
indexNames.add(cd.getIndexName());
}
AbstractType<?> comparator = CFMetaData.getColumnDefinitionComparator(cf_def);
for (ColumnDef c : cf_def.column_metadata)
{
TypeParser.parse(c.validation_class);
@ -653,7 +651,7 @@ public class ThriftValidation
catch (MarshalException e)
{
throw new InvalidRequestException(String.format("Column name %s is not valid for comparator %s",
ByteBufferUtil.bytesToHex(c.name), cf_def.comparator_type));
ByteBufferUtil.bytesToHex(c.name), comparator));
}
if (c.index_type == null)

View File

@ -0,0 +1,231 @@
/*
* 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.utils;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Arrays;
import org.apache.commons.lang.StringUtils;
import com.google.common.base.Objects;
/**
* Implements semantic versioning as defined at http://semver.org/.
*
* Note: The following code uses a slight variation from the document above in
* that it doesn't allow dashes in pre-release and build identifier.
*/
public class SemanticVersion implements Comparable<SemanticVersion>
{
private static final String VERSION_REGEXP = "(\\d+)\\.(\\d+)\\.(\\d+)(\\-[.\\w]+)?(\\+[.\\w]+)?";
private static final Pattern pattern = Pattern.compile(VERSION_REGEXP);
public final int major;
public final int minor;
public final int patch;
private final String[] preRelease;
private final String[] build;
private SemanticVersion(int major, int minor, int patch, String[] preRelease, String[] build)
{
this.major = major;
this.minor = minor;
this.patch = patch;
this.preRelease = preRelease;
this.build = build;
}
/**
* Parse a semantic version from a string.
*
* @param version the string to parse
* @throws IllegalArgumentException if the provided string does not
* represent a semantic version
*/
public SemanticVersion(String version)
{
Matcher matcher = pattern.matcher(version);
if (!matcher.matches())
throw new IllegalArgumentException("Invalid version value: " + version + " (see http://semver.org/ for details)");
try
{
this.major = Integer.valueOf(matcher.group(1));
this.minor = Integer.valueOf(matcher.group(2));
this.patch = Integer.valueOf(matcher.group(3));
String pr = matcher.group(4);
String bld = matcher.group(5);
this.preRelease = pr == null || pr.isEmpty() ? null : parseIdentifiers(version, pr);
this.build = bld == null || bld.isEmpty() ? null : parseIdentifiers(version, bld);
}
catch (NumberFormatException e)
{
throw new IllegalArgumentException("Invalid version value: " + version + " (see http://semver.org/ for details)");
}
}
private static String[] parseIdentifiers(String version, String str)
{
// Drop initial - or +
str = str.substring(1);
String[] parts = str.split("\\.");
for (String part : parts)
{
if (!part.matches("\\w+"))
throw new IllegalArgumentException("Invalid version value: " + version + " (see http://semver.org/ for details)");
}
return parts;
}
public int compareTo(SemanticVersion other)
{
if (major < other.major)
return -1;
if (major > other.major)
return 1;
if (minor < other.minor)
return -1;
if (minor > other.minor)
return 1;
if (patch < other.patch)
return -1;
if (patch > other.patch)
return 1;
int c = compareIdentifiers(preRelease, other.preRelease, 1);
if (c != 0)
return c;
return compareIdentifiers(build, other.build, -1);
}
/**
* Returns a version that is backward compatible with this version amongst a list
* of provided version, or null if none can be found.
*
* For instance:
* "2.0.0".findSupportingVersion("2.0.0", "3.0.0") == "2.0.0"
* "2.0.0".findSupportingVersion("2.1.3", "3.0.0") == "2.1.3"
* "2.0.0".findSupportingVersion("3.0.0") == null
* "2.0.3".findSupportingVersion("2.0.0") == "2.0.0"
* "2.1.0".findSupportingVersion("2.0.0") == null
*/
public SemanticVersion findSupportingVersion(SemanticVersion... versions)
{
for (SemanticVersion version : versions)
{
if (isSupportedBy(version))
return version;
}
return null;
}
public boolean isSupportedBy(SemanticVersion version)
{
return major == version.major && minor <= version.minor;
}
private static int compareIdentifiers(String[] ids1, String[] ids2, int defaultPred)
{
if (ids1 == null)
return ids2 == null ? 0 : defaultPred;
else if (ids2 == null)
return -defaultPred;
int min = Math.min(ids1.length, ids2.length);
for (int i = 0; i < min; i++)
{
Integer i1 = tryParseInt(ids1[i]);
Integer i2 = tryParseInt(ids2[i]);
if (i1 != null)
{
// integer have precedence
if (i2 == null || i1 < i2)
return -1;
else if (i1 > i2)
return 1;
}
else
{
// integer have precedence
if (i2 != null)
return 1;
int c = ids1[i].compareTo(ids2[i]);
if (c != 0)
return c;
}
}
if (ids1.length < ids2.length)
return -1;
if (ids1.length > ids2.length)
return 1;
return 0;
}
private static Integer tryParseInt(String str)
{
try
{
return Integer.valueOf(str);
}
catch (NumberFormatException e)
{
return null;
}
}
@Override
public boolean equals(Object o)
{
if(!(o instanceof SemanticVersion))
return false;
SemanticVersion that = (SemanticVersion)o;
return major == that.major
&& minor == that.minor
&& patch == that.patch
&& Arrays.equals(preRelease, that.preRelease)
&& Arrays.equals(build, that.build);
}
@Override
public int hashCode()
{
return Objects.hashCode(major, minor, patch, preRelease, build);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(major).append('.').append(minor).append('.').append(patch);
if (preRelease != null)
sb.append('-').append(StringUtils.join(preRelease, "."));
if (build != null)
sb.append('+').append(StringUtils.join(build, "."));
return sb.toString();
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.utils;
import org.junit.Test;
public class SemanticVersionTest
{
@Test
public void testParsing()
{
SemanticVersion version;
version = new SemanticVersion("1.2.3");
assert version.major == 1 && version.minor == 2 && version.patch == 3;
version = new SemanticVersion("1.2.3-foo.2+Bar");
assert version.major == 1 && version.minor == 2 && version.patch == 3;
}
@Test
public void testComparison()
{
SemanticVersion v1, v2;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.2.4");
assert v1.compareTo(v2) == -1;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.2.3");
assert v1.compareTo(v2) == 0;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("2.0.0");
assert v1.compareTo(v2) == -1;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.2.3-alpha");
assert v1.compareTo(v2) == 1;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.2.3+foo");
assert v1.compareTo(v2) == -1;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.2.3-alpha+foo");
assert v1.compareTo(v2) == 1;
}
@Test
public void testIsSupportedBy()
{
SemanticVersion v1, v2;
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.2.4");
assert v1.isSupportedBy(v2);
assert v2.isSupportedBy(v1);
v1 = new SemanticVersion("1.2.3");
v2 = new SemanticVersion("1.3.3");
assert v1.isSupportedBy(v2);
assert !v2.isSupportedBy(v1);
v1 = new SemanticVersion("2.2.3");
v2 = new SemanticVersion("1.3.3");
assert !v1.isSupportedBy(v2);
assert !v2.isSupportedBy(v1);
}
@Test
public void testInvalid()
{
assertThrows("1.0");
assertThrows("1.0.0a");
assertThrows("1.a.4");
assertThrows("1.0.0-foo&");
}
private static void assertThrows(String str)
{
try
{
new SemanticVersion(str);
assert false;
}
catch (IllegalArgumentException e) {}
}
}