Repository: cassandra
Updated Branches:
  refs/heads/trunk 5295f763d -> 9e7489116


Add support for secondary indexes on static columns

Patch by Taiyuan Zhang; reviewed by Sam Tunnicliffe for CASSANDRA-8103

Also includes fix for CASSANDRA-10958


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

Branch: refs/heads/trunk
Commit: 9e7489116feace284e0d704135186991a67ee5e3
Parents: 5295f76
Author: Taiyuan Zhang <[email protected]>
Authored: Mon Jan 18 23:57:59 2016 -0800
Committer: Sam Tunnicliffe <[email protected]>
Committed: Wed Jan 20 09:58:28 2016 +0000

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../restrictions/StatementRestrictions.java     |  17 ++
 .../cql3/statements/CreateIndexStatement.java   |   9 -
 .../cql3/statements/SelectStatement.java        |  10 +-
 .../apache/cassandra/db/filter/RowFilter.java   |   5 +
 .../index/internal/CassandraIndex.java          |   1 +
 .../composites/CollectionKeyIndexBase.java      |  21 +-
 .../composites/CollectionValueIndex.java        |  20 +-
 .../internal/composites/CompositesSearcher.java | 195 ++++++++++-------
 .../internal/composites/RegularColumnIndex.java |  22 +-
 .../unit/org/apache/cassandra/SchemaLoader.java |  27 ++-
 .../apache/cassandra/cql3/SimpleQueryTest.java  |  40 ++++
 .../SecondaryIndexOnStaticColumnTest.java       | 217 +++++++++++++++++++
 .../validation/entities/SecondaryIndexTest.java |   3 -
 .../apache/cassandra/db/SecondaryIndexTest.java |  40 +++-
 15 files changed, 510 insertions(+), 118 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 789f36e..81b3978 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.4
+ * Add support for secondary indexes on static columns (CASSANDRA-8103)
  * CommitLogUpgradeTestMaker creates broken commit logs (CASSANDRA-11051)
  * Add metric for number of dropped mutations (CASSANDRA-10866)
  * Simplify row cache invalidation code (CASSANDRA-10396)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java 
b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index 1c7db4e..fee17c2 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -78,6 +78,12 @@ public final class StatementRestrictions
      */
     private RestrictionSet nonPrimaryKeyRestrictions;
 
+    /**
+     * <code>true</code> if nonPrimaryKeyRestrictions contains restriction on 
a regular column,
+     * <code>false</code> otherwise.
+     */
+    private boolean hasRegularColumnsRestriction = false;
+
     private Set<ColumnDefinition> notNullColumns;
 
     /**
@@ -258,7 +264,13 @@ public final class StatementRestrictions
         else if (def.isClusteringColumn())
             clusteringColumnsRestrictions = 
clusteringColumnsRestrictions.mergeWith(restriction);
         else
+        {
+            if (restriction.columnDef.kind == ColumnDefinition.Kind.REGULAR)
+            {
+                hasRegularColumnsRestriction = true;
+            }
             nonPrimaryKeyRestrictions = 
nonPrimaryKeyRestrictions.addRestriction(restriction);
+        }
     }
 
     /**
@@ -332,6 +344,11 @@ public final class StatementRestrictions
         return this.isKeyRange;
     }
 
+    public boolean hasRegularColumnsRestriction()
+    {
+        return hasRegularColumnsRestriction;
+    }
+
     /**
      * Checks if the secondary index need to be queried.
      *

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/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 b2a6fd5..f3d22e9 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java
@@ -105,15 +105,6 @@ public class CreateIndexStatement extends 
SchemaAlteringStatement
             if (cfm.isCompactTable() && cd.isPrimaryKeyColumn())
                 throw new InvalidRequestException("Secondary indexes are not 
supported on PRIMARY KEY columns in COMPACT STORAGE tables");
 
-            // It would be possible to support 2ndary index on static columns 
(but not without modifications of at least ExtendedFilter and
-            // CompositesIndex) and maybe we should, but that means a query 
like:
-            //     SELECT * FROM foo WHERE static_column = 'bar'
-            // would pull the full partition every time the static column of 
partition is 'bar', which sounds like offering a
-            // fair potential for foot-shooting, so I prefer leaving that to a 
follow up ticket once we have identified cases where
-            // such indexing is actually useful.
-            if (!cfm.isCompactTable() && cd.isStatic())
-                throw new InvalidRequestException("Secondary indexes are not 
allowed on static columns");
-
             if (cd.kind == ColumnDefinition.Kind.PARTITION_KEY && 
cfm.getKeyValidatorAsClusteringComparator().size() == 1)
                 throw new InvalidRequestException(String.format("Cannot create 
secondary index on partition key column %s", target.column));
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/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 c196e5e..5346334 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -589,7 +589,7 @@ public class SelectStatement implements CQLStatement
      * Returns the limit specified by the user.
      * May be used by custom QueryHandler implementations
      *
-     * @return the limit specified by the user or 
<code>DataLimits.NO_LIMIT</code> if no value 
+     * @return the limit specified by the user or 
<code>DataLimits.NO_LIMIT</code> if no value
      * as been specified.
      */
     public int getLimit(QueryOptions options)
@@ -681,12 +681,14 @@ public class SelectStatement implements CQLStatement
         ByteBuffer[] keyComponents = getComponents(cfm, 
partition.partitionKey());
 
         Row staticRow = partition.staticRow();
-        // If there is no rows, then provided the select was a full partition 
selection
-        // (i.e. not a 2ndary index search and there was no condition on 
clustering columns),
+        // If there is no rows, and there's no restriction on 
clustering/regular columns,
+        // then provided the select was a full partition selection (either by 
partition key and/or by static column),
         // we want to include static columns and we're done.
         if (!partition.hasNext())
         {
-            if (!staticRow.isEmpty() && (!restrictions.usesSecondaryIndexing() 
|| cfm.isStaticCompactTable()) && 
!restrictions.hasClusteringColumnsRestriction())
+            if (!staticRow.isEmpty()
+                && (!restrictions.hasClusteringColumnsRestriction() || 
cfm.isStaticCompactTable())
+                && !restrictions.hasRegularColumnsRestriction())
             {
                 result.newRow(protocolVersion);
                 for (ColumnDefinition def : selection.getColumns())

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/db/filter/RowFilter.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java 
b/src/java/org/apache/cassandra/db/filter/RowFilter.java
index 17db323..4cd2f64 100644
--- a/src/java/org/apache/cassandra/db/filter/RowFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java
@@ -229,6 +229,11 @@ public abstract class RowFilter implements 
Iterable<RowFilter.Expression>
                 DecoratedKey pk;
                 public UnfilteredRowIterator 
applyToPartition(UnfilteredRowIterator partition)
                 {
+                    // The filter might be on static columns, so need to check 
static row first.
+                    Row staticRow = applyToRow(partition.staticRow());
+                    if (staticRow == null)
+                        return null;
+
                     pk = partition.partitionKey();
                     return Transformation.apply(partition, this);
                 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/index/internal/CassandraIndex.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/index/internal/CassandraIndex.java 
b/src/java/org/apache/cassandra/index/internal/CassandraIndex.java
index ef813b8..7322611 100644
--- a/src/java/org/apache/cassandra/index/internal/CassandraIndex.java
+++ b/src/java/org/apache/cassandra/index/internal/CassandraIndex.java
@@ -816,6 +816,7 @@ public abstract class CassandraIndex implements Index
             case CLUSTERING:
                 return 
CassandraIndexFunctions.CLUSTERING_COLUMN_INDEX_FUNCTIONS;
             case REGULAR:
+            case STATIC:
                 return CassandraIndexFunctions.REGULAR_COLUMN_INDEX_FUNCTIONS;
             case PARTITION_KEY:
                 return CassandraIndexFunctions.PARTITION_KEY_INDEX_FUNCTIONS;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/index/internal/composites/CollectionKeyIndexBase.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/index/internal/composites/CollectionKeyIndexBase.java
 
b/src/java/org/apache/cassandra/index/internal/composites/CollectionKeyIndexBase.java
index fe77c96..ef76870 100644
--- 
a/src/java/org/apache/cassandra/index/internal/composites/CollectionKeyIndexBase.java
+++ 
b/src/java/org/apache/cassandra/index/internal/composites/CollectionKeyIndexBase.java
@@ -54,6 +54,9 @@ public abstract class CollectionKeyIndexBase extends 
CassandraIndex
     {
         CBuilder builder = CBuilder.create(getIndexComparator());
         builder.add(partitionKey);
+
+        // When indexing a static column, prefix will be empty but only the
+        // partition key is needed at query time.
         for (int i = 0; i < prefix.size(); i++)
             builder.add(prefix.get(i));
 
@@ -63,16 +66,24 @@ public abstract class CollectionKeyIndexBase extends 
CassandraIndex
     public IndexEntry decodeEntry(DecoratedKey indexedValue,
                                   Row indexEntry)
     {
-        int count = 1 + baseCfs.metadata.clusteringColumns().size();
         Clustering clustering = indexEntry.clustering();
-        CBuilder builder = CBuilder.create(baseCfs.getComparator());
-        for (int i = 0; i < count - 1; i++)
-            builder.add(clustering.get(i + 1));
+
+        Clustering indexedEntryClustering = null;
+        if (getIndexedColumn().isStatic())
+            indexedEntryClustering = Clustering.STATIC_CLUSTERING;
+        else
+        {
+            int count = 1 + baseCfs.metadata.clusteringColumns().size();
+            CBuilder builder = CBuilder.create(baseCfs.getComparator());
+            for (int i = 0; i < count - 1; i++)
+                builder.add(clustering.get(i + 1));
+            indexedEntryClustering = builder.build();
+        }
 
         return new IndexEntry(indexedValue,
                               clustering,
                               indexEntry.primaryKeyLivenessInfo().timestamp(),
                               clustering.get(0),
-                              builder.build());
+                              indexedEntryClustering);
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/index/internal/composites/CollectionValueIndex.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/index/internal/composites/CollectionValueIndex.java
 
b/src/java/org/apache/cassandra/index/internal/composites/CollectionValueIndex.java
index 95bd7e1..5929e69 100644
--- 
a/src/java/org/apache/cassandra/index/internal/composites/CollectionValueIndex.java
+++ 
b/src/java/org/apache/cassandra/index/internal/composites/CollectionValueIndex.java
@@ -63,7 +63,10 @@ public class CollectionValueIndex extends CassandraIndex
         for (int i = 0; i < prefix.size(); i++)
             builder.add(prefix.get(i));
 
-        // When indexing, cell will be present, but when searching, it won't  
(CASSANDRA-7525)
+        // When indexing a static column, prefix will be empty but only the
+        // partition key is needed at query time.
+        // In the non-static case, cell will be present during indexing but
+        // not when searching (CASSANDRA-7525).
         if (prefix.size() == baseCfs.metadata.clusteringColumns().size() && 
path != null)
             builder.add(path.get(0));
 
@@ -73,15 +76,22 @@ public class CollectionValueIndex extends CassandraIndex
     public IndexEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
     {
         Clustering clustering = indexEntry.clustering();
-        CBuilder builder = CBuilder.create(baseCfs.getComparator());
-        for (int i = 0; i < baseCfs.getComparator().size(); i++)
-            builder.add(clustering.get(i + 1));
+        Clustering indexedEntryClustering = null;
+        if (getIndexedColumn().isStatic())
+            indexedEntryClustering = Clustering.STATIC_CLUSTERING;
+        else
+        {
+            CBuilder builder = CBuilder.create(baseCfs.getComparator());
+            for (int i = 0; i < baseCfs.getComparator().size(); i++)
+                builder.add(clustering.get(i + 1));
+            indexedEntryClustering = builder.build();
+        }
 
         return new IndexEntry(indexedValue,
                                 clustering,
                                 
indexEntry.primaryKeyLivenessInfo().timestamp(),
                                 clustering.get(0),
-                                builder.build());
+                                indexedEntryClustering);
     }
 
     public boolean supportsOperator(ColumnDefinition indexedColumn, Operator 
operator)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
 
b/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
index ddaa653..c12ac6b 100644
--- 
a/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
+++ 
b/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
@@ -24,6 +24,8 @@ import java.util.List;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
+import org.apache.cassandra.db.filter.ColumnFilter;
 import org.apache.cassandra.db.filter.DataLimits;
 import org.apache.cassandra.db.filter.RowFilter;
 import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
@@ -50,6 +52,11 @@ public class CompositesSearcher extends 
CassandraIndexSearcher
         return command.selectsKey(partitionKey) && 
command.selectsClustering(partitionKey, entry.indexedEntryClustering);
     }
 
+    private boolean isStaticColumn()
+    {
+        return index.getIndexedColumn().isStatic();
+    }
+
     protected UnfilteredPartitionIterator queryDataFromIndex(final 
DecoratedKey indexKey,
                                                              final RowIterator 
indexHits,
                                                              final ReadCommand 
command,
@@ -103,52 +110,68 @@ public class CompositesSearcher extends 
CassandraIndexSearcher
                         nextEntry = index.decodeEntry(indexKey, 
indexHits.next());
                     }
 
-                    // Gather all index hits belonging to the same partition 
and query the data for those hits.
-                    // TODO: it's much more efficient to do 1 read for all 
hits to the same partition than doing
-                    // 1 read per index hit. However, this basically mean 
materializing all hits for a partition
-                    // in memory so we should consider adding some paging 
mechanism. However, index hits should
-                    // be relatively small so it's much better than the 
previous code that was materializing all
-                    // *data* for a given partition.
-                    BTreeSet.Builder<Clustering> clusterings = 
BTreeSet.builder(index.baseCfs.getComparator());
-                    List<IndexEntry> entries = new ArrayList<>();
+                    SinglePartitionReadCommand dataCmd;
                     DecoratedKey partitionKey = 
index.baseCfs.decorateKey(nextEntry.indexedKey);
-
-                    while (nextEntry != null && 
partitionKey.getKey().equals(nextEntry.indexedKey))
+                    List<IndexEntry> entries = new ArrayList<>();
+                    if (isStaticColumn())
                     {
-                        // We're queried a slice of the index, but some hits 
may not match some of the clustering column constraints
-                        if (isMatchingEntry(partitionKey, nextEntry, command))
-                        {
-                            clusterings.add(nextEntry.indexedEntryClustering);
-                            entries.add(nextEntry);
-                        }
-
+                        // If the index is on a static column, we just need to 
do a full read on the partition.
+                        // Note that we want to re-use the 
command.columnFilter() in case of future change.
+                        dataCmd = 
SinglePartitionReadCommand.create(index.baseCfs.metadata,
+                                                                    
command.nowInSec(),
+                                                                    
command.columnFilter(),
+                                                                    
RowFilter.NONE,
+                                                                    
DataLimits.NONE,
+                                                                    
partitionKey,
+                                                                    new 
ClusteringIndexSliceFilter(Slices.ALL, false));
+                        entries.add(nextEntry);
                         nextEntry = indexHits.hasNext() ? 
index.decodeEntry(indexKey, indexHits.next()) : null;
                     }
-
-                    // Because we've eliminated entries that don't match the 
clustering columns, it's possible we added nothing
-                    if (clusterings.isEmpty())
+                    else
                     {
-                        continue;
+                        // Gather all index hits belonging to the same 
partition and query the data for those hits.
+                        // TODO: it's much more efficient to do 1 read for all 
hits to the same partition than doing
+                        // 1 read per index hit. However, this basically mean 
materializing all hits for a partition
+                        // in memory so we should consider adding some paging 
mechanism. However, index hits should
+                        // be relatively small so it's much better than the 
previous code that was materializing all
+                        // *data* for a given partition.
+                        BTreeSet.Builder<Clustering> clusterings = 
BTreeSet.builder(index.baseCfs.getComparator());
+                        while (nextEntry != null && 
partitionKey.getKey().equals(nextEntry.indexedKey))
+                        {
+                            // We're queried a slice of the index, but some 
hits may not match some of the clustering column constraints
+                            if (isMatchingEntry(partitionKey, nextEntry, 
command))
+                            {
+                                
clusterings.add(nextEntry.indexedEntryClustering);
+                                entries.add(nextEntry);
+                            }
+
+                            nextEntry = indexHits.hasNext() ? 
index.decodeEntry(indexKey, indexHits.next()) : null;
+                        }
+
+                        // Because we've eliminated entries that don't match 
the clustering columns, it's possible we added nothing
+                        if (clusterings.isEmpty())
+                            continue;
+
+                        // Query the gathered index hits. We still need to 
filter stale hits from the resulting query.
+                        ClusteringIndexNamesFilter filter = new 
ClusteringIndexNamesFilter(clusterings.build(), false);
+                        dataCmd = 
SinglePartitionReadCommand.create(index.baseCfs.metadata,
+                                                                    
command.nowInSec(),
+                                                                    
command.columnFilter(),
+                                                                    
command.rowFilter(),
+                                                                    
DataLimits.NONE,
+                                                                    
partitionKey,
+                                                                    filter);
                     }
 
-                    // Query the gathered index hits. We still need to filter 
stale hits from the resulting query.
-                    ClusteringIndexNamesFilter filter = new 
ClusteringIndexNamesFilter(clusterings.build(), false);
-                    SinglePartitionReadCommand dataCmd = 
SinglePartitionReadCommand.create(index.baseCfs.metadata,
-                                                                               
            command.nowInSec(),
-                                                                               
            command.columnFilter(),
-                                                                               
            command.rowFilter(),
-                                                                               
            DataLimits.NONE,
-                                                                               
            partitionKey,
-                                                                               
            filter);
                     @SuppressWarnings("resource") // We close right away if 
empty, and if it's assign to next it will be called either
                     // by the next caller of next, or through closing this 
iterator is this come before.
-                    UnfilteredRowIterator dataIter = 
filterStaleEntries(dataCmd.queryMemtableAndDisk(index.baseCfs,
-                                                                               
                      executionController.baseReadOpOrderGroup()),
-                                                                        
indexKey.getKey(),
-                                                                        
entries,
-                                                                        
executionController.writeOpOrderGroup(),
-                                                                        
command.nowInSec());
-
+                    UnfilteredRowIterator dataIter =
+                        
filterStaleEntries(dataCmd.queryMemtableAndDisk(index.baseCfs,
+                                                                        
executionController.baseReadOpOrderGroup()),
+                                           indexKey.getKey(),
+                                           entries,
+                                           
executionController.writeOpOrderGroup(),
+                                           command.nowInSec());
 
                     if (dataIter.isEmpty())
                     {
@@ -179,11 +202,12 @@ public class CompositesSearcher extends 
CassandraIndexSearcher
     {
         entries.forEach(entry ->
             index.deleteStaleEntry(entry.indexValue,
-                                     entry.indexClustering,
-                                     new DeletionTime(entry.timestamp, 
nowInSec),
-                                     writeOp));
+                                   entry.indexClustering,
+                                   new DeletionTime(entry.timestamp, nowInSec),
+                                   writeOp));
     }
 
+    // We assume all rows in dataIter belong to the same partition.
     private UnfilteredRowIterator filterStaleEntries(UnfilteredRowIterator 
dataIter,
                                                      final ByteBuffer 
indexValue,
                                                      final List<IndexEntry> 
entries,
@@ -204,50 +228,75 @@ public class CompositesSearcher extends 
CassandraIndexSearcher
             });
         }
 
+        UnfilteredRowIterator iteratorToReturn = null;
         ClusteringComparator comparator = dataIter.metadata().comparator;
-        class Transform extends Transformation
+        if (isStaticColumn())
         {
-            private int entriesIdx;
+            if (entries.size() != 1)
+                throw new AssertionError("A partition should have at most one 
index within a static column index");
 
-            @Override
-            public Row applyToRow(Row row)
+            iteratorToReturn = dataIter;
+            if (index.isStale(dataIter.staticRow(), indexValue, nowInSec))
             {
-                IndexEntry entry = findEntry(row.clustering());
-                if (!index.isStale(row, indexValue, nowInSec))
-                    return row;
-
-                staleEntries.add(entry);
-                return null;
+                // The entry is staled, we return no rows in this partition.
+                staleEntries.addAll(entries);
+                iteratorToReturn = 
UnfilteredRowIterators.noRowsIterator(dataIter.metadata(),
+                                                                         
dataIter.partitionKey(),
+                                                                         
Rows.EMPTY_STATIC_ROW,
+                                                                         
dataIter.partitionLevelDeletion(),
+                                                                         
dataIter.isReverseOrder());
             }
-
-            private IndexEntry findEntry(Clustering clustering)
+            deleteAllEntries(staleEntries, writeOp, nowInSec);
+        }
+        else
+        {
+            class Transform extends Transformation
             {
-                assert entriesIdx < entries.size();
-                while (entriesIdx < entries.size())
+                private int entriesIdx;
+
+                @Override
+                public Row applyToRow(Row row)
                 {
-                    IndexEntry entry = entries.get(entriesIdx++);
-                    // The entries are in clustering order. So that the 
requested entry should be the
-                    // next entry, the one at 'entriesIdx'. However, we can 
have stale entries, entries
-                    // that have no corresponding row in the base table 
typically because of a range
-                    // tombstone or partition level deletion. Delete such 
stale entries.
-                    int cmp = comparator.compare(entry.indexedEntryClustering, 
clustering);
-                    assert cmp <= 0; // this would means entries are not in 
clustering order, which shouldn't happen
-                    if (cmp == 0)
-                        return entry;
-                    else
-                        staleEntries.add(entry);
+                    IndexEntry entry = findEntry(row.clustering());
+                    if (!index.isStale(row, indexValue, nowInSec))
+                        return row;
+
+                    staleEntries.add(entry);
+                    return null;
                 }
-                // entries correspond to the rows we've queried, so we 
shouldn't have a row that has no corresponding entry.
-                throw new AssertionError();
-            }
 
-            @Override
-            public void onPartitionClose()
-            {
-                deleteAllEntries(staleEntries, writeOp, nowInSec);
+                private IndexEntry findEntry(Clustering clustering)
+                {
+                    assert entriesIdx < entries.size();
+                    while (entriesIdx < entries.size())
+                    {
+                        IndexEntry entry = entries.get(entriesIdx++);
+                        // The entries are in clustering order. So that the 
requested entry should be the
+                        // next entry, the one at 'entriesIdx'. However, we 
can have stale entries, entries
+                        // that have no corresponding row in the base table 
typically because of a range
+                        // tombstone or partition level deletion. Delete such 
stale entries.
+                        // For static column, we only need to compare the 
partition key, otherwise we compare
+                        // the whole clustering.
+                        int cmp = 
comparator.compare(entry.indexedEntryClustering, clustering);
+                        assert cmp <= 0; // this would means entries are not 
in clustering order, which shouldn't happen
+                        if (cmp == 0)
+                            return entry;
+                        else
+                            staleEntries.add(entry);
+                    }
+                    // entries correspond to the rows we've queried, so we 
shouldn't have a row that has no corresponding entry.
+                    throw new AssertionError();
+                }
+
+                @Override
+                public void onPartitionClose()
+                {
+                    deleteAllEntries(staleEntries, writeOp, nowInSec);
+                }
             }
+            iteratorToReturn = Transformation.apply(dataIter, new Transform());
         }
 
-        return Transformation.apply(dataIter, new Transform());
+        return iteratorToReturn;
     }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/src/java/org/apache/cassandra/index/internal/composites/RegularColumnIndex.java
----------------------------------------------------------------------
diff --git 
a/src/java/org/apache/cassandra/index/internal/composites/RegularColumnIndex.java
 
b/src/java/org/apache/cassandra/index/internal/composites/RegularColumnIndex.java
index f1dc3af..9cbfe03 100644
--- 
a/src/java/org/apache/cassandra/index/internal/composites/RegularColumnIndex.java
+++ 
b/src/java/org/apache/cassandra/index/internal/composites/RegularColumnIndex.java
@@ -68,22 +68,34 @@ public class RegularColumnIndex extends CassandraIndex
         for (int i = 0; i < prefix.size(); i++)
             builder.add(prefix.get(i));
 
+        // Note: if indexing a static column, prefix will be 
Clustering.STATIC_CLUSTERING
+        // so the Clustering obtained from builder::build will contain a value 
for only
+        // the partition key. At query time though, this is all that's needed 
as the entire
+        // base table partition should be returned for any mathching index 
entry.
         return builder;
     }
 
     public IndexEntry decodeEntry(DecoratedKey indexedValue, Row indexEntry)
     {
         Clustering clustering = indexEntry.clustering();
-        ClusteringComparator baseComparator = baseCfs.getComparator();
-        CBuilder builder = CBuilder.create(baseComparator);
-        for (int i = 0; i < baseComparator.size(); i++)
-            builder.add(clustering.get(i + 1));
+
+        Clustering indexedEntryClustering = null;
+        if (getIndexedColumn().isStatic())
+            indexedEntryClustering = Clustering.STATIC_CLUSTERING;
+        else
+        {
+            ClusteringComparator baseComparator = baseCfs.getComparator();
+            CBuilder builder = CBuilder.create(baseComparator);
+            for (int i = 0; i < baseComparator.size(); i++)
+                builder.add(clustering.get(i + 1));
+            indexedEntryClustering = builder.build();
+        }
 
         return new IndexEntry(indexedValue,
                                 clustering,
                                 
indexEntry.primaryKeyLivenessInfo().timestamp(),
                                 clustering.get(0),
-                                builder.build());
+                                indexedEntryClustering);
     }
 
     public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/test/unit/org/apache/cassandra/SchemaLoader.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java 
b/test/unit/org/apache/cassandra/SchemaLoader.java
index 5d720c4..e375990 100644
--- a/test/unit/org/apache/cassandra/SchemaLoader.java
+++ b/test/unit/org/apache/cassandra/SchemaLoader.java
@@ -398,7 +398,12 @@ public class SchemaLoader
         return standardCFMD(ksName, cfName);
 
     }
-    public static CFMetaData compositeIndexCFMD(String ksName, String cfName, 
boolean withIndex) throws ConfigurationException
+    public static CFMetaData compositeIndexCFMD(String ksName, String cfName, 
boolean withRegularIndex) throws ConfigurationException
+    {
+        return compositeIndexCFMD(ksName, cfName, withRegularIndex, false);
+    }
+
+    public static CFMetaData compositeIndexCFMD(String ksName, String cfName, 
boolean withRegularIndex, boolean withStaticIndex) throws ConfigurationException
     {
         // the withIndex flag exists to allow tests index creation
         // on existing columns
@@ -407,9 +412,11 @@ public class SchemaLoader
                 .addClusteringColumn("c1", AsciiType.instance)
                 .addRegularColumn("birthdate", LongType.instance)
                 .addRegularColumn("notbirthdate", LongType.instance)
+                .addStaticColumn("static", LongType.instance)
                 .build();
 
-        if (withIndex)
+        if (withRegularIndex)
+        {
             cfm.indexes(
                 cfm.getIndexes()
                    .with(IndexMetadata.fromIndexTargets(cfm,
@@ -419,6 +426,20 @@ public class SchemaLoader
                                                         "birthdate_key_index",
                                                         
IndexMetadata.Kind.COMPOSITES,
                                                         
Collections.EMPTY_MAP)));
+        }
+
+        if (withStaticIndex)
+        {
+            cfm.indexes(
+                    cfm.getIndexes()
+                       .with(IndexMetadata.fromIndexTargets(cfm,
+                                                            
Collections.singletonList(
+                                                                new 
IndexTarget(new ColumnIdentifier("static", true),
+                                                                               
 IndexTarget.Type.VALUES)),
+                                                            "static_index",
+                                                            
IndexMetadata.Kind.COMPOSITES,
+                                                            
Collections.EMPTY_MAP)));
+        }
 
         return cfm.compression(getCompressionParameters());
     }
@@ -446,7 +467,7 @@ public class SchemaLoader
 
         return cfm.compression(getCompressionParameters());
     }
-    
+
     public static CFMetaData jdbcCFMD(String ksName, String cfName, 
AbstractType comp)
     {
         return CFMetaData.Builder.create(ksName, 
cfName).addPartitionKey("key", BytesType.instance)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/test/unit/org/apache/cassandra/cql3/SimpleQueryTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/SimpleQueryTest.java 
b/test/unit/org/apache/cassandra/cql3/SimpleQueryTest.java
index 052b53d..a46c750 100644
--- a/test/unit/org/apache/cassandra/cql3/SimpleQueryTest.java
+++ b/test/unit/org/apache/cassandra/cql3/SimpleQueryTest.java
@@ -18,6 +18,7 @@
 package org.apache.cassandra.cql3;
 
 import java.util.*;
+
 import org.junit.Test;
 
 import static junit.framework.Assert.*;
@@ -529,4 +530,43 @@ public class SimpleQueryTest extends CQLTester
             row(0, 0, 0, 0)
         );
     }
+
+    /** Test for Cassandra issue 10958 **/
+    @Test
+    public void restrictionOnRegularColumnWithStaticColumnPresentTest() throws 
Throwable
+    {
+        createTable("CREATE TABLE %s (id int, id2 int, age int static, extra 
int, PRIMARY KEY(id, id2))");
+
+        execute("INSERT INTO %s (id, id2, age, extra) VALUES (?, ?, ?, ?)", 1, 
1, 1, 1);
+        execute("INSERT INTO %s (id, id2, age, extra) VALUES (?, ?, ?, ?)", 2, 
2, 2, 2);
+        execute("UPDATE %s SET age=? WHERE id=?", 3, 3);
+
+        assertRows(execute("SELECT * FROM %s"),
+            row(1, 1, 1, 1),
+            row(2, 2, 2, 2),
+            row(3, null, 3, null)
+        );
+
+        assertRows(execute("SELECT * FROM %s WHERE extra > 1 ALLOW FILTERING"),
+            row(2, 2, 2, 2)
+        );
+    }
+
+    @Test
+    public void testRowFilteringOnStaticColumn() throws Throwable
+    {
+        createTable("CREATE TABLE %s (id int, name text, age int static, 
PRIMARY KEY (id, name))");
+        for (int i = 0; i < 5; i++)
+        {
+            execute("INSERT INTO %s (id, name, age) VALUES (?, ?, ?)", i, 
"NameDoesNotMatter", i);
+        }
+
+        assertInvalid("SELECT id, age FROM %s WHERE age < 1");
+        assertRows(execute("SELECT id, age FROM %s WHERE age < 1 ALLOW 
FILTERING"),
+                   row(0, 0));
+        assertRows(execute("SELECT id, age FROM %s WHERE age > 0 AND age < 3 
ALLOW FILTERING"),
+                   row(1, 1), row(2, 2));
+        assertRows(execute("SELECT id, age FROM %s WHERE age > 3 ALLOW 
FILTERING"),
+                   row(4, 4));
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexOnStaticColumnTest.java
----------------------------------------------------------------------
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexOnStaticColumnTest.java
 
b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexOnStaticColumnTest.java
new file mode 100644
index 0000000..f69d8d5
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexOnStaticColumnTest.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.validation.entities;
+
+import org.junit.Test;
+import org.apache.cassandra.cql3.CQLTester;
+
+public class SecondaryIndexOnStaticColumnTest extends CQLTester
+{
+    @Test
+    public void testSimpleStaticColumn() throws Throwable
+    {
+        createTable("CREATE TABLE %s (id int, name text, age int static, 
PRIMARY KEY (id, name))");
+
+        createIndex("CREATE INDEX static_age on %s(age)");
+        int id1 = 1, id2 = 2, age1 = 24, age2 = 32;
+        String name1A = "Taylor", name1B = "Swift",
+               name2 = "Jamie";
+
+        execute("INSERT INTO %s (id, name, age) VALUES (?, ?, ?)", id1, 
name1A, age1);
+        execute("INSERT INTO %s (id, name, age) VALUES (?, ?, ?)", id1, 
name1B, age1);
+        execute("INSERT INTO %s (id, name, age) VALUES (?, ?, ?)", id2, name2, 
age2);
+
+        assertRows(execute("SELECT id, name, age FROM %s WHERE age=?", age1),
+              row(id1, name1B, age1), row(id1, name1A, age1));
+        assertRows(execute("SELECT id, name, age FROM %s WHERE age=?", age2),
+                row(id2, name2, age2));
+
+        // Update the rows. Validate that updated values will be reflected in 
the index.
+        int newAge1 = 40;
+        execute("UPDATE %s SET age = ? WHERE id = ?", newAge1, id1);
+        assertEmpty(execute("SELECT id, name, age FROM %s WHERE age=?", age1));
+        assertRows(execute("SELECT id, name, age FROM %s WHERE age=?", 
newAge1),
+                row(id1, name1B, newAge1), row(id1, name1A, newAge1));
+        execute("DELETE FROM %s WHERE id = ?", id2);
+        assertEmpty(execute("SELECT id, name, age FROM %s WHERE age=?", age2));
+    }
+
+    @Test
+    public void testIndexOnCompoundRowKey() throws Throwable
+    {
+        createTable("CREATE TABLE %s (interval text, seq int, id int, severity 
int static, PRIMARY KEY ((interval, seq), id) ) WITH CLUSTERING ORDER BY (id 
DESC)");
+
+        execute("CREATE INDEX ON %s (severity)");
+
+        execute("insert into %s (interval, seq, id , severity) values('t',1, 
3, 10)");
+        execute("insert into %s (interval, seq, id , severity) values('t',1, 
4, 10)");
+        execute("insert into %s (interval, seq, id , severity) values('t',2, 
3, 10)");
+        execute("insert into %s (interval, seq, id , severity) values('t',2, 
4, 10)");
+        execute("insert into %s (interval, seq, id , severity) values('m',1, 
3, 11)");
+        execute("insert into %s (interval, seq, id , severity) values('m',1, 
4, 11)");
+        execute("insert into %s (interval, seq, id , severity) values('m',2, 
3, 11)");
+        execute("insert into %s (interval, seq, id , severity) values('m',2, 
4, 11)");
+
+        assertRows(execute("select * from %s where severity = 10 and interval 
= 't' and seq = 1"),
+                   row("t", 1, 4, 10), row("t", 1, 3, 10));
+    }
+
+    @Test
+    public void testIndexOnCollections() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int, v int, l list<int> static, s 
set<text> static, m map<text, int> static, PRIMARY KEY (k, v))");
+
+        createIndex("CREATE INDEX ON %s (l)");
+        createIndex("CREATE INDEX ON %s (s)");
+        createIndex("CREATE INDEX ON %s (m)");
+        createIndex("CREATE INDEX ON %s (keys(m))");
+
+        execute("INSERT INTO %s (k, v, l, s, m) VALUES (0, 0, [1, 2],    
{'a'},      {'a' : 1, 'b' : 2})");
+        execute("INSERT INTO %s (k, v)          VALUES (0, 1)                  
                ");
+        execute("INSERT INTO %s (k, v, l, s, m) VALUES (1, 0, [4, 5],    
{'d'},      {'b' : 1, 'c' : 4})");
+
+        // lists
+        assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 1"), row(0, 
0), row(0, 1));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE k = 1 AND l CONTAINS 
1"));
+        assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 4"), row(1, 
0));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE l CONTAINS 6"));
+
+        // update lists
+        execute("UPDATE %s SET l = l + [3] WHERE k = ?", 0);
+        assertRows(execute("SELECT k, v FROM %s WHERE l CONTAINS 3"), row(0, 
0), row(0, 1));
+
+        // sets
+        assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'a'"), row(0, 
0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND s CONTAINS 
'a'"), row(0, 0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'd'"), row(1, 
0));
+        assertEmpty(execute("SELECT k, v FROM %s  WHERE s CONTAINS 'e'"));
+
+        // update sets
+        execute("UPDATE %s SET s = s + {'b'} WHERE k = ?", 0);
+        assertRows(execute("SELECT k, v FROM %s WHERE s CONTAINS 'b'"), row(0, 
0), row(0, 1));
+        execute("UPDATE %s SET s = s - {'a'} WHERE k = ?", 0);
+        assertEmpty(execute("SELECT k, v FROM %s WHERE s CONTAINS 'a'"));
+
+        // maps
+        assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 1"), row(1, 
0), row(0, 0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS 
1"), row(0, 0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 4"), row(1, 
0));
+        assertEmpty(execute("SELECT k, v FROM %s  WHERE m CONTAINS 5"));
+
+        assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'b'"), 
row(1, 0), row(0, 0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE k = 0 AND m CONTAINS KEY 
'b'"), row(0, 0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'c'"), 
row(1, 0));
+        assertEmpty(execute("SELECT k, v FROM %s  WHERE m CONTAINS KEY 'd'"));
+
+        // update maps.
+        execute("UPDATE %s SET m['c'] = 5 WHERE k = 0");
+        assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS 5"), row(0, 
0), row(0, 1));
+        assertRows(execute("SELECT k, v FROM %s WHERE m CONTAINS KEY 'c'"), 
row(1, 0), row(0, 0), row(0, 1));
+        execute("DELETE m['a'] FROM %s WHERE k = 0");
+        assertEmpty(execute("SELECT k, v FROM %s  WHERE m CONTAINS KEY 'a'"));
+    }
+
+    @Test
+    public void testIndexOnFrozenCollections() throws Throwable
+    {
+        createTable("CREATE TABLE %s (k int, v int, l frozen<list<int>> 
static, s frozen<set<text>> static, m frozen<map<text, int>> static, PRIMARY 
KEY (k, v))");
+
+        createIndex("CREATE INDEX ON %s (FULL(l))");
+        createIndex("CREATE INDEX ON %s (FULL(s))");
+        createIndex("CREATE INDEX ON %s (FULL(m))");
+
+        execute("INSERT INTO %s (k, v, l, s, m) VALUES (0, 0, [1, 2],    
{'a'},      {'a' : 1, 'b' : 2})");
+        execute("INSERT INTO %s (k, v)          VALUES (0, 1)                  
                ");
+        execute("INSERT INTO %s (k, v, l, s, m) VALUES (1, 0, [4, 5],    
{'d'},      {'b' : 1, 'c' : 4})");
+        execute("UPDATE %s SET l=[3], s={'3'}, m={'3': 3} WHERE k=3" );
+
+        // lists
+        assertRows(execute("SELECT k, v FROM %s WHERE l = [1, 2]"), row(0, 0), 
row(0, 1));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE k = 1 AND l = [1, 2]"));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE l = [4]"));
+        assertRows(execute("SELECT k, v FROM %s WHERE l = [3]"), row(3, null));
+
+        // update lists
+        execute("UPDATE %s SET l = [1, 2, 3] WHERE k = ?", 0);
+        assertEmpty(execute("SELECT k, v FROM %s WHERE l = [1, 2]"));
+        assertRows(execute("SELECT k, v FROM %s WHERE l = [1, 2, 3]"), row(0, 
0), row(0, 1));
+
+        // sets
+        assertRows(execute("SELECT k, v FROM %s WHERE s = {'a'}"), row(0, 0), 
row(0, 1));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE k = 1 AND s = {'a'}"));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE s = {'b'}"));
+        assertRows(execute("SELECT k, v FROM %s WHERE s = {'3'}"), row(3, 
null));
+
+        // update sets
+        execute("UPDATE %s SET s = {'a', 'b'} WHERE k = ?", 0);
+        assertEmpty(execute("SELECT k, v FROM %s WHERE s = {'a'}"));
+        assertRows(execute("SELECT k, v FROM %s WHERE s = {'a', 'b'}"), row(0, 
0), row(0, 1));
+
+        // maps
+        assertRows(execute("SELECT k, v FROM %s WHERE m = {'a' : 1, 'b' : 
2}"), row(0, 0), row(0, 1));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE k = 1 AND m = {'a' : 1, 
'b' : 2}"));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE m = {'a' : 1, 'b' : 
3}"));
+        assertEmpty(execute("SELECT k, v FROM %s WHERE m = {'a' : 1, 'c' : 
2}"));
+        assertRows(execute("SELECT k, v FROM %s WHERE m = {'3': 3}"), row(3, 
null));
+
+        // update maps.
+        execute("UPDATE %s SET m = {'a': 2, 'b': 3} WHERE k = ?", 0);
+        assertEmpty(execute("SELECT k, v FROM %s WHERE m = {'a': 1, 'b': 2}"));
+        assertRows(execute("SELECT k, v FROM %s WHERE m = {'a': 2, 'b': 3}"), 
row(0, 0), row(0, 1));
+    }
+
+    @Test
+    public void testStaticIndexAndNonStaticIndex() throws Throwable
+    {
+        createTable("CREATE TABLE %s (id int, company text, age int static, 
salary int, PRIMARY KEY(id, company))");
+        createIndex("CREATE INDEX on %s(age)");
+        createIndex("CREATE INDEX on %s(salary)");
+
+        String company1 = "company1", company2 = "company2";
+
+        execute("INSERT INTO %s(id, company, age, salary) VALUES(?, ?, ?, ?)", 
1, company1, 20, 1000);
+        execute("INSERT INTO %s(id, company,      salary) VALUES(?, ?,    ?)", 
1, company2,     2000);
+        execute("INSERT INTO %s(id, company, age, salary) VALUES(?, ?, ?, ?)", 
2, company1, 40, 2000);
+
+        assertRows(execute("SELECT id, company, age, salary FROM %s WHERE age 
= 20 AND salary = 2000 ALLOW FILTERING"),
+                   row(1, company2, 20, 2000));
+    }
+
+    @Test
+    public void testIndexOnUDT() throws Throwable
+    {
+        String typeName = createType("CREATE TYPE %s (street text, city 
text)");
+
+        createTable(String.format(
+            "CREATE TABLE %%s (id int, company text, home frozen<%s> static, 
price int, PRIMARY KEY(id, company))",
+            typeName));
+        createIndex("CREATE INDEX on %s(home)");
+
+        String addressString = "{street: 'Centre', city: 'C'}";
+        String companyName = "Random";
+
+        execute("INSERT INTO %s(id, company, home, price) "
+                + "VALUES(1, '" + companyName + "', " + addressString + ", 
10000)");
+        assertRows(execute("SELECT id, company FROM %s WHERE home = " + 
addressString), row(1, companyName));
+        String newAddressString = "{street: 'Fifth', city: 'P'}";
+
+        execute("UPDATE %s SET home = " + newAddressString + " WHERE id = 1");
+        assertEmpty(execute("SELECT id, company FROM %s WHERE home = " + 
addressString));
+        assertRows(execute("SELECT id, company FROM %s WHERE home = " + 
newAddressString), row(1, companyName));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
----------------------------------------------------------------------
diff --git 
a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
 
b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
index 06f1987..c2a7923 100644
--- 
a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
+++ 
b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
@@ -749,9 +749,6 @@ public class SecondaryIndexTest extends CQLTester
         assertInvalid("CREATE INDEX ON %s (a)");
         assertInvalid("CREATE INDEX ON %s (b)");
         assertInvalid("CREATE INDEX ON %s (c)");
-
-        createTable("CREATE TABLE %s (a int, b int, c int static , PRIMARY KEY 
(a, b))");
-        assertInvalid("CREATE INDEX ON %s (c)");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/cassandra/blob/9e748911/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java 
b/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java
index ee01a47..a037d90 100644
--- a/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java
+++ b/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java
@@ -28,7 +28,6 @@ import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
-
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.Util;
 import org.apache.cassandra.config.ColumnDefinition;
@@ -37,10 +36,8 @@ import org.apache.cassandra.cql3.statements.IndexTarget;
 import org.apache.cassandra.db.marshal.AbstractType;
 import org.apache.cassandra.db.partitions.*;
 import org.apache.cassandra.db.rows.Row;
-import org.apache.cassandra.db.rows.RowIterator;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.index.Index;
-import org.apache.cassandra.index.internal.CassandraIndex;
 import org.apache.cassandra.schema.IndexMetadata;
 import org.apache.cassandra.schema.KeyspaceParams;
 import org.apache.cassandra.utils.ByteBufferUtil;
@@ -66,7 +63,7 @@ public class SecondaryIndexTest
         SchemaLoader.prepareServer();
         SchemaLoader.createKeyspace(KEYSPACE1,
                                     KeyspaceParams.simple(1),
-                                    SchemaLoader.compositeIndexCFMD(KEYSPACE1, 
WITH_COMPOSITE_INDEX, true).gcGraceSeconds(0),
+                                    SchemaLoader.compositeIndexCFMD(KEYSPACE1, 
WITH_COMPOSITE_INDEX, true, true).gcGraceSeconds(0),
                                     SchemaLoader.compositeIndexCFMD(KEYSPACE1, 
COMPOSITE_INDEX_TO_BE_ADDED, false).gcGraceSeconds(0),
                                     SchemaLoader.keysIndexCFMD(KEYSPACE1, 
WITH_KEYS_INDEX, true).gcGraceSeconds(0));
     }
@@ -119,7 +116,7 @@ public class SecondaryIndexTest
                                       .build();
 
         Index.Searcher searcher = 
cfs.indexManager.getBestIndexFor(rc).searcherFor(rc);
-        try (ReadExecutionController executionController = 
rc.executionController(); 
+        try (ReadExecutionController executionController = 
rc.executionController();
              UnfilteredPartitionIterator pi = 
searcher.search(executionController))
         {
             assertTrue(pi.hasNext());
@@ -325,15 +322,30 @@ public class SecondaryIndexTest
     @Test
     public void testDeleteOfInconsistentValuesFromCompositeIndex() throws 
Exception
     {
+        runDeleteOfInconsistentValuesFromCompositeIndexTest(false);
+    }
+
+    @Test
+    public void 
testDeleteOfInconsistentValuesFromCompositeIndexOnStaticColumn() throws 
Exception
+    {
+        runDeleteOfInconsistentValuesFromCompositeIndexTest(true);
+    }
+
+    private void runDeleteOfInconsistentValuesFromCompositeIndexTest(boolean 
isStatic) throws Exception
+    {
         Keyspace keyspace = Keyspace.open(KEYSPACE1);
         String cfName = WITH_COMPOSITE_INDEX;
 
         ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(cfName);
 
-        ByteBuffer col = ByteBufferUtil.bytes("birthdate");
+        String colName = isStatic ? "static" : "birthdate";
+        ByteBuffer col = ByteBufferUtil.bytes(colName);
 
         // create a row and update the author value
-        new RowUpdateBuilder(cfs.metadata, 0, 
"k1").clustering("c").add("birthdate", 10l).build().applyUnsafe();
+        RowUpdateBuilder builder = new RowUpdateBuilder(cfs.metadata, 0, "k1");
+        if (!isStatic)
+            builder = builder.clustering("c");
+        builder.add(colName, 10l).build().applyUnsafe();
 
         // test that the index query fetches this version
         assertIndexedOne(cfs, col, 10l);
@@ -343,9 +355,11 @@ public class SecondaryIndexTest
         assertIndexedOne(cfs, col, 10l);
 
         // now apply another update, but force the index update to be skipped
-        keyspace.apply(new RowUpdateBuilder(cfs.metadata, 1, 
"k1").clustering("c").add("birthdate", 20l).build(),
-                       true,
-                       false);
+        builder = new RowUpdateBuilder(cfs.metadata, 0, "k1");
+        if (!isStatic)
+            builder = builder.clustering("c");
+        builder.add(colName, 20l);
+        keyspace.apply(builder.build(), true, false);
 
         // Now searching the index for either the old or new value should 
return 0 rows
         // because the new value was not indexed and the old value should be 
ignored
@@ -357,7 +371,11 @@ public class SecondaryIndexTest
         // now, reset back to the original value, still skipping the index 
update, to
         // make sure the value was expunged from the index when it was 
discovered to be inconsistent
         // TODO: Figure out why this is re-inserting
-        keyspace.apply(new RowUpdateBuilder(cfs.metadata, 2, 
"k1").clustering("c1").add("birthdate", 10l).build(), true, false);
+        builder = new RowUpdateBuilder(cfs.metadata, 2, "k1");
+        if (!isStatic)
+            builder = builder.clustering("c");
+        builder.add(colName, 10L);
+        keyspace.apply(builder.build(), true, false);
         assertIndexedNone(cfs, col, 20l);
 
         ColumnFamilyStore indexCfs = 
cfs.indexManager.getAllIndexColumnFamilyStores().iterator().next();

Reply via email to