You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sl...@apache.org on 2012/07/30 15:53:00 UTC
git commit: Add support for row key composite
Updated Branches:
refs/heads/trunk 7588f74d0 -> daac1edc3
Add support for row key composite
patch by slebresne; reviewed by xedin for CASSANDRA-4179
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/daac1edc
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/daac1edc
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/daac1edc
Branch: refs/heads/trunk
Commit: daac1edc36068e983b2fe3502dcc886a59226715
Parents: 7588f74
Author: Sylvain Lebresne <sy...@datastax.com>
Authored: Wed Jul 4 12:54:15 2012 +0200
Committer: Sylvain Lebresne <sy...@datastax.com>
Committed: Mon Jul 30 15:52:51 2012 +0200
----------------------------------------------------------------------
CHANGES.txt | 1 +
src/java/org/apache/cassandra/config/Avro.java | 2 +-
.../org/apache/cassandra/config/CFMetaData.java | 88 ++++---
.../apache/cassandra/cql/AlterTableStatement.java | 7 +-
.../cassandra/cql/CreateColumnFamilyStatement.java | 3 +-
.../org/apache/cassandra/cql3/CFDefinition.java | 80 ++++--
.../apache/cassandra/cql3/ColumnNameBuilder.java | 5 +
src/java/org/apache/cassandra/cql3/Cql.g | 50 +++-
src/java/org/apache/cassandra/cql3/Term.java | 24 --
.../cql3/statements/AlterTableStatement.java | 13 +-
.../statements/CreateColumnFamilyStatement.java | 24 +-
.../cassandra/cql3/statements/DeleteStatement.java | 34 +--
.../cassandra/cql3/statements/SelectStatement.java | 208 ++++++++++-----
.../cassandra/cql3/statements/UpdateStatement.java | 88 ++++---
.../apache/cassandra/db/marshal/CompositeType.java | 5 +
15 files changed, 398 insertions(+), 234 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 55678f2..48360ad 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -32,6 +32,7 @@
* (cql3) Add support for set, map and list (CASSANDRA-3647)
* Validate date type correctly (CASSANDRA-4441)
* (cql3) Allow definitions with only a PK (CASSANDRA-4361)
+ * (cql3) Add support for row key composites (CASSANDRA-4179)
1.1.3
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/config/Avro.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/Avro.java b/src/java/org/apache/cassandra/config/Avro.java
index 2d0c34d..ed914f2 100644
--- a/src/java/org/apache/cassandra/config/Avro.java
+++ b/src/java/org/apache/cassandra/config/Avro.java
@@ -143,7 +143,7 @@ public class Avro
// Isn't AVRO supposed to handle stuff like this?
if (cf.min_compaction_threshold != null) { newCFMD.minCompactionThreshold(cf.min_compaction_threshold); }
if (cf.max_compaction_threshold != null) { newCFMD.maxCompactionThreshold(cf.max_compaction_threshold); }
- if (cf.key_alias != null) { newCFMD.keyAlias(cf.key_alias); }
+ if (cf.key_alias != null) { newCFMD.keyAliases(Collections.<ByteBuffer>singletonList(cf.key_alias)); }
if (cf.column_aliases != null)
newCFMD.columnAliases(new ArrayList<ByteBuffer>(cf.column_aliases)); // fix avro stupidity
if (cf.value_alias != null) { newCFMD.valueAlias(cf.value_alias); }
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/config/CFMetaData.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java
index 970df94..c5da29f 100644
--- a/src/java/org/apache/cassandra/config/CFMetaData.java
+++ b/src/java/org/apache/cassandra/config/CFMetaData.java
@@ -120,7 +120,8 @@ public final class CFMetaData
+ "key_validator text,"
+ "min_compaction_threshold int,"
+ "max_compaction_threshold int,"
- + "key_alias text,"
+ + "key_alias text," // that one is kept for compatibility sake
+ + "key_aliases text,"
+ "bloom_filter_fp_chance double,"
+ "caching text,"
+ "compaction_strategy_class text,"
@@ -205,7 +206,7 @@ public final class CFMetaData
private AbstractType<?> keyValidator; // default BytesType (no-op), use comparator types
private int minCompactionThreshold; // default 4
private int maxCompactionThreshold; // default 32
- private ByteBuffer keyAlias; // default NULL
+ private List<ByteBuffer> keyAliases = new ArrayList<ByteBuffer>();
private List<ByteBuffer> columnAliases = new ArrayList<ByteBuffer>();
private ByteBuffer valueAlias; // default NULL
private Double bloomFilterFpChance; // default NULL
@@ -231,8 +232,7 @@ public final class CFMetaData
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 keyAlias(ByteBuffer prop) {keyAlias = prop; updateCfDef(); return this;}
- public CFMetaData keyAlias(String alias) { return keyAlias(ByteBufferUtil.bytes(alias)); }
+ public CFMetaData keyAliases(List<ByteBuffer> prop) {keyAliases = 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;}
@@ -309,7 +309,6 @@ public final class CFMetaData
defaultValidator = BytesType.instance;
keyValidator = BytesType.instance;
comment = "";
- keyAlias = null; // This qualifies as a 'strange default'.
valueAlias = null;
column_metadata = new HashMap<ByteBuffer,ColumnDefinition>();
@@ -398,7 +397,7 @@ public final class CFMetaData
.keyValidator(oldCFMD.keyValidator)
.minCompactionThreshold(oldCFMD.minCompactionThreshold)
.maxCompactionThreshold(oldCFMD.maxCompactionThreshold)
- .keyAlias(oldCFMD.keyAlias)
+ .keyAliases(new ArrayList<ByteBuffer>(oldCFMD.keyAliases))
.columnAliases(new ArrayList<ByteBuffer>(oldCFMD.columnAliases))
.valueAlias(oldCFMD.valueAlias)
.columnMetadata(clonedColumns)
@@ -468,14 +467,18 @@ public final class CFMetaData
return maxCompactionThreshold;
}
+ // Used by CQL2 only.
public ByteBuffer getKeyName()
{
- return keyAlias == null ? DEFAULT_KEY_NAME : keyAlias;
+ if (keyAliases.size() > 1)
+ throw new IllegalStateException("Cannot acces column family with composite key from CQL < 3.0.0");
+
+ return keyAliases.isEmpty() ? DEFAULT_KEY_NAME : keyAliases.get(0);
}
- public ByteBuffer getKeyAlias()
+ public List<ByteBuffer> getKeyAliases()
{
- return keyAlias;
+ return keyAliases;
}
public List<ByteBuffer> getColumnAliases()
@@ -542,7 +545,7 @@ public final class CFMetaData
.append(maxCompactionThreshold, rhs.maxCompactionThreshold)
.append(cfId, rhs.cfId)
.append(column_metadata, rhs.column_metadata)
- .append(keyAlias, rhs.keyAlias)
+ .append(keyAliases, rhs.keyAliases)
.append(columnAliases, rhs.columnAliases)
.append(valueAlias, rhs.valueAlias)
.append(compactionStrategyClass, rhs.compactionStrategyClass)
@@ -572,7 +575,7 @@ public final class CFMetaData
.append(maxCompactionThreshold)
.append(cfId)
.append(column_metadata)
- .append(keyAlias)
+ .append(keyAliases)
.append(columnAliases)
.append(valueAlias)
.append(compactionStrategyClass)
@@ -641,7 +644,7 @@ public final class CFMetaData
if (cf_def.isSetGc_grace_seconds()) { newCFMD.gcGraceSeconds(cf_def.gc_grace_seconds); }
if (cf_def.isSetMin_compaction_threshold()) { newCFMD.minCompactionThreshold(cf_def.min_compaction_threshold); }
if (cf_def.isSetMax_compaction_threshold()) { newCFMD.maxCompactionThreshold(cf_def.max_compaction_threshold); }
- if (cf_def.isSetKey_alias()) { newCFMD.keyAlias(cf_def.key_alias); }
+ if (cf_def.isSetKey_alias()) { newCFMD.keyAliases(Collections.<ByteBuffer>singletonList(cf_def.key_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);
@@ -743,14 +746,25 @@ public final class CFMetaData
keyValidator = cfm.keyValidator;
minCompactionThreshold = cfm.minCompactionThreshold;
maxCompactionThreshold = cfm.maxCompactionThreshold;
- keyAlias = cfm.keyAlias;
- // We don't want updates coming from thrift to erase columnAliases/valuAlias, which would be wrong, but those are not exposed throught thrift. So
- // we just only override the value when the new has those set.
- // Note that this doesn't remove feature on the CQL side since removing columnAliases/valuAlias is non-sensical and not allowed (actually, updating
- // those is not allowed either but it would be possible to allow it through some ALTER RENAME later).
+ /*
+ * We don't allow changing the number of aliases (removal would be plain wrong and we've decided to no support addition since it would
+ * only make sense in very few cases).
+ * However, since thrift doesn't know about aliases (expect for the key aliases, but even then it doesn't support composite ones), we
+ * don't want to reject update that don't set the aliases at all.
+ */
+ if (!cfm.keyAliases.isEmpty())
+ {
+ if (keyAliases.size() != cfm.keyAliases.size())
+ throw new ConfigurationException("Cannot change the number of key aliases");
+ keyAliases = cfm.keyAliases;
+ }
if (!cfm.columnAliases.isEmpty())
+ {
+ if (columnAliases.size() != cfm.columnAliases.size())
+ throw new ConfigurationException("Cannot change the number of column aliases");
columnAliases = cfm.columnAliases;
+ }
if (cfm.valueAlias != null)
valueAlias = cfm.valueAlias;
bloomFilterFpChance = cfm.bloomFilterFpChance;
@@ -835,7 +849,9 @@ public final class CFMetaData
def.setKey_validation_class(keyValidator.toString());
def.setMin_compaction_threshold(minCompactionThreshold);
def.setMax_compaction_threshold(maxCompactionThreshold);
- def.setKey_alias(keyAlias);
+ // We only return the alias if only one is set since thrift don't know about multiple key aliases
+ if (keyAliases.size() == 1)
+ def.setKey_alias(keyAliases.get(0));
List<org.apache.cassandra.thrift.ColumnDef> column_meta = new ArrayList<org.apache.cassandra.thrift.ColumnDef>(column_metadata.size());
for (ColumnDefinition cd : column_metadata.values())
{
@@ -989,8 +1005,9 @@ public final class CFMetaData
// check if any of the columns has name equal to the cf.key_alias
for (ColumnDefinition columndef : column_metadata.values())
{
- if (keyAlias != null && keyAlias.equals(columndef.name))
- throw new ConfigurationException("Cannot have key alias equals to a column name: " + UTF8Type.instance.compose(keyAlias));
+ for (ByteBuffer alias : keyAliases)
+ if (alias.equals(columndef.name))
+ throw new ConfigurationException("Cannot have key alias equals to a column name: " + UTF8Type.instance.compose(alias));
for (ByteBuffer alias : columnAliases)
if (alias.equals(columndef.name))
@@ -1000,7 +1017,8 @@ public final class CFMetaData
throw new ConfigurationException("Cannot have value alias equals to a column name: " + UTF8Type.instance.compose(valueAlias));
}
- validateAlias(keyAlias, "Key");
+ for (ByteBuffer alias : keyAliases)
+ validateAlias(alias, "Key");
for (ByteBuffer alias : columnAliases)
validateAlias(alias, "Column");
validateAlias(valueAlias, "Value");
@@ -1202,8 +1220,7 @@ public final class CFMetaData
cf.addColumn(Column.create(keyValidator.toString(), timestamp, cfName, "key_validator"));
cf.addColumn(Column.create(minCompactionThreshold, timestamp, cfName, "min_compaction_threshold"));
cf.addColumn(Column.create(maxCompactionThreshold, timestamp, cfName, "max_compaction_threshold"));
- cf.addColumn(keyAlias == null ? DeletedColumn.create(ldt, timestamp, cfName, "key_alias")
- : Column.create(keyAlias, timestamp, cfName, "key_alias"));
+ cf.addColumn(Column.create(json(aliasesAsStrings(keyAliases)), timestamp, cfName, "key_aliases"));
cf.addColumn(bloomFilterFpChance == null ? DeletedColumn.create(ldt, timestamp, cfName, "bloomFilterFpChance")
: Column.create(bloomFilterFpChance, timestamp, cfName, "bloom_filter_fp_chance"));
cf.addColumn(Column.create(caching.toString(), timestamp, cfName, "caching"));
@@ -1211,7 +1228,7 @@ public final class CFMetaData
cf.addColumn(Column.create(json(compressionParameters.asThriftOptions()), timestamp, cfName, "compression_parameters"));
cf.addColumn(valueAlias == null ? DeletedColumn.create(ldt, timestamp, cfName, "value_alias")
: Column.create(valueAlias, timestamp, cfName, "value_alias"));
- cf.addColumn(Column.create(json(columnAliasesAsStrings()), timestamp, cfName, "column_aliases"));
+ cf.addColumn(Column.create(json(aliasesAsStrings(columnAliases)), timestamp, cfName, "column_aliases"));
cf.addColumn(Column.create(json(compactionStrategyOptions), timestamp, cfName, "compaction_strategy_options"));
}
@@ -1239,14 +1256,21 @@ public final class CFMetaData
cfm.maxCompactionThreshold(result.getInt("max_compaction_threshold"));
if (result.has("comment"))
cfm.comment(result.getString("comment"));
- if (result.has("key_alias"))
- cfm.keyAlias(result.getBytes("key_alias"));
+ // We need support the old key_alias for compatibility sake
+ if (result.has("key_aliases"))
+ {
+ cfm.keyAliases(aliasesFromStrings(fromJsonList(result.getString("key_aliases"))));
+ }
+ else if (result.has("key_alias"))
+ {
+ cfm.keyAliases(Collections.<ByteBuffer>singletonList(result.getBytes("key_alias")));
+ }
if (result.has("bloom_filter_fp_chance"))
cfm.bloomFilterFpChance(result.getDouble("bloom_filter_fp_chance"));
cfm.caching(Caching.valueOf(result.getString("caching")));
cfm.compactionStrategyClass(createCompactionStrategy(result.getString("compaction_strategy_class")));
cfm.compressionParameters(CompressionParameters.create(fromJsonMap(result.getString("compression_parameters"))));
- cfm.columnAliases(columnAliasesFromStrings(fromJsonList(result.getString("column_aliases"))));
+ cfm.columnAliases(aliasesFromStrings(fromJsonList(result.getString("column_aliases"))));
if (result.has("value_alias"))
cfm.valueAlias(result.getBytes("value_alias"));
cfm.compactionStrategyOptions(fromJsonMap(result.getString("compaction_strategy_options")));
@@ -1278,15 +1302,15 @@ public final class CFMetaData
return fromSchema(result);
}
- private List<String> columnAliasesAsStrings()
+ private List<String> aliasesAsStrings(List<ByteBuffer> rawAliases)
{
- List<String> aliases = new ArrayList<String>(columnAliases.size());
- for (ByteBuffer rawAlias : columnAliases)
+ List<String> aliases = new ArrayList<String>(rawAliases.size());
+ for (ByteBuffer rawAlias : rawAliases)
aliases.add(UTF8Type.instance.compose(rawAlias));
return aliases;
}
- private static List<ByteBuffer> columnAliasesFromStrings(List<String> aliases)
+ private static List<ByteBuffer> aliasesFromStrings(List<String> aliases)
{
List<ByteBuffer> rawAliases = new ArrayList<ByteBuffer>(aliases.size());
for (String alias : aliases)
@@ -1383,7 +1407,7 @@ public final class CFMetaData
.append("keyValidator", keyValidator)
.append("minCompactionThreshold", minCompactionThreshold)
.append("maxCompactionThreshold", maxCompactionThreshold)
- .append("keyAlias", keyAlias)
+ .append("keyAliases", keyAliases)
.append("columnAliases", columnAliases)
.append("valueAlias", valueAlias)
.append("column_metadata", column_metadata)
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql/AlterTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/AlterTableStatement.java b/src/java/org/apache/cassandra/cql/AlterTableStatement.java
index 7d225e6..85d7619 100644
--- a/src/java/org/apache/cassandra/cql/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql/AlterTableStatement.java
@@ -74,10 +74,10 @@ public class AlterTableStatement
switch (oType)
{
case ADD:
- if (cfm.getKeyAlias() != null && cfm.getKeyAlias().equals(columnName))
+ if (!cfm.getKeyAliases().isEmpty() && cfm.getKeyAliases().contains(columnName))
throw new InvalidRequestException("Invalid column name: "
+ this.columnName
- + ", because it equals to key_alias.");
+ + ", because it equals to a key alias.");
cfm.addColumnDefinition(new ColumnDefinition(columnName,
TypeParser.parse(validator),
@@ -88,7 +88,8 @@ public class AlterTableStatement
break;
case ALTER:
- if (cfm.getKeyAlias() != null && cfm.getKeyAlias().equals(columnName))
+ // We only look for the first key alias which is ok for CQL2
+ if (!cfm.getKeyAliases().isEmpty() && cfm.getKeyAliases().get(0).equals(columnName))
{
cfm.keyValidator(TypeParser.parse(validator));
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java b/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java
index c9255eb..dd72eaf 100644
--- a/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java
+++ b/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java
@@ -19,6 +19,7 @@ package org.apache.cassandra.cql;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -176,7 +177,7 @@ public class CreateColumnFamilyStatement
.maxCompactionThreshold(getPropertyInt(CFPropDefs.KW_MAXCOMPACTIONTHRESHOLD, CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD))
.columnMetadata(getColumns(comparator))
.keyValidator(TypeParser.parse(CFPropDefs.comparators.get(getKeyType())))
- .keyAlias(keyAlias)
+ .keyAliases(Collections.<ByteBuffer>singletonList(keyAlias))
.compactionStrategyClass(cfProps.compactionStrategyClass)
.compactionStrategyOptions(cfProps.compactionStrategyOptions)
.compressionParameters(CompressionParameters.create(cfProps.compressionParameters))
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/CFDefinition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/CFDefinition.java b/src/java/org/apache/cassandra/cql3/CFDefinition.java
index d960a79..56b68eb 100644
--- a/src/java/org/apache/cassandra/cql3/CFDefinition.java
+++ b/src/java/org/apache/cassandra/cql3/CFDefinition.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.*;
+import com.google.common.base.Joiner;
import com.google.common.collect.AbstractIterator;
import org.apache.cassandra.config.CFMetaData;
@@ -43,16 +44,17 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
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> keys = new LinkedHashMap<ColumnIdentifier, Name>();
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;
- // Note that isCompact means here that no component of the comparator correspond to the column names
- // defined in the CREATE TABLE QUERY. This is not exactly equivalent to using the 'WITH COMPACT STORAGE'
+ public final boolean hasCompositeKey;
+ // Note that isCompact means here that no componet of the comparator correspond to the column names
+ // defined in the CREATE TABLE QUERY. This is not exactly equivalent to the 'WITH COMPACT STORAGE'
// option when creating a table in that "static CF" without a composite type will have isCompact == false
// even though one must use 'WITH COMPACT STORAGE' to declare them.
public final boolean isCompact;
@@ -61,7 +63,25 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
public CFDefinition(CFMetaData cfm)
{
this.cfm = cfm;
- this.key = new Name(cfm.ksName, cfm.cfName, getKeyId(cfm), Name.Kind.KEY_ALIAS, cfm.getKeyValidator());
+
+ if (cfm.getKeyValidator() instanceof CompositeType)
+ {
+ this.hasCompositeKey = true;
+ CompositeType keyComposite = (CompositeType)cfm.getKeyValidator();
+ assert keyComposite.types.size() > 1;
+ for (int i = 0; i < keyComposite.types.size(); i++)
+ {
+ ColumnIdentifier id = getKeyId(cfm, i);
+ this.keys.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.KEY_ALIAS, i, keyComposite.types.get(i)));
+ }
+ }
+ else
+ {
+ this.hasCompositeKey = false;
+ ColumnIdentifier id = getKeyId(cfm, 0);
+ this.keys.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.KEY_ALIAS, 0, cfm.getKeyValidator()));
+ }
+
if (cfm.comparator instanceof CompositeType)
{
this.isComposite = true;
@@ -140,11 +160,13 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
assert value == null || metadata.isEmpty();
}
- private static ColumnIdentifier getKeyId(CFMetaData cfm)
+ private static ColumnIdentifier getKeyId(CFMetaData cfm, int i)
{
- return cfm.getKeyAlias() == null
- ? new ColumnIdentifier(DEFAULT_KEY_ALIAS, false)
- : new ColumnIdentifier(cfm.getKeyAlias(), definitionType);
+ List<ByteBuffer> definedNames = cfm.getKeyAliases();
+ // For compatibility sake, non-composite key default alias is 'key', not 'key1'.
+ return definedNames == null || i >= definedNames.size()
+ ? new ColumnIdentifier(i == 0 ? DEFAULT_KEY_ALIAS : DEFAULT_KEY_ALIAS + (i + 1), false)
+ : new ColumnIdentifier(cfm.getKeyAliases().get(i), definitionType);
}
private static ColumnIdentifier getColumnId(CFMetaData cfm, int i)
@@ -183,8 +205,9 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
public Name get(ColumnIdentifier name)
{
- if (name.equals(key.name))
- return key;
+ CFDefinition.Name kdef = keys.get(name);
+ if (kdef != null)
+ return kdef;
if (value != null && name.equals(value.name))
return value;
CFDefinition.Name def = columns.get(name);
@@ -197,18 +220,15 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
{
return new AbstractIterator<Name>()
{
- private boolean keyDone;
+ private final Iterator<Name> keyIter = keys.values().iterator();
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 (keyIter.hasNext())
+ return keyIter.next();
if (columnIter.hasNext())
return columnIter.next();
@@ -227,6 +247,13 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
};
}
+ public ColumnNameBuilder getKeyNameBuilder()
+ {
+ return hasCompositeKey
+ ? new CompositeType.Builder((CompositeType)cfm.getKeyValidator())
+ : new NonCompositeBuilder(cfm.getKeyValidator());
+ }
+
public ColumnNameBuilder getColumnNameBuilder()
{
return isComposite
@@ -254,27 +281,21 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
}
public final Kind kind;
- public final int position; // only make sense for COLUMN_ALIAS
+ public final int position; // only make sense for KEY_ALIAS and COLUMN_ALIAS
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
- sb.append(key.name);
- for (Name name : columns.values())
- sb.append(", ").append(name.name);
+ sb.append(Joiner.on(", ").join(keys.values()));
+ if (!columns.isEmpty())
+ sb.append(", ").append(Joiner.on(", ").join(columns.values()));
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("]");
+ sb.append("{").append(Joiner.on(", ").join(metadata.values())).append(" }");
return sb.toString();
}
@@ -312,6 +333,11 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
return columnName == null ? 0 : 1;
}
+ public int remainingCount()
+ {
+ return columnName == null ? 1 : 0;
+ }
+
public ByteBuffer build()
{
return columnName == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : columnName;
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java b/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java
index 867c572..380153f 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnNameBuilder.java
@@ -52,6 +52,11 @@ public interface ColumnNameBuilder
public int componentCount();
/**
+ * @return the maximum number of component that can still be added to this Builder
+ */
+ public int remainingCount();
+
+ /**
* Build the column name.
* @return the built column name
*/
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g
index e6aa09e..cb5a265 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -200,7 +200,7 @@ selectCountClause returns [List<Selector> expr]
whereClause returns [List<Relation> clause]
@init{ $clause = new ArrayList<Relation>(); }
- : first=relation { $clause.add(first); } (K_AND next=relation { $clause.add(next); })*
+ : relation[$clause] (K_AND relation[$clause])*
;
orderByClause[Map<ColumnIdentifier, Boolean> orderings]
@@ -370,8 +370,13 @@ cfamDefinition[CreateColumnFamilyStatement.RawStatement 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); } )* ')'
+ : k=cident v=comparatorType { $expr.addDefinition(k, v); } (K_PRIMARY K_KEY { $expr.addKeyAlias(k); })?
+ | K_PRIMARY K_KEY '(' pkDef[expr] (',' c=cident { $expr.addColumnAlias(c); } )* ')'
+ ;
+
+pkDef[CreateColumnFamilyStatement.RawStatement expr]
+ : k=cident { $expr.addKeyAlias(k); }
+ | '(' k1=cident { $expr.addKeyAlias(k1); } ( ',' kn=cident { $expr.addKeyAlias(kn); } )* ')'
;
cfamProperty[CreateColumnFamilyStatement.RawStatement expr]
@@ -494,11 +499,6 @@ map_literal returns [Map<Term, Term> value]
{ $value = m; }
;
-extendedTerm returns [Term term]
- : K_TOKEN '(' t=term ')' { $term = Term.tokenOf(t); }
- | t=term { $term = t; }
- ;
-
term returns [Term term]
: t=(STRING_LITERAL | UUID | INTEGER | FLOAT ) { $term = new Term($t.text, $t.type); }
| t=QMARK { $term = new Term($t.text, $t.type, ++currentBindMarkerIdx); }
@@ -568,11 +568,35 @@ properties returns [Map<String, String> props]
: 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); }
- | K_TOKEN '(' name=cident ')' type=('=' |'<' | '<=' | '>=' | '>') t=extendedTerm { $rel = new Relation($name.id, $type.text, $t.term, true); }
- | name=cident K_IN { $rel = Relation.createInRelation($name.id); }
- '(' f1=term { $rel.addInValue(f1); } (',' fN=term { $rel.addInValue(fN); } )* ')'
+// Either a string or a list of terms
+tokenDefinition returns [Pair<String, List<Term>> tkdef]
+ : K_TOKEN { List<Term> l = new ArrayList<Term>(); }
+ '(' t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* ')' { $tkdef = Pair.<String, List<Term>>create(null, l); }
+ | t=STRING_LITERAL { $tkdef = Pair.<String, List<Term>>create($t.text, null); }
+ ;
+
+relation[List<Relation> clauses]
+ : name=cident type=('=' | '<' | '<=' | '>=' | '>') t=term { $clauses.add(new Relation($name.id, $type.text, $t.term)); }
+ | K_TOKEN { List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>(); }
+ '(' name1=cident { l.add(name1); } ( ',' namen=cident { l.add(namen); })* ')'
+ type=('=' |'<' | '<=' | '>=' | '>') tkd=tokenDefinition
+ {
+ if (tkd.right != null && tkd.right.size() != l.size())
+ {
+ addRecognitionError("The number of arguments to the token() function don't match");
+ }
+ else
+ {
+ Term str = tkd.left == null ? null : new Term(tkd.left, Term.Type.STRING);
+ for (int i = 0; i < l.size(); i++)
+ {
+ Term tt = str == null ? Term.tokenOf(tkd.right.get(i)) : str;
+ $clauses.add(new Relation(l.get(i), $type.text, tt, true));
+ }
+ }
+ }
+ | name=cident K_IN { Relation rel = Relation.createInRelation($name.id); }
+ '(' f1=term { rel.addInValue(f1); } (',' fN=term { rel.addInValue(fN); } )* ')' { $clauses.add(rel); }
;
comparatorType returns [ParsedType t]
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/Term.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java
index 4cece06..9be6499 100644
--- a/src/java/org/apache/cassandra/cql3/Term.java
+++ b/src/java/org/apache/cassandra/cql3/Term.java
@@ -136,30 +136,6 @@ public class Term
}
}
- public Token getAsToken(AbstractType<?> validator, List<ByteBuffer> variables, IPartitioner<?> p) throws InvalidRequestException
- {
- if (!(isToken || type == Type.STRING))
- throw new InvalidRequestException("Invalid value for token (use a string literal of the token value or the token() function)");
-
- try
- {
- if (isToken)
- {
- ByteBuffer value = getByteBuffer(validator, variables);
- return p.getToken(value);
- }
- else
- {
- p.getTokenFactory().validate(text);
- return p.getTokenFactory().fromString(text);
- }
- }
- catch (ConfigurationException e)
- {
- throw new InvalidRequestException(e.getMessage());
- }
- }
-
/**
* Obtain the term's type.
*
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
index 26ad6cc..ff24945 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
@@ -112,14 +112,21 @@ public class AlterTableStatement extends SchemaAlteringStatement
AbstractType<?> newType = validator.getType();
if (newType instanceof CounterColumnType)
throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", columnName));
- cfm.keyValidator(newType);
+ if (cfDef.hasCompositeKey)
+ {
+ List<AbstractType<?>> newTypes = new ArrayList<AbstractType<?>>(((CompositeType) cfm.getKeyValidator()).types);
+ newTypes.set(name.position, newType);
+ cfm.keyValidator(CompositeType.getInstance(newTypes));
+ }
+ else
+ {
+ cfm.keyValidator(newType);
+ }
break;
case COLUMN_ALIAS:
assert cfDef.isComposite;
-
List<AbstractType<?>> newTypes = new ArrayList<AbstractType<?>>(((CompositeType) cfm.comparator).types);
newTypes.set(name.position, validator.getType());
-
cfm.comparator = CompositeType.getInstance(newTypes);
break;
case VALUE_ALIAS:
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java
index 32caee9..0f14ba1 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateColumnFamilyStatement.java
@@ -51,7 +51,7 @@ public class CreateColumnFamilyStatement extends SchemaAlteringStatement
private AbstractType<?> defaultValidator;
private AbstractType<?> keyValidator;
- private ByteBuffer keyAlias;
+ private final List<ByteBuffer> keyAliases = new ArrayList<ByteBuffer>();
private final List<ByteBuffer> columnAliases = new ArrayList<ByteBuffer>();
private ByteBuffer valueAlias;
@@ -121,7 +121,7 @@ public class CreateColumnFamilyStatement extends SchemaAlteringStatement
cfmd.defaultValidator(defaultValidator)
.columnMetadata(getColumns())
.keyValidator(keyValidator)
- .keyAlias(keyAlias)
+ .keyAliases(keyAliases)
.columnAliases(columnAliases)
.valueAlias(valueAlias);
@@ -181,16 +181,20 @@ public class CreateColumnFamilyStatement extends SchemaAlteringStatement
stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below
}
- // Ensure that exactly one key has been specified.
+ // Ensure that at least 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));
- if (stmt.keyValidator instanceof CounterColumnType)
- throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", stmt.keyAlias));
+ List<AbstractType<?>> keyTypes = new ArrayList<AbstractType<?>>(keyAliases.size());
+ for (ColumnIdentifier alias : keyAliases)
+ {
+ stmt.keyAliases.add(alias.key);
+ AbstractType<?> t = getTypeAndRemove(stmt.columns, alias);
+ if (t instanceof CounterColumnType)
+ throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", alias));
+ keyTypes.add(t);
+ }
+ stmt.keyValidator = keyTypes.size() == 1 ? keyTypes.get(0) : CompositeType.getInstance(keyTypes);
// Handle column aliases
if (columnAliases.isEmpty())
@@ -319,7 +323,7 @@ public class CreateColumnFamilyStatement extends SchemaAlteringStatement
definitions.put(def, type);
}
- public void setKeyAlias(ColumnIdentifier alias)
+ public void addKeyAlias(ColumnIdentifier alias)
{
keyAliases.add(alias);
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
index 1e6a9a2..0fb97fc 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java
@@ -65,30 +65,12 @@ public class DeleteStatement extends ModificationStatement
public List<IMutation> getMutations(ClientState clientState, List<ByteBuffer> variables) throws UnavailableException, TimeoutException, InvalidRequestException
{
- // 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));
+ // keys
+ List<ByteBuffer> keys = UpdateStatement.buildKeyNames(cfDef, processedKeys, variables);
+ // columns
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;
- }
- 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);
- }
- }
+ CFDefinition.Name firstEmpty = UpdateStatement.buildColumnNames(cfDef, processedKeys, builder, variables, false);
boolean fullKey = builder.componentCount() == cfDef.columns.size();
boolean isRange = cfDef.isCompact ? !fullKey : (!fullKey || toRemove.isEmpty());
@@ -110,16 +92,12 @@ public class DeleteStatement extends ModificationStatement
}
}
- List<ByteBuffer> rawKeys = new ArrayList<ByteBuffer>(keys.size());
- for (Term key: keys)
- rawKeys.add(key.getByteBuffer(cfDef.key.type, variables));
-
- Map<ByteBuffer, ColumnGroupMap> rows = needsReading ? readRows(rawKeys, builder, (CompositeType)cfDef.cfm.comparator) : null;
+ Map<ByteBuffer, ColumnGroupMap> rows = needsReading ? readRows(keys, builder, (CompositeType)cfDef.cfm.comparator) : null;
List<IMutation> rowMutations = new ArrayList<IMutation>(keys.size());
UpdateParameters params = new UpdateParameters(variables, getTimestamp(clientState), -1);
- for (ByteBuffer key : rawKeys)
+ for (ByteBuffer key : keys)
rowMutations.add(mutationForKey(cfDef, key, builder, isRange, params, rows == null ? null : rows.get(key)));
return rowMutations;
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 84b0f74..1649a8c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -30,6 +30,7 @@ import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.config.CFMetaData;
+import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.filter.*;
@@ -64,12 +65,15 @@ public class SelectStatement implements CQLStatement
public final Parameters parameters;
private final List<Pair<CFDefinition.Name, Selector>> selectedNames = new ArrayList<Pair<CFDefinition.Name, Selector>>(); // empty => wildcard
- private Restriction keyRestriction;
+ private final Restriction[] keyRestrictions;
private final Restriction[] columnRestrictions;
private final Map<CFDefinition.Name, Restriction> metadataRestrictions = new HashMap<CFDefinition.Name, Restriction>();
private Restriction sliceRestriction;
private boolean isReversed;
+ private boolean onToken;
+ private boolean isKeyRange;
+ private boolean keyIsInRelation;
private static enum Bound
{
@@ -86,6 +90,7 @@ public class SelectStatement implements CQLStatement
{
this.cfDef = cfDef;
this.boundTerms = boundTerms;
+ this.keyRestrictions = new Restriction[cfDef.keys.size()];
this.columnRestrictions = new Restriction[cfDef.columns.size()];
this.parameters = parameters;
}
@@ -115,7 +120,7 @@ public class SelectStatement implements CQLStatement
try
{
List<Row> rows;
- if (isKeyRange())
+ if (isKeyRange)
{
rows = multiRangeSlice(variables);
}
@@ -221,7 +226,7 @@ public class SelectStatement implements CQLStatement
IPartitioner<?> p = StorageService.getPartitioner();
AbstractBounds<RowPosition> bounds;
- if (keyRestriction != null && keyRestriction.onToken)
+ if (onToken)
{
Token startToken = getTokenBound(Bound.START, variables, p);
Token endToken = getTokenBound(Bound.END, variables, p);
@@ -296,64 +301,82 @@ public class SelectStatement implements CQLStatement
return sliceRestriction != null && !sliceRestriction.isInclusive(Bound.START) ? parameters.limit + 1 : parameters.limit;
}
- private boolean isKeyRange()
- {
- // If indexed columns or a token range, they always use getRangeSlices
- if (!metadataRestrictions.isEmpty())
- return true;
-
- return keyRestriction == null || !keyRestriction.isEquality() || keyRestriction.onToken;
- }
-
private Collection<ByteBuffer> getKeys(final List<ByteBuffer> variables) throws InvalidRequestException
{
- assert keyRestriction != null && keyRestriction.isEquality();
-
- List<ByteBuffer> keys = new ArrayList<ByteBuffer>(keyRestriction.eqValues.size());
- for (Term t : keyRestriction.eqValues)
- keys.add(t.getByteBuffer(cfDef.key.type, variables));
+ List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
+ ColumnNameBuilder builder = cfDef.getKeyNameBuilder();
+ for (CFDefinition.Name name : cfDef.keys.values())
+ {
+ Restriction r = keyRestrictions[name.position];
+ assert r != null;
+ if (builder.remainingCount() == 1)
+ {
+ for (Term t : r.eqValues)
+ keys.add(builder.copy().add(t, Relation.Type.EQ, variables).build());
+ }
+ else
+ {
+ if (r.eqValues.size() > 1)
+ throw new InvalidRequestException("IN is only supported on the last column of the partition key");
+ builder.add(r.eqValues.get(0), Relation.Type.EQ, variables);
+ }
+ }
return keys;
}
private ByteBuffer getKeyBound(Bound b, List<ByteBuffer> variables) throws InvalidRequestException
{
- if (keyRestriction == null)
- {
- return ByteBufferUtil.EMPTY_BYTE_BUFFER;
- }
- else if (keyRestriction.isEquality())
- {
- assert keyRestriction.eqValues.size() == 1;
- return keyRestriction.eqValues.get(0).getByteBuffer(cfDef.key.type, variables);
- }
- else
- {
- Term bound = keyRestriction.bound(b);
- return bound == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : bound.getByteBuffer(cfDef.key.type, variables);
- }
+ return buildBound(b, keyRestrictions, cfDef.getKeyNameBuilder(), variables);
}
private Token getTokenBound(Bound b, List<ByteBuffer> variables, IPartitioner<?> p) throws InvalidRequestException
{
- assert keyRestriction != null;
- if (keyRestriction.isEquality())
+ assert onToken;
+
+ Restriction keyRestriction = keyRestrictions[0];
+ Term t = keyRestriction.isEquality()
+ ? keyRestriction.eqValues.get(0)
+ : keyRestriction.bound(b);
+
+ if (t == null)
+ return p.getMinimumToken();
+
+ if (t.getType() == Term.Type.STRING)
{
- assert keyRestriction.eqValues.size() == 1;
- return keyRestriction.eqValues.get(0).getAsToken(cfDef.key.type, variables, p);
+ try
+ {
+ String text = t.getText();
+ p.getTokenFactory().validate(text);
+ return p.getTokenFactory().fromString(text);
+ }
+ catch (ConfigurationException e)
+ {
+ throw new InvalidRequestException(e.getMessage());
+ }
}
- else
+
+ assert t.isToken;
+ ColumnNameBuilder builder = cfDef.getKeyNameBuilder();
+ // We know all keyRestriction must be set
+ for (CFDefinition.Name name : cfDef.keys.values())
{
- Term bound = keyRestriction.bound(b);
- return bound == null ? p.getMinimumToken() : bound.getAsToken(cfDef.key.type, variables, p);
+ Restriction r = keyRestrictions[name.position];
+ builder.add(r.isEquality() ? r.eqValues.get(0) : r.bound(b), Relation.Type.EQ, variables);
}
+ return p.getToken(builder.build());
}
private boolean includeKeyBound(Bound b)
{
- if (keyRestriction == null || keyRestriction.isEquality())
- return true;
- else
- return keyRestriction.isInclusive(b);
+ for (Restriction r : keyRestrictions)
+ {
+ if (r == null)
+ return true;
+ else if (!r.isEquality())
+ return r.isInclusive(b);
+ }
+ // All equality
+ return true;
}
private boolean isColumnRange()
@@ -450,12 +473,9 @@ public class SelectStatement implements CQLStatement
}
}
- private ByteBuffer getRequestedBound(Bound b, List<ByteBuffer> variables) throws InvalidRequestException
+ private ByteBuffer buildBound(Bound b, Restriction[] restrictions, ColumnNameBuilder builder, List<ByteBuffer> variables) throws InvalidRequestException
{
- assert isColumnRange();
-
- ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
- for (Restriction r : columnRestrictions)
+ for (Restriction r : restrictions)
{
if (r == null || (!r.isEquality() && r.bound(b) == null))
{
@@ -484,6 +504,12 @@ public class SelectStatement implements CQLStatement
return (b == Bound.END) ? builder.buildAsEndOfRange() : builder.build();
}
+ private ByteBuffer getRequestedBound(Bound b, List<ByteBuffer> variables) throws InvalidRequestException
+ {
+ assert isColumnRange();
+ return buildBound(b, columnRestrictions, cfDef.getColumnNameBuilder(), variables);
+ }
+
private List<IndexExpression> getIndexExpressions(List<ByteBuffer> variables) throws InvalidRequestException
{
if (metadataRestrictions.isEmpty())
@@ -649,6 +675,16 @@ public class SelectStatement implements CQLStatement
if (row.cf == null)
continue;
+ ByteBuffer[] keyComponents = null;
+ if (cfDef.hasCompositeKey)
+ {
+ keyComponents = ((CompositeType)cfDef.cfm.getKeyValidator()).split(row.key.key);
+ }
+ else
+ {
+ keyComponents = new ByteBuffer[]{ row.key.key };
+ }
+
if (cfDef.isCompact)
{
// One cqlRow per column
@@ -658,7 +694,6 @@ public class SelectStatement implements CQLStatement
continue;
ByteBuffer[] components = null;
-
if (cfDef.isComposite)
{
components = ((CompositeType)cfDef.cfm.comparator).split(c.name());
@@ -680,7 +715,7 @@ public class SelectStatement implements CQLStatement
switch (name.kind)
{
case KEY_ALIAS:
- cqlRows.addColumnValue(row.key.key);
+ cqlRows.addColumnValue(keyComponents[name.position]);
break;
case COLUMN_ALIAS:
if (cfDef.isComposite)
@@ -723,7 +758,7 @@ public class SelectStatement implements CQLStatement
}
for (ColumnGroupMap group : builder.groups())
- handleGroup(selection, row.key.key, group, cqlRows);
+ handleGroup(selection, row.key.key, keyComponents, group, cqlRows);
}
else
{
@@ -738,7 +773,7 @@ public class SelectStatement implements CQLStatement
Selector selector = p.right;
if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS)
{
- cqlRows.addColumnValue(row.key.key);
+ cqlRows.addColumnValue(keyComponents[name.position]);
continue;
}
@@ -767,8 +802,8 @@ public class SelectStatement implements CQLStatement
// There is nothing to do if
// a. there are no results,
// b. no ordering information where given,
- // c. key restriction wasn't given or it's not an IN expression
- if (cqlRows.size() == 0 || parameters.orderings.isEmpty() || keyRestriction == null || keyRestriction.eqValues.size() < 2)
+ // c. key restriction is a Range or not an IN expression
+ if (cqlRows.size() == 0 || parameters.orderings.isEmpty() || isKeyRange || !keyIsInRelation)
return;
// optimization when only *one* order condition was given
@@ -800,7 +835,7 @@ public class SelectStatement implements CQLStatement
Collections.sort(cqlRows.rows, new CompositeComparator(startPosition, types));
}
- private void handleGroup(List<Pair<CFDefinition.Name, Selector>> selection, ByteBuffer key, ColumnGroupMap columns, ResultSet cqlRows)
+ private void handleGroup(List<Pair<CFDefinition.Name, Selector>> selection, ByteBuffer key, ByteBuffer[] keyComponents, ColumnGroupMap columns, ResultSet cqlRows)
{
// Respect requested order
for (Pair<CFDefinition.Name, Selector> p : selection)
@@ -810,7 +845,7 @@ public class SelectStatement implements CQLStatement
switch (name.kind)
{
case KEY_ALIAS:
- cqlRows.addColumnValue(key);
+ cqlRows.addColumnValue(keyComponents[name.position]);
break;
case COLUMN_ALIAS:
cqlRows.addColumnValue(columns.getKeyComponent(name.position));
@@ -914,8 +949,8 @@ public class SelectStatement implements CQLStatement
{
case KEY_ALIAS:
if (rel.operator() != Relation.Type.EQ && rel.operator() != Relation.Type.IN && !rel.onToken && !StorageService.getPartitioner().preservesOrder())
- throw new InvalidRequestException("Only EQ and IN relation are supported on first component of the PRIMARY KEY for RandomPartitioner (unless you use the token() function)");
- stmt.keyRestriction = updateRestriction(name, stmt.keyRestriction, rel);
+ throw new InvalidRequestException("Only EQ and IN relation are supported on the partition key for RandomPartitioner (unless you use the token() function)");
+ stmt.keyRestrictions[name.position] = updateRestriction(name, stmt.keyRestrictions[name.position], rel);
break;
case COLUMN_ALIAS:
stmt.columnRestrictions[name.position] = updateRestriction(name, stmt.columnRestrictions[name.position], rel);
@@ -967,9 +1002,63 @@ public class SelectStatement implements CQLStatement
previous = cname;
}
+ // If a component of the partition key is restricted by a non-EQ relation, all preceding
+ // components must have a EQ, and all following must have no restriction
+ shouldBeDone = false;
+ previous = null;
+ stmt.keyIsInRelation = false;
+ iter = cfDef.keys.values().iterator();
+ for (int i = 0; i < stmt.keyRestrictions.length; i++)
+ {
+ CFDefinition.Name cname = iter.next();
+ Restriction restriction = stmt.keyRestrictions[i];
+ if (restriction == null)
+ {
+ if (stmt.onToken)
+ throw new InvalidRequestException(String.format("The token() function must be applied to all partition key components or none of them"));
+
+ stmt.isKeyRange = true;
+ shouldBeDone = true;
+ }
+ else if (shouldBeDone)
+ {
+ throw new InvalidRequestException(String.format("partition key part %s cannot be restricted (preceding part %s is either not restricted or by a non-EQ relation)", cname, previous));
+ }
+ else if (restriction.onToken)
+ {
+ // If this is a query on tokens, it's necessary a range query (there can be more than one key per token), so reject IN queries (as we don't know how to do them)
+ stmt.isKeyRange = true;
+ stmt.onToken = true;
+
+ if (restriction.isEquality() && restriction.eqValues.size() > 1)
+ throw new InvalidRequestException("Select using the token() function don't support IN clause");
+ }
+ else if (stmt.onToken)
+ {
+ throw new InvalidRequestException(String.format("The token() function must be applied to all partition key components or none of them"));
+ }
+ else if (restriction.isEquality())
+ {
+ if (restriction.eqValues.size() > 1)
+ {
+ // We only support IN for the last name so far
+ if (i != stmt.keyRestrictions.length - 1)
+ throw new InvalidRequestException(String.format("partition KEY part %s cannot be restricted by IN relation (only the last part of the partition key can)", cname));
+ stmt.keyIsInRelation = true;
+ }
+ }
+ else
+ {
+ stmt.isKeyRange = true;
+ shouldBeDone = true;
+ }
+ previous = cname;
+ }
+
// Deal with indexed columns
if (!stmt.metadataRestrictions.isEmpty())
{
+ stmt.isKeyRange = true;
boolean hasEq = false;
Set<ByteBuffer> indexed = Table.open(keyspace()).getColumnFamilyStore(columnFamily()).indexManager.getIndexedColumns();
@@ -985,7 +1074,7 @@ public class SelectStatement implements CQLStatement
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 will do a range query, but if it's a IN relation, we don't know how to handle it.
- if (stmt.keyRestriction != null && stmt.keyRestriction.isEquality() && stmt.keyRestriction.eqValues.size() > 1)
+ if (stmt.keyIsInRelation)
throw new InvalidRequestException("Select on indexed columns and with IN clause for the PRIMARY KEY are not supported");
}
@@ -1034,9 +1123,6 @@ public class SelectStatement implements CQLStatement
stmt.isReversed = isReversed;
}
- // If this is a query on tokens, it's necessary a range query (there can be more than one key per token), so reject IN queries (as we don't know how to do them)
- if (stmt.keyRestriction != null && stmt.keyRestriction.onToken && stmt.keyRestriction.isEquality() && stmt.keyRestriction.eqValues.size() > 1)
- throw new InvalidRequestException("Select using the token() function don't support IN clause");
return new ParsedStatement.Prepared(stmt, Arrays.<ColumnSpecification>asList(names));
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
index 908c321..d34e294 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java
@@ -104,37 +104,10 @@ public class UpdateStatement extends ModificationStatement
/** {@inheritDoc} */
public List<IMutation> getMutations(ClientState clientState, List<ByteBuffer> variables) throws UnavailableException, TimeoutException, InvalidRequestException
{
- // 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));
+ List<ByteBuffer> keys = buildKeyNames(cfDef, processedKeys, variables);
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<ByteBuffer> rawKeys = new ArrayList<ByteBuffer>(keys.size());
- for (Term key: keys)
- rawKeys.add(key.getByteBuffer(cfDef.key.type, variables));
+ buildColumnNames(cfDef, processedKeys, builder, variables, true);
// Lists SET operation incurs a read. Do that now. Note that currently,
// if there is at least one list, we just read the whole "row" (in the CQL sense of
@@ -155,17 +128,70 @@ public class UpdateStatement extends ModificationStatement
}
}
- Map<ByteBuffer, ColumnGroupMap> rows = needsReading ? readRows(rawKeys, builder, (CompositeType)cfDef.cfm.comparator) : null;
+ Map<ByteBuffer, ColumnGroupMap> rows = needsReading ? readRows(keys, builder, (CompositeType)cfDef.cfm.comparator) : null;
List<IMutation> rowMutations = new LinkedList<IMutation>();
UpdateParameters params = new UpdateParameters(variables, getTimestamp(clientState), timeToLive);
- for (ByteBuffer key: rawKeys)
+ for (ByteBuffer key: keys)
rowMutations.add(mutationForKey(cfDef, key, builder, params, rows == null ? null : rows.get(key)));
return rowMutations;
}
+ // Returns the first empty component or null if none are
+ static CFDefinition.Name buildColumnNames(CFDefinition cfDef, Map<ColumnIdentifier, List<Term>> processed, ColumnNameBuilder builder, List<ByteBuffer> variables, boolean requireAllComponent)
+ throws InvalidRequestException
+ {
+ CFDefinition.Name firstEmpty = null;
+ for (CFDefinition.Name name : cfDef.columns.values())
+ {
+ List<Term> values = processed.get(name.name);
+ if (values == null || values.isEmpty())
+ {
+ firstEmpty = name;
+ if (requireAllComponent && 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);
+ }
+ }
+ return firstEmpty;
+ }
+
+ static List<ByteBuffer> buildKeyNames(CFDefinition cfDef, Map<ColumnIdentifier, List<Term>> processed, List<ByteBuffer> variables)
+ throws InvalidRequestException
+ {
+ ColumnNameBuilder keyBuilder = cfDef.getKeyNameBuilder();
+ List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
+ for (CFDefinition.Name name : cfDef.keys.values())
+ {
+ List<Term> values = processed.get(name.name);
+ if (values == null || values.isEmpty())
+ throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
+
+ if (keyBuilder.remainingCount() == 1)
+ {
+ for (Term t : values)
+ keys.add(keyBuilder.copy().add(t, Relation.Type.EQ, variables).build());
+ }
+ else
+ {
+ if (values.size() > 1)
+ throw new InvalidRequestException("IN is only supported on the last column of the partition key");
+ keyBuilder.add(values.get(0), Relation.Type.EQ, variables);
+ }
+ }
+ return keys;
+ }
+
/**
* Compute a row mutation for a single key
*
http://git-wip-us.apache.org/repos/asf/cassandra/blob/daac1edc/src/java/org/apache/cassandra/db/marshal/CompositeType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/CompositeType.java b/src/java/org/apache/cassandra/db/marshal/CompositeType.java
index 1c8283a..a834d94 100644
--- a/src/java/org/apache/cassandra/db/marshal/CompositeType.java
+++ b/src/java/org/apache/cassandra/db/marshal/CompositeType.java
@@ -273,6 +273,11 @@ public class CompositeType extends AbstractCompositeType
return components.size();
}
+ public int remainingCount()
+ {
+ return composite.types.size() - components.size();
+ }
+
public ByteBuffer build()
{
DataOutputBuffer out = new DataOutputBuffer(serializedSize);