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