Repository: cassandra
Updated Branches:
  refs/heads/trunk 72d187c0f -> 525ac00c0


Fix issues w/ CONTAINS (KEY) queries on 2ary indexes

Patch by Benjamin Lerer; reviewed by Tyler Hobbs for CASSANDRA-8147


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/52a701f2
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/52a701f2
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/52a701f2

Branch: refs/heads/trunk
Commit: 52a701f295490cf10850b2a6ad8fb3fdcbc57211
Parents: 684b4f9
Author: blerer <b_le...@hotmail.com>
Authored: Tue Nov 4 10:16:57 2014 -0600
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Tue Nov 4 10:16:57 2014 -0600

----------------------------------------------------------------------
 CHANGES.txt                                     |  2 +
 pylib/cqlshlib/cql3handling.py                  |  4 +-
 .../org/apache/cassandra/config/CFMetaData.java |  2 +-
 .../cassandra/config/ColumnDefinition.java      | 12 +++++
 .../org/apache/cassandra/cql3/Relation.java     | 57 ++++++++++++++++----
 .../cql3/statements/CreateIndexStatement.java   |  6 ++-
 .../cql3/statements/SelectStatement.java        |  2 +-
 .../apache/cassandra/db/IndexExpression.java    | 44 +++++++++++++++
 .../cassandra/db/index/SecondaryIndex.java      | 23 +++++++-
 .../db/index/SecondaryIndexManager.java         |  2 +-
 .../db/index/SecondaryIndexSearcher.java        |  7 +--
 .../db/index/composites/CompositesIndex.java    |  9 ++--
 .../CompositesIndexOnCollectionKey.java         |  7 +++
 .../CompositesIndexOnCollectionValue.java       |  6 +++
 .../cassandra/db/marshal/CollectionType.java    |  9 ++++
 .../cassandra/cql3/ContainsRelationTest.java    | 44 +++++++++++++++
 16 files changed, 211 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 37b3f83..7fb32fe 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,6 @@
 2.1.2
+ * Fix issues with CONTAINS (KEY) queries on secondary indexes
+   (CASSANDRA-8147)
  * Fix read-rate tracking of sstables for some queries (CASSANDRA-8239)
  * Fix default timestamp in QueryOptions (CASSANDRA-8246)
  * Set socket timeout when reading remote version (CASSANDRA-8188)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/pylib/cqlshlib/cql3handling.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index 8d2fec5..e12e7e1 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -599,10 +599,10 @@ syntax_rules += r'''
                     ;
 <whereClause> ::= <relation> ( "AND" <relation> )*
                 ;
-<relation> ::= [rel_lhs]=<cident> ( "=" | "<" | ">" | "<=" | ">=" | "CONTAINS" 
) <term>
+<relation> ::= [rel_lhs]=<cident> ( "=" | "<" | ">" | "<=" | ">=" | "CONTAINS" 
( "KEY" )? ) <term>
              | token="TOKEN" "(" [rel_tokname]=<cident>
                                  ( "," [rel_tokname]=<cident> )*
-                             ")" ("=" | "<" | ">" | "<=" | ">=" | "CONTAINS") 
<tokenDefinition>
+                             ")" ("=" | "<" | ">" | "<=" | ">=") 
<tokenDefinition>
              | [rel_lhs]=<cident> "IN" "(" <term> ( "," <term> )* ")"
              ;
 <selectClause> ::= "DISTINCT"? <selector> ("AS" <cident>)? ("," <selector> 
("AS" <cident>)?)*

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/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 b5784ed..d986c40 100644
--- a/src/java/org/apache/cassandra/config/CFMetaData.java
+++ b/src/java/org/apache/cassandra/config/CFMetaData.java
@@ -1540,7 +1540,7 @@ public final class CFMetaData
 
                 if (c.getIndexType() == IndexType.CUSTOM)
                 {
-                    if (c.getIndexOptions() == null || 
!c.getIndexOptions().containsKey(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME))
+                    if (c.getIndexOptions() == null || 
!c.hasIndexOption(SecondaryIndex.CUSTOM_INDEX_OPTION_NAME))
                         throw new ConfigurationException("Required index 
option missing: " + SecondaryIndex.CUSTOM_INDEX_OPTION_NAME);
                 }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/config/ColumnDefinition.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/ColumnDefinition.java 
b/src/java/org/apache/cassandra/config/ColumnDefinition.java
index cbb3e75..ff66162 100644
--- a/src/java/org/apache/cassandra/config/ColumnDefinition.java
+++ b/src/java/org/apache/cassandra/config/ColumnDefinition.java
@@ -476,4 +476,16 @@ public class ColumnDefinition extends ColumnSpecification
     {
         return indexOptions;
     }
+
+    /**
+     * Checks if the index option with the specified name has been specified.
+     *
+     * @param name index option name
+     * @return <code>true</code> if the index option with the specified name 
has been specified, <code>false</code>
+     * otherwise.
+     */
+    public boolean hasIndexOption(String name)
+    {
+        return indexOptions.containsKey(name);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/cql3/Relation.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Relation.java 
b/src/java/org/apache/cassandra/cql3/Relation.java
index 42373c3..c6a7c65 100644
--- a/src/java/org/apache/cassandra/cql3/Relation.java
+++ b/src/java/org/apache/cassandra/cql3/Relation.java
@@ -17,6 +17,10 @@
  */
 package org.apache.cassandra.cql3;
 
+import org.apache.cassandra.config.ColumnDefinition;
+import org.apache.cassandra.db.index.SecondaryIndex;
+import org.apache.cassandra.db.marshal.CollectionType;
+
 
 public abstract class Relation {
 
@@ -24,19 +28,50 @@ public abstract class Relation {
 
     public static enum Type
     {
-        EQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY, NEQ;
-
-        public boolean allowsIndexQuery()
+        EQ
         {
-            switch (this)
+            public boolean allowsIndexQueryOn(ColumnDefinition columnDef)
             {
-                case EQ:
-                case CONTAINS:
-                case CONTAINS_KEY:
-                    return true;
-                default:
-                    return false;
+                return columnDef.isIndexed();
             }
+        },
+        LT,
+        LTE,
+        GTE,
+        GT,
+        IN,
+        CONTAINS
+        {
+            public boolean allowsIndexQueryOn(ColumnDefinition columnDef)
+            {
+                return columnDef.isIndexed()
+                        && columnDef.type.isCollection()
+                        && (!((CollectionType<?>) columnDef.type).isMap()
+                                || 
columnDef.hasIndexOption(SecondaryIndex.INDEX_VALUES_OPTION_NAME));
+            }
+        },
+        CONTAINS_KEY
+        {
+            public boolean allowsIndexQueryOn(ColumnDefinition columnDef)
+            {
+                return columnDef.isIndexed()
+                        && columnDef.type.isCollection()
+                        && (!((CollectionType<?>) columnDef.type).isMap()
+                                || 
columnDef.hasIndexOption(SecondaryIndex.INDEX_KEYS_OPTION_NAME));
+            }
+        },
+        NEQ;
+
+        /**
+         * Checks if this relation type allow index queries on the specified 
column
+         *
+         * @param columnDef the column definition.
+         * @return <code>true</code> if this relation type allow index queries 
on the specified column,
+         * <code>false</code> otherwise.
+         */
+        public boolean allowsIndexQueryOn(ColumnDefinition columnDef)
+        {
+            return false;
         }
 
         @Override
@@ -56,6 +91,8 @@ public abstract class Relation {
                     return ">=";
                 case NEQ:
                     return "!=";
+                case CONTAINS_KEY:
+                    return "CONTAINS KEY";
                 default:
                     return this.name();
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
index 4809187..fc5c4d1 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
@@ -29,6 +29,7 @@ import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.config.IndexType;
 import org.apache.cassandra.config.Schema;
+import org.apache.cassandra.db.index.SecondaryIndex;
 import org.apache.cassandra.db.marshal.MapType;
 import org.apache.cassandra.exceptions.*;
 import org.apache.cassandra.cql3.*;
@@ -82,7 +83,7 @@ public class CreateIndexStatement extends 
SchemaAlteringStatement
 
         if (cd.getIndexType() != null)
         {
-            boolean previousIsKeys = 
cd.getIndexOptions().containsKey("index_keys");
+            boolean previousIsKeys = 
cd.hasIndexOption(SecondaryIndex.INDEX_KEYS_OPTION_NAME);
             if (isMap && target.isCollectionKeys != previousIsKeys)
             {
                 String msg = "Cannot create index on %s %s, an index on %s %s 
already exists and indexing "
@@ -137,7 +138,8 @@ public class CreateIndexStatement extends 
SchemaAlteringStatement
             // to also index map keys, so we record that this is the values we 
index to make our
             // lives easier then.
             if (cd.type.isCollection())
-                options = ImmutableMap.of(target.isCollectionKeys ? 
"index_keys" : "index_values", "");
+                options = ImmutableMap.of(target.isCollectionKeys ? 
SecondaryIndex.INDEX_KEYS_OPTION_NAME
+                                                                  : 
SecondaryIndex.INDEX_VALUES_OPTION_NAME, "");
             cd.setIndexType(IndexType.COMPOSITES, options);
         }
         else

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/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 3d57f4c..3354c0b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -1496,7 +1496,7 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
                 handleUnrecognizedEntity(entity, relation);
 
             stmt.restrictedColumns.add(def);
-            if (def.isIndexed() && relation.operator().allowsIndexQuery())
+            if (relation.operator().allowsIndexQueryOn(def))
                 return new boolean[]{true, def.kind == 
ColumnDefinition.Kind.CLUSTERING_COLUMN};
             return new boolean[]{false, false};
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/IndexExpression.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/IndexExpression.java 
b/src/java/org/apache/cassandra/db/IndexExpression.java
index b57890a..910bfbc 100644
--- a/src/java/org/apache/cassandra/db/IndexExpression.java
+++ b/src/java/org/apache/cassandra/db/IndexExpression.java
@@ -76,6 +76,50 @@ public class IndexExpression
                     return false;
             }
         }
+
+        @Override
+        public String toString()
+        {
+            switch (this)
+            {
+                case EQ:
+                    return "=";
+                case LT:
+                    return "<";
+                case LTE:
+                    return "<=";
+                case GT:
+                    return ">";
+                case GTE:
+                    return ">=";
+                case CONTAINS_KEY:
+                    return "CONTAINS KEY";
+                default:
+                    return this.name();
+            }
+        }
+    }
+
+    /**
+     * Checks if the operator of this <code>IndexExpression</code> is a 
<code>CONTAINS</code> operator.
+     *
+     * @return <code>true</code> if the operator of this 
<code>IndexExpression</code> is a <code>CONTAINS</code>
+     * operator, <code>false</code> otherwise.
+     */
+    public boolean isContains()
+    {
+        return Operator.CONTAINS == operator;
+    }
+
+    /**
+     * Checks if the operator of this <code>IndexExpression</code> is a 
<code>CONTAINS_KEY</code> operator.
+     *
+     * @return <code>true</code> if the operator of this 
<code>IndexExpression</code> is a <code>CONTAINS_KEY</code>
+     * operator, <code>false</code> otherwise.
+     */
+    public boolean isContainsKey()
+    {
+        return Operator.CONTAINS_KEY == operator;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndex.java 
b/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
index 4d50fa6..e3ed73c 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndex.java
@@ -32,11 +32,14 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
+
 import org.apache.cassandra.db.BufferDecoratedKey;
 import org.apache.cassandra.db.Cell;
 import org.apache.cassandra.db.ColumnFamilyStore;
 import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.IndexExpression;
 import org.apache.cassandra.db.SystemKeyspace;
+
 import org.apache.cassandra.db.compaction.CompactionManager;
 import org.apache.cassandra.db.composites.CellName;
 import org.apache.cassandra.db.composites.CellNameType;
@@ -45,6 +48,7 @@ import 
org.apache.cassandra.db.index.composites.CompositesIndex;
 import org.apache.cassandra.db.index.keys.KeysIndex;
 import org.apache.cassandra.db.marshal.AbstractType;
 import org.apache.cassandra.db.marshal.BytesType;
+import org.apache.cassandra.db.marshal.CollectionType;
 import org.apache.cassandra.db.marshal.LocalByPartionerType;
 import org.apache.cassandra.dht.LocalToken;
 import org.apache.cassandra.exceptions.ConfigurationException;
@@ -52,7 +56,8 @@ import org.apache.cassandra.io.sstable.ReducingKeyIterator;
 import org.apache.cassandra.io.sstable.SSTableReader;
 import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.utils.FBUtilities;
-import org.apache.cassandra.utils.memory.MemtableAllocator;
+
+import com.google.common.collect.Iterables;
 
 /**
  * Abstract base class for different types of secondary indexes.
@@ -65,6 +70,16 @@ public abstract class SecondaryIndex
 
     public static final String CUSTOM_INDEX_OPTION_NAME = "class_name";
 
+    /**
+     * The name of the option used to specify that the index is on the 
collection keys.
+     */
+    public static final String INDEX_KEYS_OPTION_NAME = "index_keys";
+
+    /**
+     * The name of the option used to specify that the index is on the 
collection values.
+     */
+    public static final String INDEX_VALUES_OPTION_NAME = "index_values";
+
     public static final AbstractType<?> keyComparator = 
StorageService.getPartitioner().preservesOrder()
                                                       ? BytesType.instance
                                                       : new 
LocalByPartionerType(StorageService.getPartitioner());
@@ -281,6 +296,12 @@ public abstract class SecondaryIndex
         }
     }
 
+    /** Returns true if the index supports lookups for the given operator, 
false otherwise. */
+    public boolean supportsOperator(IndexExpression.Operator operator)
+    {
+        return operator == IndexExpression.Operator.EQ;
+    }
+
     /**
      * Returns the decoratedKey for a column value
      * @param value column value

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java 
b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
index c2d481b..976bbb8 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
@@ -528,7 +528,7 @@ public class SecondaryIndexManager
         {
             SecondaryIndex index = getIndexForColumn(ix.column);
 
-            if (index == null)
+            if (index == null || !index.supportsOperator(ix.operator))
                 continue;
 
             Set<ByteBuffer> columns = 
groupByIndexType.get(index.indexTypeForGrouping());

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java 
b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
index 1239c29..b9ccd8e 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
@@ -54,11 +54,11 @@ public abstract class SecondaryIndexSearcher
     {
         for (IndexExpression expression : clause)
         {
-            if (!columns.contains(expression.column) || 
!expression.operator.allowsIndexQuery())
+            if (!columns.contains(expression.column))
                 continue;
 
             SecondaryIndex index = 
indexManager.getIndexForColumn(expression.column);
-            if (index != null && index.getIndexCfs() != null)
+            if (index != null && index.getIndexCfs() != null && 
index.supportsOperator(expression.operator))
                 return true;
         }
         return false;
@@ -88,8 +88,9 @@ public abstract class SecondaryIndexSearcher
                 continue;
 
             SecondaryIndex index = 
indexManager.getIndexForColumn(expression.column);
-            if (index == null || index.getIndexCfs() == null || 
!expression.operator.allowsIndexQuery())
+            if (index == null || index.getIndexCfs() == null || 
!index.supportsOperator(expression.operator))
                 continue;
+
             int columns = index.getIndexCfs().getMeanColumns();
             candidates.put(index, columns);
             if (columns < bestMeanCount)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java 
b/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
index f69f716..410ea83 100644
--- a/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
+++ b/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java
@@ -30,6 +30,7 @@ import org.apache.cassandra.db.composites.CellName;
 import org.apache.cassandra.db.composites.CellNameType;
 import org.apache.cassandra.db.composites.Composite;
 import org.apache.cassandra.db.index.AbstractSimplePerColumnSecondaryIndex;
+import org.apache.cassandra.db.index.SecondaryIndex;
 import org.apache.cassandra.db.index.SecondaryIndexManager;
 import org.apache.cassandra.db.index.SecondaryIndexSearcher;
 import org.apache.cassandra.db.marshal.AbstractType;
@@ -66,7 +67,7 @@ public abstract class CompositesIndex extends 
AbstractSimplePerColumnSecondaryIn
                 case SET:
                     return new CompositesIndexOnCollectionKey();
                 case MAP:
-                    return cfDef.getIndexOptions().containsKey("index_keys")
+                    return 
cfDef.hasIndexOption(SecondaryIndex.INDEX_KEYS_OPTION_NAME)
                          ? new CompositesIndexOnCollectionKey()
                          : new CompositesIndexOnCollectionValue();
             }
@@ -98,7 +99,7 @@ public abstract class CompositesIndex extends 
AbstractSimplePerColumnSecondaryIn
                 case SET:
                     return 
CompositesIndexOnCollectionKey.buildIndexComparator(baseMetadata, cfDef);
                 case MAP:
-                    return cfDef.getIndexOptions().containsKey("index_keys")
+                    return 
cfDef.hasIndexOption(SecondaryIndex.INDEX_KEYS_OPTION_NAME)
                          ? 
CompositesIndexOnCollectionKey.buildIndexComparator(baseMetadata, cfDef)
                          : 
CompositesIndexOnCollectionValue.buildIndexComparator(baseMetadata, cfDef);
             }
@@ -159,8 +160,8 @@ public abstract class CompositesIndex extends 
AbstractSimplePerColumnSecondaryIn
 
         if (columnDef.type.isCollection())
         {
-            options.remove("index_values");
-            options.remove("index_keys");
+            options.remove(SecondaryIndex.INDEX_VALUES_OPTION_NAME);
+            options.remove(SecondaryIndex.INDEX_KEYS_OPTION_NAME);
         }
 
         if (!options.isEmpty())

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionKey.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionKey.java
 
b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionKey.java
index c252546..1067a94 100644
--- 
a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionKey.java
+++ 
b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionKey.java
@@ -89,6 +89,13 @@ public class CompositesIndexOnCollectionKey extends 
CompositesIndex
     }
 
     @Override
+    public boolean supportsOperator(IndexExpression.Operator operator)
+    {
+        return operator == IndexExpression.Operator.CONTAINS_KEY ||
+                operator == IndexExpression.Operator.CONTAINS && 
columnDef.type instanceof SetType;
+    }
+
+    @Override
     public boolean indexes(CellName name)
     {
         // We index if the CQL3 column name is the one of the collection we 
index

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
 
b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
index 7a8c552..5b4aa64 100644
--- 
a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
+++ 
b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java
@@ -95,6 +95,12 @@ public class CompositesIndexOnCollectionValue extends 
CompositesIndex
     }
 
     @Override
+    public boolean supportsOperator(IndexExpression.Operator operator)
+    {
+        return operator == IndexExpression.Operator.CONTAINS;
+    }
+
+    @Override
     public boolean indexes(CellName name)
     {
         AbstractType<?> comp = 
baseCfs.metadata.getColumnDefinitionComparator(columnDef);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/src/java/org/apache/cassandra/db/marshal/CollectionType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java 
b/src/java/org/apache/cassandra/db/marshal/CollectionType.java
index 8a5fe5c..e63f2a5 100644
--- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java
+++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java
@@ -115,6 +115,15 @@ public abstract class CollectionType<T> extends 
AbstractType<T>
         return true;
     }
 
+    /**
+     * Checks if this collection is Map.
+     * @return <code>true</code> if this collection is a Map, 
<code>false</code> otherwise.
+     */
+    public boolean isMap()
+    {
+        return kind == Kind.MAP;
+    }
+
     protected List<Cell> enforceLimit(List<Cell> cells, int version)
     {
         if (version >= 3 || cells.size() <= MAX_ELEMENTS)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/52a701f2/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java 
b/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
index f854ec6..335636b 100644
--- a/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/ContainsRelationTest.java
@@ -204,4 +204,48 @@ public class ContainsRelationTest extends CQLTester
             row(0, 1, 1, set(3, 4, 5))
         );
     }
+
+    @Test
+    public void testContainsKeyAndContainsWithIndexOnMapKey() throws Throwable
+    {
+        createTable("CREATE TABLE %s (account text, id int, categories 
map<text,text>, PRIMARY KEY (account, id))");
+        createIndex("CREATE INDEX ON %s(keys(categories))");
+
+        execute("INSERT INTO %s (account, id , categories) VALUES (?, ?, ?)", 
"test", 5, map("lmn", "foo"));
+        execute("INSERT INTO %s (account, id , categories) VALUES (?, ?, ?)", 
"test", 6, map("lmn", "foo2"));
+
+        assertInvalid("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS ?", "test", "foo");
+
+        assertRows(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS KEY ?", "test", "lmn"),
+                   row("test", 5, map("lmn", "foo")),
+                   row("test", 6, map("lmn", "foo2")));
+        assertRows(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS KEY ? AND categories CONTAINS ? ALLOW FILTERING",
+                           "test", "lmn", "foo"),
+                   row("test", 5, map("lmn", "foo")));
+        assertRows(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS ? AND categories CONTAINS KEY ? ALLOW FILTERING",
+                           "test", "foo", "lmn"),
+                   row("test", 5, map("lmn", "foo")));
+    }
+
+    @Test
+    public void testContainsKeyAndContainsWithIndexOnMapValue() throws 
Throwable
+    {
+        createTable("CREATE TABLE %s (account text, id int, categories 
map<text,text>, PRIMARY KEY (account, id))");
+        createIndex("CREATE INDEX ON %s(categories)");
+
+        execute("INSERT INTO %s (account, id , categories) VALUES (?, ?, ?)", 
"test", 5, map("lmn", "foo"));
+        execute("INSERT INTO %s (account, id , categories) VALUES (?, ?, ?)", 
"test", 6, map("lmn2", "foo"));
+
+        assertInvalid("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS KEY ?", "test", "lmn");
+
+        assertRows(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS ?", "test", "foo"),
+                   row("test", 5, map("lmn", "foo")),
+                   row("test", 6, map("lmn2", "foo")));
+        assertRows(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS KEY ? AND categories CONTAINS ? ALLOW FILTERING",
+                           "test", "lmn", "foo"),
+                   row("test", 5, map("lmn", "foo")));
+        assertRows(execute("SELECT * FROM %s WHERE account = ? AND categories 
CONTAINS ? AND categories CONTAINS KEY ? ALLOW FILTERING",
+                           "test", "foo", "lmn"),
+                   row("test", 5, map("lmn", "foo")));
+    }
 }

Reply via email to