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