Repository: cassandra
Updated Branches:
  refs/heads/cassandra-2.1 f302eb784 -> 5009ee31b
  refs/heads/trunk f47863e13 -> f1b0c26a1


Add support for custom 2i validation
patch by Sergio Bossa and Andrés de la Peña for CASSANDRA-7575


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

Branch: refs/heads/cassandra-2.1
Commit: 5009ee31b933fcc5843417fd65ab9ff91bb74e73
Parents: f302eb7
Author: Jonathan Ellis <jbel...@apache.org>
Authored: Mon Aug 4 07:32:56 2014 -0500
Committer: Jonathan Ellis <jbel...@apache.org>
Committed: Mon Aug 4 07:32:56 2014 -0500

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 .../cql3/statements/SelectStatement.java        | 15 ++++-
 .../db/index/SecondaryIndexManager.java         | 47 ++++++++++++++++
 .../db/index/SecondaryIndexSearcher.java        | 12 ++++
 .../db/index/PerRowSecondaryIndexTest.java      | 59 +++++++++++++++++---
 5 files changed, 124 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/5009ee31/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index e2cc92b..6ba5e7a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.1
+ * Add support for custom 2i validation (CASSANDRA-7575)
  * Pig support for hadoop CqlInputFormat (CASSANDRA-6454)
  * Add listen_interface and rpc_interface options (CASSANDRA-7417)
  * Improve schema merge performance (CASSANDRA-7444)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5009ee31/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 e4ef0a8..45dd77e 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -36,6 +36,7 @@ import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.ColumnDefinition;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.filter.*;
+import org.apache.cassandra.db.index.SecondaryIndexManager;
 import org.apache.cassandra.db.marshal.*;
 import org.apache.cassandra.dht.*;
 import org.apache.cassandra.exceptions.*;
@@ -357,7 +358,7 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
         if (filter == null)
             return null;
 
-        List<IndexExpression> expressions = getIndexExpressions(options);
+        List<IndexExpression> expressions = 
getValidatedIndexExpressions(options);
         // The LIMIT provided by the user is the number of CQL row he wants 
returned.
         // We want to have getRangeSlice to count the number of columns, not 
the number of keys.
         AbstractBounds<RowPosition> keyBounds = getKeyBounds(options);
@@ -1012,7 +1013,7 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
         return buildBound(b, cfm.clusteringColumns(), columnRestrictions, 
isReversed, cfm.comparator, options);
     }
 
-    public List<IndexExpression> getIndexExpressions(QueryOptions options) 
throws InvalidRequestException
+    public List<IndexExpression> getValidatedIndexExpressions(QueryOptions 
options) throws InvalidRequestException
     {
         if (!usesSecondaryIndexing || restrictedColumns.isEmpty())
             return Collections.emptyList();
@@ -1081,6 +1082,14 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
                 expressions.add(new IndexExpression(def.name.bytes, 
IndexExpression.Operator.EQ, value));
             }
         }
+        
+        if (usesSecondaryIndexing)
+        {
+            ColumnFamilyStore cfs = 
Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
+            SecondaryIndexManager secondaryIndexManager = cfs.indexManager;
+            secondaryIndexManager.validateIndexSearchersForQuery(expressions);
+        }
+        
         return expressions;
     }
 
@@ -1858,7 +1867,7 @@ public class SelectStatement implements CQLStatement, 
MeasurableForPreparedCache
             // the static parts. But 1) we don't have an easy way to do that 
with 2i and 2) since we don't support index on static columns
             // so far, 2i means that you've restricted a non static column, so 
the query is somewhat non-sensical.
             if (stmt.selectsOnlyStaticColumns)
-                throw new InvalidRequestException("Queries using 2ndary 
indexes don't support selecting only static columns");
+                throw new InvalidRequestException("Queries using 2ndary 
indexes don't support selecting only static columns");            
         }
 
         private void verifyOrderingIsAllowed(SelectStatement stmt) throws 
InvalidRequestException

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5009ee31/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 edb9126..669f651 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
@@ -50,6 +50,7 @@ import org.apache.cassandra.db.compaction.CompactionManager;
 import org.apache.cassandra.db.composites.CellName;
 import org.apache.cassandra.db.filter.ExtendedFilter;
 import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.io.sstable.ReducingKeyIterator;
 import org.apache.cassandra.io.sstable.SSTableReader;
 import org.apache.cassandra.utils.FBUtilities;
@@ -563,6 +564,52 @@ public class SecondaryIndexManager
     }
 
     /**
+     * Validates an union of expression index types. It will throw a {@link 
RuntimeException} if
+     * any of the expressions in the provided clause is not valid for its 
index implementation.
+     * @param clause the query clause
+     * @throws org.apache.cassandra.exceptions.InvalidRequestException in case 
of validation errors
+     */
+    public void validateIndexSearchersForQuery(List<IndexExpression> clause) 
throws InvalidRequestException
+    {
+        // Group by index type
+        Map<String, Set<IndexExpression>> expressionsByIndexType = new 
HashMap<>();
+        Map<String, Set<ByteBuffer>> columnsByIndexType = new HashMap<>();
+        for (IndexExpression indexExpression : clause)
+        {
+            SecondaryIndex index = getIndexForColumn(indexExpression.column);
+
+            if (index == null)
+                continue;
+
+            String canonicalIndexName = index.getClass().getCanonicalName();
+            Set<IndexExpression> expressions = 
expressionsByIndexType.get(canonicalIndexName);
+            Set<ByteBuffer> columns = 
columnsByIndexType.get(canonicalIndexName);
+            if (expressions == null)
+            {
+                expressions = new HashSet<>();
+                columns = new HashSet<>();
+                expressionsByIndexType.put(canonicalIndexName, expressions);
+                columnsByIndexType.put(canonicalIndexName, columns);
+            }
+
+            expressions.add(indexExpression);
+            columns.add(indexExpression.column);
+        }
+
+        // Validate
+        for (Map.Entry<String, Set<IndexExpression>> expressions : 
expressionsByIndexType.entrySet())
+        {
+            Set<ByteBuffer> columns = 
columnsByIndexType.get(expressions.getKey());
+            SecondaryIndex secondaryIndex = 
getIndexForColumn(columns.iterator().next());
+            SecondaryIndexSearcher searcher = 
secondaryIndex.createSecondaryIndexSearcher(columns);
+            for (IndexExpression expression : expressions.getValue())
+            {
+                searcher.validate(expression);
+            }
+        }
+    }
+
+    /**
      * Performs a search across a number of column indexes
      *
      * @param filter the column range to restrict to

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5009ee31/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 395708a..1239c29 100644
--- a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
+++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java
@@ -22,6 +22,7 @@ import java.util.*;
 
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.filter.ExtendedFilter;
+import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.tracing.Tracing;
 import org.apache.cassandra.utils.FBUtilities;
 
@@ -62,6 +63,17 @@ public abstract class SecondaryIndexSearcher
         }
         return false;
     }
+    
+    /**
+     * Validates the specified {@link IndexExpression}. It will throw an 
{@link org.apache.cassandra.exceptions.InvalidRequestException}
+     * if the provided clause is not valid for the index implementation.
+     *
+     * @param indexExpression An {@link IndexExpression} to be validated
+     * @throws org.apache.cassandra.exceptions.InvalidRequestException in case 
of validation errors
+     */
+    public void validate(IndexExpression indexExpression) throws 
InvalidRequestException
+    {
+    }
 
     protected IndexExpression 
highestSelectivityPredicate(List<IndexExpression> clause)
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5009ee31/test/unit/org/apache/cassandra/db/index/PerRowSecondaryIndexTest.java
----------------------------------------------------------------------
diff --git 
a/test/unit/org/apache/cassandra/db/index/PerRowSecondaryIndexTest.java 
b/test/unit/org/apache/cassandra/db/index/PerRowSecondaryIndexTest.java
index 158dd2c..c6a80ea 100644
--- a/test/unit/org/apache/cassandra/db/index/PerRowSecondaryIndexTest.java
+++ b/test/unit/org/apache/cassandra/db/index/PerRowSecondaryIndexTest.java
@@ -18,8 +18,11 @@
 package org.apache.cassandra.db.index;
 
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 import org.junit.Before;
@@ -28,14 +31,16 @@ import org.junit.Test;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.Util;
 import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.db.Cell;
-import org.apache.cassandra.db.ColumnFamily;
-import org.apache.cassandra.db.ColumnFamilyStore;
-import org.apache.cassandra.db.DecoratedKey;
-import org.apache.cassandra.db.Mutation;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.columniterator.IdentityQueryFilter;
 import org.apache.cassandra.db.composites.CellName;
+import org.apache.cassandra.db.filter.ExtendedFilter;
 import org.apache.cassandra.db.filter.QueryFilter;
+import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.exceptions.InvalidRequestException;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.concurrent.OpOrder;
 
@@ -114,6 +119,30 @@ public class PerRowSecondaryIndexTest extends SchemaLoader
 
         assertTrue(Arrays.equals("k3".getBytes(), 
PerRowSecondaryIndexTest.TestIndex.LAST_INDEXED_KEY.array()));
     }
+    
+    @Test
+    public void testInvalidSearch() throws IOException
+    {
+        Mutation rm;
+        rm = new Mutation("PerRowSecondaryIndex", ByteBufferUtil.bytes("k4"));
+        rm.add("Indexed1", Util.cellname("indexed"), 
ByteBufferUtil.bytes("foo"), 1);
+        rm.apply();
+        
+        // test we can search:
+        UntypedResultSet result = QueryProcessor.executeInternal("SELECT * 
FROM \"PerRowSecondaryIndex\".\"Indexed1\" WHERE indexed = 'foo'");
+        assertEquals(1, result.size());
+
+        // test we can't search if the searcher doesn't validate the 
expression:
+        try
+        {
+            QueryProcessor.executeInternal("SELECT * FROM 
\"PerRowSecondaryIndex\".\"Indexed1\" WHERE indexed = 'invalid'");
+            fail("Query should have been invalid!");
+        }
+        catch (Exception e)
+        {
+            assertTrue(e instanceof InvalidRequestException || (e.getCause() 
!= null && (e.getCause() instanceof InvalidRequestException)));
+        }
+    }
 
     public static class TestIndex extends PerRowSecondaryIndex
     {
@@ -165,7 +194,23 @@ public class PerRowSecondaryIndexTest extends SchemaLoader
         @Override
         protected SecondaryIndexSearcher 
createSecondaryIndexSearcher(Set<ByteBuffer> columns)
         {
-            return null;
+            return new SecondaryIndexSearcher(baseCfs.indexManager, columns)
+            {
+                
+                @Override
+                public List<Row> search(ExtendedFilter filter)
+                {
+                    return Arrays.asList(new Row(LAST_INDEXED_KEY, 
LAST_INDEXED_ROW));
+                }
+
+                @Override
+                public void validate(IndexExpression indexExpression) throws 
InvalidRequestException
+                {
+                    if 
(indexExpression.value.equals(ByteBufferUtil.bytes("invalid")))
+                        throw new InvalidRequestException("Invalid search!");
+                }
+                
+            };
         }
 
         @Override
@@ -176,7 +221,7 @@ public class PerRowSecondaryIndexTest extends SchemaLoader
         @Override
         public ColumnFamilyStore getIndexCfs()
         {
-            return null;
+            return baseCfs;
         }
 
         @Override

Reply via email to