mirror of https://github.com/apache/cassandra
CQL 3.0 beta
patch by slebresne; reviewed by jbellis and urandom for CASSANDRA-3761
This commit is contained in:
parent
86637d43cb
commit
655ccc3abf
|
|
@ -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
|
||||
|
|
|
|||
15
build.xml
15
build.xml
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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; }
|
||||
;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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().
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue