Merge branch 'cassandra-2.2' into cassandra-3.0
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/943e732c Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/943e732c Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/943e732c Branch: refs/heads/cassandra-3.0 Commit: 943e732caeb1da59e5e9af5479cd9d75ca9d9030 Parents: 474d3bf dff7b44 Author: Sam Tunnicliffe <s...@beobal.com> Authored: Fri Apr 29 11:50:03 2016 +0100 Committer: Sam Tunnicliffe <s...@beobal.com> Committed: Fri Apr 29 11:55:07 2016 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../restrictions/PrimaryKeyRestrictionSet.java | 71 +++++++++++++++++++- .../restrictions/StatementRestrictions.java | 34 +++++++--- .../SelectSingleColumnRelationTest.java | 4 ++ 4 files changed, 97 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/943e732c/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 268d011,fb06cd6..3184cce --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,19 -1,5 +1,20 @@@ -2.2.7 +3.0.6 + * Fix paging for range queries where all clustering columns are specified (CASSANDRA-11669) + * Don't require HEAP_NEW_SIZE to be set when using G1 (CASSANDRA-11600) + * Fix sstabledump not showing cells after tombstone marker (CASSANDRA-11654) + * Ignore all LocalStrategy keyspaces for streaming and other related + operations (CASSANDRA-11627) + * Ensure columnfilter covers indexed columns for thrift 2i queries (CASSANDRA-11523) + * Only open one sstable scanner per sstable (CASSANDRA-11412) + * Option to specify ProtocolVersion in cassandra-stress (CASSANDRA-11410) + * ArithmeticException in avgFunctionForDecimal (CASSANDRA-11485) + * LogAwareFileLister should only use OLD sstable files in current folder to determine disk consistency (CASSANDRA-11470) + * Notify indexers of expired rows during compaction (CASSANDRA-11329) + * Properly respond with ProtocolError when a v1/v2 native protocol + header is received (CASSANDRA-11464) + * Validate that num_tokens and initial_token are consistent with one another (CASSANDRA-10120) +Merged from 2.2: + * Restore ability to filter on clustering columns when using a 2i (CASSANDRA-11510) * JSON datetime formatting needs timezone (CASSANDRA-11137) * Fix is_dense recalculation for Thrift-updated tables (CASSANDRA-11502) * Remove unnescessary file existence check during anticompaction (CASSANDRA-11660) http://git-wip-us.apache.org/repos/asf/cassandra/blob/943e732c/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java index 8121858,0c10f13..e063f91 --- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java @@@ -64,27 -67,48 +64,58 @@@ final class PrimaryKeyRestrictionSet ex */ private boolean contains; + /** + * <code>true</code> if the restrictions corresponding to a partition key, <code>false</code> if it's clustering columns. + */ + private boolean isPartitionKey; + /** + * If restrictions apply to clustering columns, we need to check whether they can be satisfied by an index lookup + * as this affects which other restrictions can legally be specified (if an index is present, we are more lenient + * about what additional filtering can be performed on the results of a lookup - see CASSANDRA-11510). + * + * We don't hold a reference to the SecondaryIndexManager itself as this is not strictly a singleton (although + * we often treat is as one), the field would also require annotation with @Unmetered to avoid blowing up the + * object size (used when calculating the size of prepared statements for caching). Instead, we refer to the + * CFMetaData and retrieve the index manager when necessary. + * + * There are a couple of scenarios where the CFM can be null (and we make sure and test for null when we use it): + * * where an empty set of restrictions are created for use in processing query results - see + * SelectStatement.forSelection + * * where the restrictions apply to partition keys and not clustering columns e.g. + * StatementRestrictions.partitionKeyRestrictions + * * in unit tests (in particular PrimaryKeyRestrictionSetTest which is primarily concerned with the correct + * generation of bounds when secondary indexes are not used). + */ + private final CFMetaData cfm; + - public PrimaryKeyRestrictionSet(CType ctype) + public PrimaryKeyRestrictionSet(ClusteringComparator comparator, boolean isPartitionKey) { - this(ctype, null); ++ this(comparator, isPartitionKey, null); + } + - public PrimaryKeyRestrictionSet(CType ctype, CFMetaData cfm) ++ public PrimaryKeyRestrictionSet(ClusteringComparator comparator, boolean isPartitionKey, CFMetaData cfm) + { - super(ctype); + super(comparator); ++ ++ if (cfm != null) ++ assert !isPartitionKey; ++ + this.cfm = cfm; this.restrictions = new RestrictionSet(); this.eq = true; + this.isPartitionKey = isPartitionKey; } private PrimaryKeyRestrictionSet(PrimaryKeyRestrictionSet primaryKeyRestrictions, - Restriction restriction) throws InvalidRequestException + Restriction restriction) throws InvalidRequestException { - super(primaryKeyRestrictions.ctype); + super(primaryKeyRestrictions.comparator); this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction); + this.isPartitionKey = primaryKeyRestrictions.isPartitionKey; + this.cfm = primaryKeyRestrictions.cfm; - if (!primaryKeyRestrictions.isEmpty()) + if (!primaryKeyRestrictions.isEmpty() && !hasSupportingIndex(restriction)) { ColumnDefinition lastRestrictionStart = primaryKeyRestrictions.restrictions.lastRestriction().getFirstColumn(); ColumnDefinition newRestrictionStart = restriction.getFirstColumn(); @@@ -110,15 -134,12 +141,22 @@@ this.eq = true; } + private List<ByteBuffer> toByteBuffers(SortedSet<? extends ClusteringPrefix> clusterings) + { + // It's currently a tad hard to follow that this is only called for partition key so we should fix that + List<ByteBuffer> l = new ArrayList<>(clusterings.size()); + for (ClusteringPrefix clustering : clusterings) + l.add(CFMetaData.serializePartitionKey(clustering)); + return l; + } + + private boolean hasSupportingIndex(Restriction restriction) + { + return cfm != null - && restriction.hasSupportingIndex(Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfId).indexManager); ++ && restriction.hasSupportingIndex(Keyspace.openAndGetStore(cfm).indexManager); ++ + } + @Override public boolean isSlice() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/943e732c/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java index 763a7be,5b7c58d..942d1f9 --- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java @@@ -125,53 -112,28 +126,58 @@@ public final class StatementRestriction VariableSpecifications boundNames, boolean selectsOnlyStaticColumns, boolean selectACollection, - boolean useFiltering) + boolean useFiltering, + boolean forView) throws InvalidRequestException { - this(type, cfm); ++ this.type = type; + this.cfm = cfm; - this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsCType()); - this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator, cfm); ++ this.partitionKeyRestrictions = new PrimaryKeyRestrictionSet(cfm.getKeyValidatorAsClusteringComparator(), true); ++ this.clusteringColumnsRestrictions = new PrimaryKeyRestrictionSet(cfm.comparator, false, cfm); + this.nonPrimaryKeyRestrictions = new RestrictionSet(); ++ this.notNullColumns = new HashSet<>(); /* - * WHERE clause. For a given entity, rules are: - EQ relation conflicts with anything else (including a 2nd EQ) - * - Can't have more than one LT(E) relation (resp. GT(E) relation) - IN relation are restricted to row keys - * (for now) and conflicts with anything else (we could allow two IN for the same entity but that doesn't seem - * very useful) - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value - * in CQL so far) + * WHERE clause. For a given entity, rules are: + * - EQ relation conflicts with anything else (including a 2nd EQ) + * - Can't have more than one LT(E) relation (resp. GT(E) relation) + * - IN relation are restricted to row keys (for now) and conflicts with anything else (we could + * allow two IN for the same entity but that doesn't seem very useful) + * - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value + * in CQL so far) */ - for (Relation relation : whereClause) - addRestriction(relation.toRestriction(cfm, boundNames)); + for (Relation relation : whereClause.relations) + { + if (relation.operator() == Operator.IS_NOT) + { + if (!forView) + throw new InvalidRequestException("Unsupported restriction: " + relation); + + for (ColumnDefinition def : relation.toRestriction(cfm, boundNames).getColumnDefs()) + this.notNullColumns.add(def); + } + else + { + addRestriction(relation.toRestriction(cfm, boundNames)); + } + } + + boolean hasQueriableClusteringColumnIndex = false; + boolean hasQueriableIndex = false; - SecondaryIndexManager secondaryIndexManager = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName).indexManager; - boolean hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager); - boolean hasQueriableIndex = hasQueriableClusteringColumnIndex - || partitionKeyRestrictions.hasSupportingIndex(secondaryIndexManager) - || nonPrimaryKeyRestrictions.hasSupportingIndex(secondaryIndexManager); + if (type.allowUseOfSecondaryIndices()) + { + ColumnFamilyStore cfs = Keyspace.open(cfm.ksName).getColumnFamilyStore(cfm.cfName); + SecondaryIndexManager secondaryIndexManager = cfs.indexManager; + + if (whereClause.containsCustomExpressions()) + processCustomIndexExpressions(whereClause.expressions, boundNames, secondaryIndexManager); + + hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(secondaryIndexManager); + hasQueriableIndex = !indexRestrictions.getCustomIndexExpressions().isEmpty() + || hasQueriableClusteringColumnIndex + || partitionKeyRestrictions.hasSupportingIndex(secondaryIndexManager) + || nonPrimaryKeyRestrictions.hasSupportingIndex(secondaryIndexManager); + } // At this point, the select statement if fully constructed, but we still have a few things to validate processPartitionKeyRestrictions(hasQueriableIndex); @@@ -430,130 -301,61 +436,136 @@@ * Processes the clustering column restrictions. * * @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise + * @param selectsOnlyStaticColumns <code>true</code> if the selected or modified columns are all statics, + * <code>false</code> otherwise. * @param selectACollection <code>true</code> if the query should return a collection column - * @throws InvalidRequestException if the request is invalid */ private void processClusteringColumnsRestrictions(boolean hasQueriableIndex, - boolean selectACollection) throws InvalidRequestException + boolean selectsOnlyStaticColumns, + boolean selectACollection, + boolean forView) throws InvalidRequestException { - checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection, - "Cannot restrict clustering columns by IN relations when a collection is selected by the query"); - checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex, - "Cannot restrict clustering columns by a CONTAINS relation without a secondary index"); + checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.isSlice(), + "Slice restrictions are not supported on the clustering columns in %s statements", type); - if (hasClusteringColumnsRestriction() && clusteringRestrictionsNeedFiltering()) + if (!type.allowClusteringColumnSlices() + && (!cfm.isCompactTable() || (cfm.isCompactTable() && !hasClusteringColumnsRestriction()))) { - if (hasQueriableIndex) - { - usesSecondaryIndexing = true; - return; - } - - List<ColumnDefinition> clusteringColumns = cfm.clusteringColumns(); - List<ColumnDefinition> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs()); + if (!selectsOnlyStaticColumns && hasUnrestrictedClusteringColumns()) + throw invalidRequest("Some clustering keys are missing: %s", + Joiner.on(", ").join(getUnrestrictedClusteringColumns())); + } + else + { + checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection, + "Cannot restrict clustering columns by IN relations when a collection is selected by the query"); + checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex, + "Cannot restrict clustering columns by a CONTAINS relation without a secondary index"); - if (hasClusteringColumnsRestriction()) - for (int i = 0, m = restrictedColumns.size(); i < m; i++) ++ if (hasClusteringColumnsRestriction() && clusteringRestrictionsNeedFiltering()) { - ColumnDefinition clusteringColumn = clusteringColumns.get(i); - ColumnDefinition restrictedColumn = restrictedColumns.get(i); ++ if (hasQueriableIndex || forView) ++ { ++ usesSecondaryIndexing = true; ++ return; ++ } ++ + List<ColumnDefinition> clusteringColumns = cfm.clusteringColumns(); + List<ColumnDefinition> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs()); - if (!clusteringColumn.equals(restrictedColumn)) + for (int i = 0, m = restrictedColumns.size(); i < m; i++) { - throw invalidRequest( - "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted", - restrictedColumn.name, - clusteringColumn.name); + ColumnDefinition clusteringColumn = clusteringColumns.get(i); + ColumnDefinition restrictedColumn = restrictedColumns.get(i); + + if (!clusteringColumn.equals(restrictedColumn)) + { - checkTrue(hasQueriableIndex || forView, - "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted", - restrictedColumn.name, - clusteringColumn.name); - - usesSecondaryIndexing = true; // handle gaps and non-keyrange cases. - break; ++ throw invalidRequest( ++ "PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted", ++ restrictedColumn.name, ++ clusteringColumn.name); + } } } } + } - if (clusteringColumnsRestrictions.isContains()) - usesSecondaryIndexing = true; + public final boolean clusteringRestrictionsNeedFiltering() + { + assert clusteringColumnsRestrictions instanceof PrimaryKeyRestrictionSet; + return ((PrimaryKeyRestrictionSet) clusteringColumnsRestrictions).needsFiltering(); } - public List<IndexExpression> getIndexExpressions(SecondaryIndexManager indexManager, - QueryOptions options) throws InvalidRequestException + /** + * Returns the clustering columns that are not restricted. + * @return the clustering columns that are not restricted. + */ + private Collection<ColumnIdentifier> getUnrestrictedClusteringColumns() + { + List<ColumnDefinition> missingClusteringColumns = new ArrayList<>(cfm.clusteringColumns()); + missingClusteringColumns.removeAll(new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs())); + return ColumnDefinition.toIdentifiers(missingClusteringColumns); + } + + /** + * Checks if some clustering columns are not restricted. + * @return <code>true</code> if some clustering columns are not restricted, <code>false</code> otherwise. + */ + private boolean hasUnrestrictedClusteringColumns() + { + return cfm.clusteringColumns().size() != clusteringColumnsRestrictions.size(); + } + + private void processCustomIndexExpressions(List<CustomIndexExpression> expressions, + VariableSpecifications boundNames, + SecondaryIndexManager indexManager) + { + if (!MessagingService.instance().areAllNodesAtLeast30()) + throw new InvalidRequestException("Please upgrade all nodes to at least 3.0 before using custom index expressions"); + + if (expressions.size() > 1) + throw new InvalidRequestException(IndexRestrictions.MULTIPLE_EXPRESSIONS); + + CustomIndexExpression expression = expressions.get(0); + + CFName cfName = expression.targetIndex.getCfName(); + if (cfName.hasKeyspace() + && !expression.targetIndex.getKeyspace().equals(cfm.ksName)) + throw IndexRestrictions.invalidIndex(expression.targetIndex, cfm); + + if (cfName.getColumnFamily() != null && !cfName.getColumnFamily().equals(cfm.cfName)) + throw IndexRestrictions.invalidIndex(expression.targetIndex, cfm); + + if (!cfm.getIndexes().has(expression.targetIndex.getIdx())) + throw IndexRestrictions.indexNotFound(expression.targetIndex, cfm); + + Index index = indexManager.getIndex(cfm.getIndexes().get(expression.targetIndex.getIdx()).get()); + + if (!index.getIndexMetadata().isCustom()) + throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex); + + AbstractType<?> expressionType = index.customExpressionValueType(); + if (expressionType == null) + throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex); + + expression.prepareValue(cfm, expressionType, boundNames); + + indexRestrictions.add(expression); + } + + public RowFilter getRowFilter(SecondaryIndexManager indexManager, QueryOptions options) { - if (!usesSecondaryIndexing || indexRestrictions.isEmpty()) - return Collections.emptyList(); + if (indexRestrictions.isEmpty()) + return RowFilter.NONE; + + RowFilter filter = RowFilter.create(); + for (Restrictions restrictions : indexRestrictions.getRestrictions()) + restrictions.addRowFilterTo(filter, indexManager, options); - List<IndexExpression> expressions = new ArrayList<>(); - for (Restrictions restrictions : indexRestrictions) - restrictions.addIndexExpressionTo(expressions, indexManager, options); + for (CustomIndexExpression expression : indexRestrictions.getCustomIndexExpressions()) + expression.addToRowFilter(filter, cfm, options); - return expressions; + return filter; } /** http://git-wip-us.apache.org/repos/asf/cassandra/blob/943e732c/test/unit/org/apache/cassandra/cql3/validation/operations/SelectSingleColumnRelationTest.java ----------------------------------------------------------------------