Merge branch 'cassandra-2.1' into trunk
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/71778eec Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/71778eec Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/71778eec Branch: refs/heads/trunk Commit: 71778eec24e42512365cb562aecf07b5fbd28265 Parents: 258e59f 9c7a601 Author: Tyler Hobbs <[email protected]> Authored: Thu Mar 5 12:34:56 2015 -0600 Committer: Tyler Hobbs <[email protected]> Committed: Thu Mar 5 12:34:56 2015 -0600 ---------------------------------------------------------------------- CHANGES.txt | 2 + .../cassandra/cql3/MultiColumnRelation.java | 17 +- .../AbstractPrimaryKeyRestrictions.java | 12 + .../cql3/restrictions/AbstractRestriction.java | 5 +- .../ForwardingPrimaryKeyRestrictions.java | 25 + .../restrictions/MultiColumnRestriction.java | 269 ++--- .../restrictions/PrimaryKeyRestrictionSet.java | 325 +++++ .../restrictions/PrimaryKeyRestrictions.java | 6 +- .../cql3/restrictions/Restriction.java | 49 +- .../cql3/restrictions/RestrictionSet.java | 252 ++++ .../cql3/restrictions/Restrictions.java | 4 +- .../SingleColumnPrimaryKeyRestrictions.java | 327 ----- .../restrictions/SingleColumnRestriction.java | 111 +- .../restrictions/SingleColumnRestrictions.java | 210 ---- .../restrictions/StatementRestrictions.java | 16 +- .../cql3/restrictions/TokenRestriction.java | 21 +- .../cql3/statements/ModificationStatement.java | 66 +- .../db/composites/CompositesBuilder.java | 117 +- .../cassandra/cql3/MultiColumnRelationTest.java | 206 +++- .../PrimaryKeyRestrictionSetTest.java | 1120 ++++++++++++++++++ 20 files changed, 2293 insertions(+), 867 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/CHANGES.txt ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/MultiColumnRelation.java index 7e7a901,37eb69e..b54bdd0 --- a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java +++ b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java @@@ -112,109 -91,54 +112,106 @@@ public class MultiColumnRelation extend /** * For non-IN relations, returns the Tuples.Literal or Tuples.Raw marker for a single tuple. + * @return a Tuples.Literal for non-IN relations or Tuples.Raw marker for a single tuple. */ - public Term.MultiColumnRaw getValue() + private Term.MultiColumnRaw getValue() { - assert relationType != Operator.IN; - return valuesOrMarker; + return relationType == Operator.IN ? inMarker : valuesOrMarker; } - /** - * For IN relations, returns the list of Tuples.Literal instances or Tuples.Raw markers. - * If a single IN marker was used, this will return null; - */ - public List<? extends Term.MultiColumnRaw> getInValues() + @Override + public boolean isMultiColumn() { + return true; + } - return inValues; + @Override + protected Restriction newEQRestriction(CFMetaData cfm, + VariableSpecifications boundNames) throws InvalidRequestException + { + List<ColumnDefinition> receivers = receivers(cfm); + Term term = toTerm(receivers, getValue(), cfm.ksName, boundNames); - return new MultiColumnRestriction.EQ(cfm.comparator, receivers, term); ++ return new MultiColumnRestriction.EQ(receivers, term); } - /** - * For IN relations, returns the single marker for the IN values if there is one, otherwise null. - */ - public Tuples.INRaw getInMarker() + @Override + protected Restriction newINRestriction(CFMetaData cfm, + VariableSpecifications boundNames) throws InvalidRequestException { - return inMarker; + List<ColumnDefinition> receivers = receivers(cfm); + List<Term> terms = toTerms(receivers, inValues, cfm.ksName, boundNames); + if (terms == null) + { + Term term = toTerm(receivers, getValue(), cfm.ksName, boundNames); - return new MultiColumnRestriction.InWithMarker(cfm.comparator, receivers, (AbstractMarker) term); ++ return new MultiColumnRestriction.InWithMarker(receivers, (AbstractMarker) term); + } - return new MultiColumnRestriction.InWithValues(cfm.comparator, receivers, terms); ++ return new MultiColumnRestriction.InWithValues(receivers, terms); } - public boolean isMultiColumn() + @Override + protected Restriction newSliceRestriction(CFMetaData cfm, + VariableSpecifications boundNames, + Bound bound, + boolean inclusive) throws InvalidRequestException { - return true; + List<ColumnDefinition> receivers = receivers(cfm); + Term term = toTerm(receivers(cfm), getValue(), cfm.ksName, boundNames); - return new MultiColumnRestriction.Slice(cfm.comparator, receivers, bound, inclusive, term); ++ return new MultiColumnRestriction.Slice(receivers, bound, inclusive, term); } @Override - public String toString() + protected Restriction newContainsRestriction(CFMetaData cfm, + VariableSpecifications boundNames, + boolean isKey) throws InvalidRequestException { - if (relationType == Operator.IN) + throw invalidRequest("%s cannot be used for Multi-column relations", operator()); + } + + @Override + protected Term toTerm(List<? extends ColumnSpecification> receivers, + Raw raw, + String keyspace, + VariableSpecifications boundNames) throws InvalidRequestException + { + Term term = ((MultiColumnRaw) raw).prepare(keyspace, receivers); + term.collectMarkerSpecification(boundNames); + return term; + } + + protected List<ColumnDefinition> receivers(CFMetaData cfm) throws InvalidRequestException + { + List<ColumnDefinition> names = new ArrayList<>(getEntities().size()); + int previousPosition = -1; + for (ColumnIdentifier.Raw raw : getEntities()) { - StringBuilder sb = new StringBuilder(Tuples.tupleToString(entities)); - sb.append(" IN "); - sb.append(inMarker != null ? '?' : Tuples.tupleToString(inValues)); - return sb.toString(); + ColumnDefinition def = toColumnDefinition(cfm, raw); + checkTrue(def.isClusteringColumn(), "Multi-column relations can only be applied to clustering columns but was applied to: %s", def.name); + checkFalse(names.contains(def), "Column \"%s\" appeared twice in a relation: %s", def.name, this); + + // check that no clustering columns were skipped - if (def.position() != previousPosition + 1) - { - checkFalse(previousPosition == -1, "Clustering columns may not be skipped in multi-column relations. " + - "They should appear in the PRIMARY KEY order. Got %s", this); - throw invalidRequest("Clustering columns must appear in the PRIMARY KEY order in multi-column relations: %s", this); - } ++ checkFalse(previousPosition != -1 && def.position() != previousPosition + 1, ++ "Clustering columns must appear in the PRIMARY KEY order in multi-column relations: %s", this); ++ + names.add(def); + previousPosition = def.position(); } - else + return names; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(Tuples.tupleToString(entities)); + if (isIN()) { - StringBuilder sb = new StringBuilder(Tuples.tupleToString(entities)); - sb.append(" "); - sb.append(relationType); - sb.append(" "); - sb.append(valuesOrMarker); - return sb.toString(); + return builder.append(" IN ") + .append(inMarker != null ? '?' : Tuples.tupleToString(inValues)) + .toString(); } + return builder.append(" ") + .append(relationType) + .append(" ") + .append(valuesOrMarker) + .toString(); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java index 0107603,0000000..2eaa386 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractPrimaryKeyRestrictions.java @@@ -1,48 -1,0 +1,60 @@@ +/* + * 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.restrictions; + ++import java.nio.ByteBuffer; ++import java.util.List; ++ ++import org.apache.cassandra.cql3.QueryOptions; ++import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.composites.CType; ++import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * Base class for <code>PrimaryKeyRestrictions</code>. + */ +abstract class AbstractPrimaryKeyRestrictions extends AbstractRestriction implements PrimaryKeyRestrictions +{ + /** + * The composite type. + */ + protected final CType ctype; + + public AbstractPrimaryKeyRestrictions(CType ctype) + { + this.ctype = ctype; + } + + @Override ++ public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException ++ { ++ return values(options); ++ } ++ ++ @Override + public final boolean isEmpty() + { + return getColumnDefs().isEmpty(); + } + + @Override + public final int size() + { + return getColumnDefs().size(); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java index 0ae7b22,0000000..b212f4d mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/AbstractRestriction.java @@@ -1,129 -1,0 +1,130 @@@ +/* + * 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.restrictions; + +import java.nio.ByteBuffer; +import java.util.List; + +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.statements.Bound; ++import org.apache.cassandra.db.composites.CompositesBuilder; +import org.apache.cassandra.exceptions.InvalidRequestException; + +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; + +/** + * Base class for <code>Restriction</code>s + */ +abstract class AbstractRestriction implements Restriction +{ + @Override + public boolean isOnToken() + { + return false; + } + + @Override + public boolean isMultiColumn() + { + return false; + } + + @Override + public boolean isSlice() + { + return false; + } + + @Override + public boolean isEQ() + { + return false; + } + + @Override + public boolean isIN() + { + return false; + } + + @Override + public boolean isContains() + { + return false; + } + + @Override + public boolean hasBound(Bound b) + { + return true; + } + + @Override - public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException ++ public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options) + { - return values(options); ++ return appendTo(builder, options); + } + + @Override + public boolean isInclusive(Bound b) + { + return true; + } + + protected static ByteBuffer validateIndexedValue(ColumnSpecification columnSpec, + ByteBuffer value) + throws InvalidRequestException + { + checkNotNull(value, "Unsupported null value for indexed column %s", columnSpec.name); + checkFalse(value.remaining() > 0xFFFF, "Index expression values may not be larger than 64K"); + return value; + } + + /** + * Checks if the specified term is using the specified function. + * + * @param term the term to check + * @param ksName the function keyspace name + * @param functionName the function name + * @return <code>true</code> if the specified term is using the specified function, <code>false</code> otherwise. + */ + protected static final boolean usesFunction(Term term, String ksName, String functionName) + { + return term != null && term.usesFunction(ksName, functionName); + } + + /** + * Checks if one of the specified term is using the specified function. + * + * @param terms the terms to check + * @param ksName the function keyspace name + * @param functionName the function name + * @return <code>true</code> if onee of the specified term is using the specified function, <code>false</code> otherwise. + */ + protected static final boolean usesFunction(List<Term> terms, String ksName, String functionName) + { + if (terms != null) + for (Term value : terms) + if (usesFunction(value, ksName, functionName)) + return true; + return false; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java index 5492c2b,0000000..537481f mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/ForwardingPrimaryKeyRestrictions.java @@@ -1,160 -1,0 +1,185 @@@ +/* + * 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.restrictions; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.List; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.IndexExpression; +import org.apache.cassandra.db.composites.Composite; ++import org.apache.cassandra.db.composites.CompositesBuilder; +import org.apache.cassandra.db.index.SecondaryIndexManager; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * A <code>PrimaryKeyRestrictions</code> which forwards all its method calls to another + * <code>PrimaryKeyRestrictions</code>. Subclasses should override one or more methods to modify the behavior + * of the backing <code>PrimaryKeyRestrictions</code> as desired per the decorator pattern. + */ +abstract class ForwardingPrimaryKeyRestrictions implements PrimaryKeyRestrictions +{ + /** + * Returns the backing delegate instance that methods are forwarded to. + * @return the backing delegate instance that methods are forwarded to. + */ + protected abstract PrimaryKeyRestrictions getDelegate(); + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return getDelegate().usesFunction(ksName, functionName); + } + + @Override + public Collection<ColumnDefinition> getColumnDefs() + { + return getDelegate().getColumnDefs(); + } + + @Override ++ public ColumnDefinition getFirstColumn() ++ { ++ return getDelegate().getFirstColumn(); ++ } ++ ++ @Override ++ public ColumnDefinition getLastColumn() ++ { ++ return getDelegate().getLastColumn(); ++ } ++ ++ @Override + public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException + { + return getDelegate().mergeWith(restriction); + } + + @Override + public boolean hasSupportingIndex(SecondaryIndexManager secondaryIndexManager) + { + return getDelegate().hasSupportingIndex(secondaryIndexManager); + } + + @Override + public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException + { + return getDelegate().values(options); + } + + @Override ++ public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options) ++ { ++ return getDelegate().appendTo(builder, options); ++ } ++ ++ @Override + public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException + { + return getDelegate().valuesAsComposites(options); + } + + @Override + public List<ByteBuffer> bounds(Bound bound, QueryOptions options) throws InvalidRequestException + { + return getDelegate().bounds(bound, options); + } + + @Override + public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException + { + return getDelegate().boundsAsComposites(bound, options); + } + + @Override ++ public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options) ++ { ++ return getDelegate().appendBoundTo(builder, bound, options); ++ } ++ ++ @Override + public boolean isInclusive(Bound bound) + { + return getDelegate().isInclusive(bound.reverse()); + } + + @Override + public boolean isEmpty() + { + return getDelegate().isEmpty(); + } + + @Override + public int size() + { + return getDelegate().size(); + } + + @Override + public boolean isOnToken() + { + return getDelegate().isOnToken(); + } + + @Override + public boolean isSlice() + { + return getDelegate().isSlice(); + } + + @Override + public boolean isEQ() + { + return getDelegate().isEQ(); + } + + @Override + public boolean isIN() + { + return getDelegate().isIN(); + } + + @Override + public boolean isContains() + { + return getDelegate().isContains(); + } + + @Override + public boolean isMultiColumn() + { + return getDelegate().isMultiColumn(); + } + + @Override + public boolean hasBound(Bound b) + { + return getDelegate().hasBound(b); + } + + @Override + public void addIndexExpressionTo(List<IndexExpression> expressions, + SecondaryIndexManager indexManager, + QueryOptions options) throws InvalidRequestException + { + getDelegate().addIndexExpressionTo(expressions, indexManager, options); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java index 9f6ab4c,0000000..be6d905 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java @@@ -1,519 -1,0 +1,452 @@@ +/* + * 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.restrictions; + +import java.nio.ByteBuffer; +import java.util.*; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.AbstractMarker; +import org.apache.cassandra.cql3.Operator; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.Tuples; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.IndexExpression; - import org.apache.cassandra.db.composites.CBuilder; - import org.apache.cassandra.db.composites.CType; - import org.apache.cassandra.db.composites.Composite; - import org.apache.cassandra.db.composites.Composites; ++import org.apache.cassandra.db.composites.*; +import org.apache.cassandra.db.index.SecondaryIndex; +import org.apache.cassandra.db.index.SecondaryIndexManager; +import org.apache.cassandra.exceptions.InvalidRequestException; + +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; +import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + - public abstract class MultiColumnRestriction extends AbstractPrimaryKeyRestrictions ++public abstract class MultiColumnRestriction extends AbstractRestriction +{ + /** + * The columns to which the restriction apply. + */ + protected final List<ColumnDefinition> columnDefs; + - public MultiColumnRestriction(CType ctype, List<ColumnDefinition> columnDefs) ++ public MultiColumnRestriction(List<ColumnDefinition> columnDefs) + { - super(ctype); + this.columnDefs = columnDefs; + } + + @Override + public boolean isMultiColumn() + { + return true; + } + + @Override - public Collection<ColumnDefinition> getColumnDefs() ++ public ColumnDefinition getFirstColumn() + { - return columnDefs; ++ return columnDefs.get(0); ++ } ++ ++ @Override ++ public ColumnDefinition getLastColumn() ++ { ++ return columnDefs.get(columnDefs.size() - 1); + } + + @Override - public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException ++ public Collection<ColumnDefinition> getColumnDefs() + { - return Composites.toByteBuffers(valuesAsComposites(options)); ++ return columnDefs; + } + + @Override - public final PrimaryKeyRestrictions mergeWith(Restriction otherRestriction) throws InvalidRequestException ++ public final Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException + { + checkTrue(otherRestriction.isMultiColumn(), + "Mixing single column relations and multi column relations on clustering columns is not allowed"); - return doMergeWith((PrimaryKeyRestrictions) otherRestriction); ++ return doMergeWith(otherRestriction); + } + - protected abstract PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestriction) throws InvalidRequestException; ++ protected abstract Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException; + + /** + * Returns the names of the columns that are specified within this <code>Restrictions</code> and the other one + * as a comma separated <code>String</code>. + * + * @param otherRestrictions the other restrictions + * @return the names of the columns that are specified within this <code>Restrictions</code> and the other one + * as a comma separated <code>String</code>. + */ - protected final String getColumnsInCommons(Restrictions otherRestrictions) ++ protected final String getColumnsInCommons(Restriction otherRestriction) + { + Set<ColumnDefinition> commons = new HashSet<>(getColumnDefs()); - commons.retainAll(otherRestrictions.getColumnDefs()); ++ commons.retainAll(otherRestriction.getColumnDefs()); + StringBuilder builder = new StringBuilder(); + for (ColumnDefinition columnDefinition : commons) + { + if (builder.length() != 0) + builder.append(" ,"); + builder.append(columnDefinition.name); + } + return builder.toString(); + } + + @Override + public final boolean hasSupportingIndex(SecondaryIndexManager indexManager) + { + for (ColumnDefinition columnDef : columnDefs) + { + SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes); + if (index != null && isSupportedBy(index)) + return true; + } + return false; + } + - @Override - public final void addIndexExpressionTo(List<IndexExpression> expressions, - SecondaryIndexManager indexManager, - QueryOptions options) throws InvalidRequestException - { - for (ColumnDefinition columnDef : columnDefs) - { - SecondaryIndex index = indexManager.getIndexForColumn(columnDef.name.bytes); - if (index != null && isSupportedBy(index)) - expressions.add(getIndexExpression(columnDef, options)); - } - } - - /** - * Returns the <code>IndexExpression</code> for the specified column. - * - * @param columnDef the column definition - * @param options the query options - * @return the <code>IndexExpression</code> for the specified column - */ - protected IndexExpression getIndexExpression(ColumnDefinition columnDef, - QueryOptions options) throws InvalidRequestException - { - // Except for EQ this operation is not supported - throw new UnsupportedOperationException(); - } - + /** + * Check if this type of restriction is supported for by the specified index. + * @param index the Secondary index + * + * @return <code>true</code> this type of restriction is supported by the specified index, + * <code>false</code> otherwise. + */ + protected abstract boolean isSupportedBy(SecondaryIndex index); + + public static class EQ extends MultiColumnRestriction + { + protected final Term value; + - public EQ(CType ctype, List<ColumnDefinition> columnDefs, Term value) ++ public EQ(List<ColumnDefinition> columnDefs, Term value) + { - super(ctype, columnDefs); ++ super(columnDefs); + this.value = value; + } + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return usesFunction(value, ksName, functionName); + } + + @Override + public String toString() + { + return String.format("EQ(%s)", value); + } + + @Override - public PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestriction) throws InvalidRequestException ++ public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException + { + throw invalidRequest("%s cannot be restricted by more than one relation if it includes an Equal", + getColumnsInCommons(otherRestriction)); + } + + @Override - public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException - { - return Collections.singletonList(compositeValue(options)); - } - - @Override - public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException - { - Composite prefix = compositeValue(options); - return Collections.singletonList(ctype.size() > prefix.size() && bound.isEnd() - ? prefix.end() - : prefix); - } - - @Override + protected boolean isSupportedBy(SecondaryIndex index) + { + return index.supportsOperator(Operator.EQ); + } + - private Composite compositeValue(QueryOptions options) throws InvalidRequestException ++ @Override ++ public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options) + { - CBuilder builder = ctype.builder(); + Tuples.Value t = ((Tuples.Value) value.bind(options)); + List<ByteBuffer> values = t.getElements(); - for (int i = 0; i < values.size(); i++) ++ for (int i = 0, m = values.size(); i < m; i++) + { - ByteBuffer component = checkNotNull(values.get(i), - "Invalid null value in condition for column %s", - columnDefs.get(i).name); - builder.add(component); ++ builder.addElementToAll(values.get(i)); ++ checkFalse(builder.containsNull(), "Invalid null value for column %s", columnDefs.get(i).name); + } - - return builder.build(); ++ return builder; + } + + @Override - protected final IndexExpression getIndexExpression(ColumnDefinition columnDef, - QueryOptions options) throws InvalidRequestException ++ public final void addIndexExpressionTo(List<IndexExpression> expressions, ++ SecondaryIndexManager indexManager, ++ QueryOptions options) throws InvalidRequestException + { + Tuples.Value t = ((Tuples.Value) value.bind(options)); + List<ByteBuffer> values = t.getElements(); - ByteBuffer component = validateIndexedValue(columnDef, values.get(columnDef.position())); - return new IndexExpression(columnDef.name.bytes, Operator.EQ, component); - } - } + - public abstract static class IN extends MultiColumnRestriction - { - @Override - public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException - { - CBuilder builder = ctype.builder(); - List<List<ByteBuffer>> splitInValues = splitValues(options); - // The IN query might not have listed the values in comparator order, so we need to re-sort - // the bounds lists to make sure the slices works correctly (also, to avoid duplicates). - TreeSet<Composite> inValues = new TreeSet<>(ctype); - for (List<ByteBuffer> components : splitInValues) ++ for (int i = 0, m = columnDefs.size(); i < m; i++) + { - for (int i = 0; i < components.size(); i++) - checkNotNull(components.get(i), "Invalid null value in condition for column " + columnDefs.get(i).name); - - inValues.add(builder.buildWith(components)); ++ ColumnDefinition columnDef = columnDefs.get(i); ++ ByteBuffer component = validateIndexedValue(columnDef, values.get(i)); ++ expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component)); + } - return new ArrayList<>(inValues); + } ++ } + ++ public abstract static class IN extends MultiColumnRestriction ++ { ++ /** ++ * {@inheritDoc} ++ */ + @Override - public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException ++ public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options) + { - CBuilder builder = ctype.builder(); + List<List<ByteBuffer>> splitInValues = splitValues(options); - // The IN query might not have listed the values in comparator order, so we need to re-sort - // the bounds lists to make sure the slices works correctly (also, to avoid duplicates). - TreeSet<Composite> inValues = new TreeSet<>(ctype); - for (List<ByteBuffer> components : splitInValues) - { - for (int i = 0; i < components.size(); i++) - checkNotNull(components.get(i), "Invalid null value in condition for column %s", columnDefs.get(i).name); ++ builder.addAllElementsToAll(splitInValues); + - Composite prefix = builder.buildWith(components); - inValues.add(bound.isEnd() && builder.remainingCount() - components.size() > 0 - ? prefix.end() - : prefix); - } - return new ArrayList<>(inValues); ++ if (builder.containsNull()) ++ throw invalidRequest("Invalid null value in condition for columns: %s", ColumnDefinition.toIdentifiers(columnDefs)); ++ return builder; + } + - public IN(CType ctype, List<ColumnDefinition> columnDefs) ++ public IN(List<ColumnDefinition> columnDefs) + { - super(ctype, columnDefs); ++ super(columnDefs); + } + + @Override + public boolean isIN() + { + return true; + } + + @Override - public PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestrictions) throws InvalidRequestException ++ public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException + { + throw invalidRequest("%s cannot be restricted by more than one relation if it includes a IN", - getColumnsInCommons(otherRestrictions)); ++ getColumnsInCommons(otherRestriction)); + } + + @Override + protected boolean isSupportedBy(SecondaryIndex index) + { + return index.supportsOperator(Operator.IN); + } + ++ @Override ++ public final void addIndexExpressionTo(List<IndexExpression> expressions, ++ SecondaryIndexManager indexManager, ++ QueryOptions options) throws InvalidRequestException ++ { ++ List<List<ByteBuffer>> splitInValues = splitValues(options); ++ checkTrue(splitInValues.size() == 1, "IN restrictions are not supported on indexed columns"); ++ List<ByteBuffer> values = splitInValues.get(0); ++ ++ for (int i = 0, m = columnDefs.size(); i < m; i++) ++ { ++ ColumnDefinition columnDef = columnDefs.get(i); ++ ByteBuffer component = validateIndexedValue(columnDef, values.get(i)); ++ expressions.add(new IndexExpression(columnDef.name.bytes, Operator.EQ, component)); ++ } ++ } ++ + protected abstract List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException; + } + + /** + * An IN restriction that has a set of terms for in values. + * For example: "SELECT ... WHERE (a, b, c) IN ((1, 2, 3), (4, 5, 6))" or "WHERE (a, b, c) IN (?, ?)" + */ + public static class InWithValues extends MultiColumnRestriction.IN + { + protected final List<Term> values; + - public InWithValues(CType ctype, List<ColumnDefinition> columnDefs, List<Term> values) ++ public InWithValues(List<ColumnDefinition> columnDefs, List<Term> values) + { - super(ctype, columnDefs); ++ super(columnDefs); + this.values = values; + } + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return usesFunction(values, ksName, functionName); + } + + @Override + public String toString() + { + return String.format("IN(%s)", values); + } + + @Override + protected List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException + { + List<List<ByteBuffer>> buffers = new ArrayList<>(values.size()); + for (Term value : values) + { + Term.MultiItemTerminal term = (Term.MultiItemTerminal) value.bind(options); + buffers.add(term.getElements()); + } + return buffers; + } + } + + /** + * An IN restriction that uses a single marker for a set of IN values that are tuples. + * For example: "SELECT ... WHERE (a, b, c) IN ?" + */ + public static class InWithMarker extends MultiColumnRestriction.IN + { + protected final AbstractMarker marker; + - public InWithMarker(CType ctype, List<ColumnDefinition> columnDefs, AbstractMarker marker) ++ public InWithMarker(List<ColumnDefinition> columnDefs, AbstractMarker marker) + { - super(ctype, columnDefs); ++ super(columnDefs); + this.marker = marker; + } + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return false; + } + + @Override + public String toString() + { + return "IN ?"; + } + + @Override + protected List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException + { + Tuples.InMarker inMarker = (Tuples.InMarker) marker; + Tuples.InValue inValue = inMarker.bind(options); + checkNotNull(inValue, "Invalid null value for IN restriction"); + return inValue.getSplitValues(); + } + } + + public static class Slice extends MultiColumnRestriction + { + private final TermSlice slice; + - public Slice(CType ctype, List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term) ++ public Slice(List<ColumnDefinition> columnDefs, Bound bound, boolean inclusive, Term term) + { - this(ctype, columnDefs, TermSlice.newInstance(bound, inclusive, term)); ++ this(columnDefs, TermSlice.newInstance(bound, inclusive, term)); + } + - private Slice(CType ctype, List<ColumnDefinition> columnDefs, TermSlice slice) ++ private Slice(List<ColumnDefinition> columnDefs, TermSlice slice) + { - super(ctype, columnDefs); ++ super(columnDefs); + this.slice = slice; + } + + @Override + public boolean isSlice() + { + return true; + } + + @Override - public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException ++ public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options) + { + throw new UnsupportedOperationException(); + } + + @Override - public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException ++ public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options) + { - return Composites.toByteBuffers(boundsAsComposites(b, options)); - } ++ List<ByteBuffer> vals = componentBounds(bound, options); + - @Override - public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException - { - CBuilder builder = ctype.builder(); - Iterator<ColumnDefinition> iter = columnDefs.iterator(); - ColumnDefinition firstName = iter.next(); - // A hack to preserve pre-6875 behavior for tuple-notation slices where the comparator mixes ASCENDING - // and DESCENDING orders. This stores the bound for the first component; we will re-use it for all following - // components, even if they don't match the first component's reversal/non-reversal. Note that this does *not* - // guarantee correct query results, it just preserves the previous behavior. - Bound firstComponentBound = !firstName.isReversedType() ? bound : bound.reverse(); - - if (!hasBound(firstComponentBound)) - { - Composite prefix = builder.build(); - return Collections.singletonList(builder.remainingCount() > 0 && bound.isEnd() - ? prefix.end() - : prefix); - } - - List<ByteBuffer> vals = componentBounds(firstComponentBound, options); - - ByteBuffer v = checkNotNull(vals.get(firstName.position()), "Invalid null value in condition for column %s", firstName.name); - builder.add(v); - - while (iter.hasNext()) ++ for (int i = 0, m = vals.size(); i < m; i++) + { - ColumnDefinition def = iter.next(); - if (def.position() >= vals.size()) - break; - - v = checkNotNull(vals.get(def.position()), "Invalid null value in condition for column %s", def.name); - builder.add(v); ++ ByteBuffer v = checkNotNull(vals.get(i), "Invalid null value in condition for column %s", columnDefs.get(i).name); ++ builder.addElementToAll(v); + } - Composite.EOC eoc = eocFor(this, bound, firstComponentBound); - return Collections.singletonList(builder.build().withEOC(eoc)); ++ return builder; + } + + @Override + protected boolean isSupportedBy(SecondaryIndex index) + { + return slice.isSupportedBy(index); + } + - private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound) - { - if (eocBound.isStart()) - return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END; - - return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START; - } - + @Override + public boolean hasBound(Bound b) + { + return slice.hasBound(b); + } + + @Override + public boolean usesFunction(String ksName, String functionName) + { + return (slice.hasBound(Bound.START) && usesFunction(slice.bound(Bound.START), ksName, functionName)) + || (slice.hasBound(Bound.END) && usesFunction(slice.bound(Bound.END), ksName, functionName)); + } + + @Override + public boolean isInclusive(Bound b) + { + return slice.isInclusive(b); + } + + @Override - public PrimaryKeyRestrictions doMergeWith(PrimaryKeyRestrictions otherRestriction) throws InvalidRequestException ++ public Restriction doMergeWith(Restriction otherRestriction) throws InvalidRequestException + { + checkTrue(otherRestriction.isSlice(), + "Column \"%s\" cannot be restricted by both an equality and an inequality relation", + getColumnsInCommons(otherRestriction)); + + Slice otherSlice = (Slice) otherRestriction; + ++ if (!getFirstColumn().equals(otherRestriction.getFirstColumn())) ++ { ++ ColumnDefinition column = getFirstColumn().position() > otherRestriction.getFirstColumn().position() ++ ? getFirstColumn() : otherRestriction.getFirstColumn(); ++ ++ throw invalidRequest("Column \"%s\" cannot be restricted by two tuple-notation inequalities not starting with the same column", ++ column.name); ++ } ++ + checkFalse(hasBound(Bound.START) && otherSlice.hasBound(Bound.START), + "More than one restriction was found for the start bound on %s", + getColumnsInCommons(otherRestriction)); + checkFalse(hasBound(Bound.END) && otherSlice.hasBound(Bound.END), + "More than one restriction was found for the end bound on %s", + getColumnsInCommons(otherRestriction)); + - List<ColumnDefinition> newColumnDefs = size() >= otherSlice.size() ? columnDefs : otherSlice.columnDefs; - return new Slice(ctype, newColumnDefs, slice.merge(otherSlice.slice)); ++ List<ColumnDefinition> newColumnDefs = columnDefs.size() >= otherSlice.columnDefs.size() ? columnDefs : otherSlice.columnDefs; ++ return new Slice(newColumnDefs, slice.merge(otherSlice.slice)); ++ } ++ ++ @Override ++ public final void addIndexExpressionTo(List<IndexExpression> expressions, ++ SecondaryIndexManager indexManager, ++ QueryOptions options) throws InvalidRequestException ++ { ++ throw invalidRequest("Slice restrictions are not supported on indexed columns"); + } + + @Override + public String toString() + { + return "SLICE" + slice; + } + + /** + * Similar to bounds(), but returns one ByteBuffer per-component in the bound instead of a single + * ByteBuffer to represent the entire bound. + * @param b the bound type + * @param options the query options + * @return one ByteBuffer per-component in the bound + * @throws InvalidRequestException if the components cannot be retrieved + */ + private List<ByteBuffer> componentBounds(Bound b, QueryOptions options) throws InvalidRequestException + { + Tuples.Value value = (Tuples.Value) slice.bound(b).bind(options); + return value.getElements(); + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java index 0000000,0000000..194f4d5 new file mode 100644 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictionSet.java @@@ -1,0 -1,0 +1,325 @@@ ++/* ++ * 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.restrictions; ++ ++import java.nio.ByteBuffer; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++ ++import org.apache.cassandra.config.ColumnDefinition; ++import org.apache.cassandra.cql3.QueryOptions; ++import org.apache.cassandra.cql3.statements.Bound; ++import org.apache.cassandra.db.IndexExpression; ++import org.apache.cassandra.db.composites.*; ++import org.apache.cassandra.db.composites.Composite.EOC; ++import org.apache.cassandra.db.index.SecondaryIndexManager; ++import org.apache.cassandra.exceptions.InvalidRequestException; ++ ++import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; ++import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; ++ ++/** ++ * A set of single column restrictions on a primary key part (partition key or clustering key). ++ */ ++final class PrimaryKeyRestrictionSet extends AbstractPrimaryKeyRestrictions ++{ ++ /** ++ * The restrictions. ++ */ ++ private final RestrictionSet restrictions; ++ ++ /** ++ * <code>true</code> if the restrictions are corresponding to an EQ, <code>false</code> otherwise. ++ */ ++ private boolean eq; ++ ++ /** ++ * <code>true</code> if the restrictions are corresponding to an IN, <code>false</code> otherwise. ++ */ ++ private boolean in; ++ ++ /** ++ * <code>true</code> if the restrictions are corresponding to a Slice, <code>false</code> otherwise. ++ */ ++ private boolean slice; ++ ++ /** ++ * <code>true</code> if the restrictions are corresponding to a Contains, <code>false</code> otherwise. ++ */ ++ private boolean contains; ++ ++ public PrimaryKeyRestrictionSet(CType ctype) ++ { ++ super(ctype); ++ this.restrictions = new RestrictionSet(); ++ this.eq = true; ++ } ++ ++ private PrimaryKeyRestrictionSet(PrimaryKeyRestrictionSet primaryKeyRestrictions, ++ Restriction restriction) throws InvalidRequestException ++ { ++ super(primaryKeyRestrictions.ctype); ++ this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction); ++ ++ if (!primaryKeyRestrictions.isEmpty()) ++ { ++ ColumnDefinition lastRestrictionStart = primaryKeyRestrictions.restrictions.lastRestriction().getFirstColumn(); ++ ColumnDefinition newRestrictionStart = restriction.getFirstColumn(); ++ ++ checkFalse(primaryKeyRestrictions.isSlice() && newRestrictionStart.position() > lastRestrictionStart.position(), ++ "Clustering column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", ++ newRestrictionStart.name, ++ lastRestrictionStart.name); ++ ++ if (newRestrictionStart.position() < lastRestrictionStart.position() && restriction.isSlice()) ++ throw invalidRequest("PRIMARY KEY column \"%s\" cannot be restricted (preceding column \"%s\" is restricted by a non-EQ relation)", ++ restrictions.nextColumn(newRestrictionStart).name, ++ newRestrictionStart.name); ++ } ++ ++ if (restriction.isSlice() || primaryKeyRestrictions.isSlice()) ++ this.slice = true; ++ else if (restriction.isContains() || primaryKeyRestrictions.isContains()) ++ this.contains = true; ++ else if (restriction.isIN()) ++ this.in = true; ++ else ++ this.eq = true; ++ } ++ ++ @Override ++ public boolean isSlice() ++ { ++ return slice; ++ } ++ ++ @Override ++ public boolean isEQ() ++ { ++ return eq; ++ } ++ ++ @Override ++ public boolean isIN() ++ { ++ return in; ++ } ++ ++ @Override ++ public boolean isOnToken() ++ { ++ return false; ++ } ++ ++ @Override ++ public boolean isContains() ++ { ++ return contains; ++ } ++ ++ @Override ++ public boolean isMultiColumn() ++ { ++ return false; ++ } ++ ++ @Override ++ public boolean usesFunction(String ksName, String functionName) ++ { ++ return restrictions.usesFunction(ksName, functionName); ++ } ++ ++ @Override ++ public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException ++ { ++ if (restriction.isOnToken()) ++ { ++ if (isEmpty()) ++ return (PrimaryKeyRestrictions) restriction; ++ ++ return new TokenFilter(this, (TokenRestriction) restriction); ++ } ++ ++ return new PrimaryKeyRestrictionSet(this, restriction); ++ } ++ ++ @Override ++ public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException ++ { ++ return appendTo(new CompositesBuilder(ctype), options).build(); ++ } ++ ++ @Override ++ public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options) ++ { ++ for (Restriction r : restrictions) ++ { ++ r.appendTo(builder, options); ++ if (builder.hasMissingElements()) ++ break; ++ } ++ return builder; ++ } ++ ++ @Override ++ public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options) ++ { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException ++ { ++ CompositesBuilder builder = new CompositesBuilder(ctype); ++ // The end-of-component of composite doesn't depend on whether the ++ // component type is reversed or not (i.e. the ReversedType is applied ++ // to the component comparator but not to the end-of-component itself), ++ // it only depends on whether the slice is reversed ++ int keyPosition = 0; ++ for (Restriction r : restrictions) ++ { ++ ColumnDefinition def = r.getFirstColumn(); ++ ++ // In a restriction, we always have Bound.START < Bound.END for the "base" comparator. ++ // So if we're doing a reverse slice, we must inverse the bounds when giving them as start and end of the slice filter. ++ // But if the actual comparator itself is reversed, we must inversed the bounds too. ++ Bound b = !def.isReversedType() ? bound : bound.reverse(); ++ if (keyPosition != def.position() || r.isContains()) ++ break; ++ ++ if (r.isSlice()) ++ { ++ if (!r.hasBound(b)) ++ { ++ // There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix. ++ // For composites, if there was preceding component and we're computing the end, we must change the last component ++ // End-Of-Component, otherwise we would be selecting only one record. ++ return builder.buildWithEOC(bound.isEnd() ? EOC.END : EOC.START); ++ } ++ ++ r.appendBoundTo(builder, b, options); ++ Composite.EOC eoc = eocFor(r, bound, b); ++ return builder.buildWithEOC(eoc); ++ } ++ ++ r.appendBoundTo(builder, b, options); ++ ++ if (builder.hasMissingElements()) ++ return Collections.emptyList(); ++ ++ keyPosition = r.getLastColumn().position() + 1; ++ } ++ // Means no relation at all or everything was an equal ++ // Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection, ++ // it would be harmless to do it. However, we use this method got the partition key too. And when a query ++ // with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that ++ // case using the eoc would be bad, since for the random partitioner we have no guarantee that ++ // prefix.end() will sort after prefix (see #5240). ++ EOC eoc = !builder.hasRemaining() ? EOC.NONE : (bound.isEnd() ? EOC.END : EOC.START); ++ return builder.buildWithEOC(eoc); ++ } ++ ++ @Override ++ public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException ++ { ++ return Composites.toByteBuffers(valuesAsComposites(options)); ++ } ++ ++ @Override ++ public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException ++ { ++ return Composites.toByteBuffers(boundsAsComposites(b, options)); ++ } ++ ++ private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound) ++ { ++ if (eocBound.isStart()) ++ return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END; ++ ++ return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START; ++ } ++ ++ @Override ++ public boolean hasBound(Bound b) ++ { ++ if (isEmpty()) ++ return false; ++ return restrictions.lastRestriction().hasBound(b); ++ } ++ ++ @Override ++ public boolean isInclusive(Bound b) ++ { ++ if (isEmpty()) ++ return false; ++ return restrictions.lastRestriction().isInclusive(b); ++ } ++ ++ @Override ++ public boolean hasSupportingIndex(SecondaryIndexManager indexManager) ++ { ++ return restrictions.hasSupportingIndex(indexManager); ++ } ++ ++ @Override ++ public void addIndexExpressionTo(List<IndexExpression> expressions, ++ SecondaryIndexManager indexManager, ++ QueryOptions options) throws InvalidRequestException ++ { ++ Boolean clusteringColumns = null; ++ int position = 0; ++ ++ for (Restriction restriction : restrictions) ++ { ++ ColumnDefinition columnDef = restriction.getFirstColumn(); ++ ++ // PrimaryKeyRestrictionSet contains only one kind of column, either partition key or clustering columns. ++ // Therefore we only need to check the column kind once. All the other columns will be of the same kind. ++ if (clusteringColumns == null) ++ clusteringColumns = columnDef.isClusteringColumn() ? Boolean.TRUE : Boolean.FALSE; ++ ++ // We ignore all the clustering columns that can be handled by slices. ++ if (clusteringColumns && !restriction.isContains()&& position == columnDef.position()) ++ { ++ position = restriction.getLastColumn().position() + 1; ++ if (!restriction.hasSupportingIndex(indexManager)) ++ continue; ++ } ++ restriction.addIndexExpressionTo(expressions, indexManager, options); ++ } ++ } ++ ++ @Override ++ public Collection<ColumnDefinition> getColumnDefs() ++ { ++ return restrictions.getColumnDefs(); ++ } ++ ++ @Override ++ public ColumnDefinition getFirstColumn() ++ { ++ return restrictions.firstColumn(); ++ } ++ ++ @Override ++ public ColumnDefinition getLastColumn() ++ { ++ return restrictions.lastColumn(); ++ } ++} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java index 5f977b7,0000000..7d7b492 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/PrimaryKeyRestrictions.java @@@ -1,40 -1,0 +1,44 @@@ +/* + * 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.restrictions; + ++import java.nio.ByteBuffer; +import java.util.List; + +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.composites.Composite; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * A set of restrictions on a primary key part (partition key or clustering key). + * + */ +interface PrimaryKeyRestrictions extends Restriction, Restrictions +{ - + @Override + public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException; + ++ public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException; ++ + public List<Composite> valuesAsComposites(QueryOptions options) throws InvalidRequestException; + ++ public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException; ++ + public List<Composite> boundsAsComposites(Bound bound, QueryOptions options) throws InvalidRequestException; +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/Restriction.java index f6d0c73,0000000..49af20c mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/Restriction.java @@@ -1,99 -1,0 +1,140 @@@ +/* + * 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.restrictions; + - import java.nio.ByteBuffer; ++import java.util.Collection; +import java.util.List; + ++import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.statements.Bound; +import org.apache.cassandra.db.IndexExpression; ++import org.apache.cassandra.db.composites.CompositesBuilder; +import org.apache.cassandra.db.index.SecondaryIndexManager; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * A restriction/clause on a column. + * The goal of this class being to group all conditions for a column in a SELECT. ++ * ++ * <p>Implementation of this class must be immutable. See {@link #mergeWith(Restriction)} for more explanation.</p> + */ +public interface Restriction +{ + public boolean isOnToken(); + public boolean isSlice(); + public boolean isEQ(); + public boolean isIN(); + public boolean isContains(); + public boolean isMultiColumn(); + - public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException; ++ /** ++ * Returns the definition of the first column. ++ * @return the definition of the first column. ++ */ ++ public ColumnDefinition getFirstColumn(); ++ ++ /** ++ * Returns the definition of the last column. ++ * @return the definition of the last column. ++ */ ++ public ColumnDefinition getLastColumn(); ++ ++ /** ++ * Returns the column definitions in position order. ++ * @return the column definitions in position order. ++ */ ++ public Collection<ColumnDefinition> getColumnDefs(); + + /** + * Returns <code>true</code> if one of the restrictions use the specified function. + * + * @param ksName the keyspace name + * @param functionName the function name + * @return <code>true</code> if one of the restrictions use the specified function, <code>false</code> otherwise. + */ + public boolean usesFunction(String ksName, String functionName); + + /** + * Checks if the specified bound is set or not. + * @param b the bound type + * @return <code>true</code> if the specified bound is set, <code>false</code> otherwise + */ + public boolean hasBound(Bound b); + - public List<ByteBuffer> bounds(Bound b, QueryOptions options) throws InvalidRequestException; - + /** + * Checks if the specified bound is inclusive or not. + * @param b the bound type + * @return <code>true</code> if the specified bound is inclusive, <code>false</code> otherwise + */ + public boolean isInclusive(Bound b); + + /** + * Merges this restriction with the specified one. + * ++ * <p>Restriction are immutable. Therefore merging two restrictions result in a new one. ++ * The reason behind this choice is that it allow a great flexibility in the way the merging can done while ++ * preventing any side effect.</p> ++ * + * @param otherRestriction the restriction to merge into this one + * @return the restriction resulting of the merge + * @throws InvalidRequestException if the restrictions cannot be merged + */ + public Restriction mergeWith(Restriction otherRestriction) throws InvalidRequestException; + + /** + * Check if the restriction is on indexed columns. + * + * @param indexManager the index manager + * @return <code>true</code> if the restriction is on indexed columns, <code>false</code> + */ + public boolean hasSupportingIndex(SecondaryIndexManager indexManager); + + /** + * Adds to the specified list the <code>IndexExpression</code>s corresponding to this <code>Restriction</code>. + * + * @param expressions the list to add the <code>IndexExpression</code>s to + * @param indexManager the secondary index manager + * @param options the query options + * @throws InvalidRequestException if this <code>Restriction</code> cannot be converted into + * <code>IndexExpression</code>s + */ + public void addIndexExpressionTo(List<IndexExpression> expressions, + SecondaryIndexManager indexManager, + QueryOptions options) + throws InvalidRequestException; ++ ++ /** ++ * Appends the values of this <code>Restriction</code> to the specified builder. ++ * ++ * @param builder the <code>CompositesBuilder</code> to append to. ++ * @param options the query options ++ * @return the <code>CompositesBuilder</code> ++ */ ++ public CompositesBuilder appendTo(CompositesBuilder builder, QueryOptions options); ++ ++ /** ++ * Appends the values of the <code>Restriction</code> for the specified bound to the specified builder. ++ * ++ * @param builder the <code>CompositesBuilder</code> to append to. ++ * @param bound the bound ++ * @param options the query options ++ * @return the <code>CompositesBuilder</code> ++ */ ++ public CompositesBuilder appendBoundTo(CompositesBuilder builder, Bound bound, QueryOptions options); +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java index 0000000,0000000..b422749 new file mode 100644 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/restrictions/RestrictionSet.java @@@ -1,0 -1,0 +1,252 @@@ ++/* ++ * 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.restrictions; ++ ++import java.util.*; ++ ++import org.apache.cassandra.config.ColumnDefinition; ++import org.apache.cassandra.cql3.QueryOptions; ++import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction.Contains; ++import org.apache.cassandra.db.IndexExpression; ++import org.apache.cassandra.db.index.SecondaryIndexManager; ++import org.apache.cassandra.exceptions.InvalidRequestException; ++ ++/** ++ * Sets of column restrictions. ++ * ++ * <p>This class is immutable in order to be use within {@link PrimaryKeyRestrictionSet} which as ++ * an implementation of {@link Restriction} need to be immutable. ++ */ ++final class RestrictionSet implements Restrictions, Iterable<Restriction> ++{ ++ /** ++ * The comparator used to sort the <code>Restriction</code>s. ++ */ ++ private static final Comparator<ColumnDefinition> COLUMN_DEFINITION_COMPARATOR = new Comparator<ColumnDefinition>() ++ { ++ @Override ++ public int compare(ColumnDefinition column, ColumnDefinition otherColumn) ++ { ++ int value = Integer.compare(column.position(), otherColumn.position()); ++ return value != 0 ? value : column.name.bytes.compareTo(otherColumn.name.bytes); ++ } ++ }; ++ ++ /** ++ * The restrictions per column. ++ */ ++ protected final TreeMap<ColumnDefinition, Restriction> restrictions; ++ ++ public RestrictionSet() ++ { ++ this(new TreeMap<ColumnDefinition, Restriction>(COLUMN_DEFINITION_COMPARATOR)); ++ } ++ ++ private RestrictionSet(TreeMap<ColumnDefinition, Restriction> restrictions) ++ { ++ this.restrictions = restrictions; ++ } ++ ++ @Override ++ public final void addIndexExpressionTo(List<IndexExpression> expressions, ++ SecondaryIndexManager indexManager, ++ QueryOptions options) throws InvalidRequestException ++ { ++ for (Restriction restriction : restrictions.values()) ++ restriction.addIndexExpressionTo(expressions, indexManager, options); ++ } ++ ++ @Override ++ public final Set<ColumnDefinition> getColumnDefs() ++ { ++ return restrictions.keySet(); ++ } ++ ++ @Override ++ public boolean usesFunction(String ksName, String functionName) ++ { ++ for (Restriction restriction : restrictions.values()) ++ if (restriction.usesFunction(ksName, functionName)) ++ return true; ++ ++ return false; ++ } ++ ++ @Override ++ public final boolean isEmpty() ++ { ++ return getColumnDefs().isEmpty(); ++ } ++ ++ @Override ++ public final int size() ++ { ++ return getColumnDefs().size(); ++ } ++ ++ /** ++ * Adds the specified restriction to this set of restrictions. ++ * ++ * @param restriction the restriction to add ++ * @return the new set of restrictions ++ * @throws InvalidRequestException if the new restriction cannot be added ++ */ ++ public RestrictionSet addRestriction(Restriction restriction) throws InvalidRequestException ++ { ++ // RestrictionSet is immutable so we need to clone the restrictions map. ++ TreeMap<ColumnDefinition, Restriction> newRestrictions = new TreeMap<>(this.restrictions); ++ return new RestrictionSet(mergeRestrictions(newRestrictions, restriction)); ++ } ++ ++ private TreeMap<ColumnDefinition, Restriction> mergeRestrictions(TreeMap<ColumnDefinition, Restriction> restrictions, ++ Restriction restriction) ++ throws InvalidRequestException ++ { ++ Collection<ColumnDefinition> columnDefs = restriction.getColumnDefs(); ++ Set<Restriction> existingRestrictions = getRestrictions(columnDefs); ++ ++ if (existingRestrictions.isEmpty()) ++ { ++ for (ColumnDefinition columnDef : columnDefs) ++ restrictions.put(columnDef, restriction); ++ } ++ else ++ { ++ for (Restriction existing : existingRestrictions) ++ { ++ Restriction newRestriction = mergeRestrictions(existing, restriction); ++ ++ for (ColumnDefinition columnDef : columnDefs) ++ restrictions.put(columnDef, newRestriction); ++ } ++ } ++ ++ return restrictions; ++ } ++ ++ /** ++ * Returns all the restrictions applied to the specified columns. ++ * ++ * @param columnDefs the column definitions ++ * @return all the restrictions applied to the specified columns ++ */ ++ private Set<Restriction> getRestrictions(Collection<ColumnDefinition> columnDefs) ++ { ++ Set<Restriction> set = new HashSet<>(); ++ for (ColumnDefinition columnDef : columnDefs) ++ { ++ Restriction existing = restrictions.get(columnDef); ++ if (existing != null) ++ set.add(existing); ++ } ++ return set; ++ } ++ ++ @Override ++ public final boolean hasSupportingIndex(SecondaryIndexManager indexManager) ++ { ++ for (Restriction restriction : restrictions.values()) ++ { ++ if (restriction.hasSupportingIndex(indexManager)) ++ return true; ++ } ++ return false; ++ } ++ ++ /** ++ * Returns the column after the specified one. ++ * ++ * @param columnDef the column for which the next one need to be found ++ * @return the column after the specified one. ++ */ ++ ColumnDefinition nextColumn(ColumnDefinition columnDef) ++ { ++ return restrictions.tailMap(columnDef, false).firstKey(); ++ } ++ ++ /** ++ * Returns the definition of the first column. ++ * ++ * @return the definition of the first column. ++ */ ++ ColumnDefinition firstColumn() ++ { ++ return isEmpty() ? null : this.restrictions.firstKey(); ++ } ++ ++ /** ++ * Returns the definition of the last column. ++ * ++ * @return the definition of the last column. ++ */ ++ ColumnDefinition lastColumn() ++ { ++ return isEmpty() ? null : this.restrictions.lastKey(); ++ } ++ ++ /** ++ * Returns the last restriction. ++ * ++ * @return the last restriction. ++ */ ++ Restriction lastRestriction() ++ { ++ return isEmpty() ? null : this.restrictions.lastEntry().getValue(); ++ } ++ ++ /** ++ * Merges the two specified restrictions. ++ * ++ * @param restriction the first restriction ++ * @param otherRestriction the second restriction ++ * @return the merged restriction ++ * @throws InvalidRequestException if the two restrictions cannot be merged ++ */ ++ private static Restriction mergeRestrictions(Restriction restriction, ++ Restriction otherRestriction) throws InvalidRequestException ++ { ++ return restriction == null ? otherRestriction ++ : restriction.mergeWith(otherRestriction); ++ } ++ ++ /** ++ * Checks if the restrictions contains multiple contains, contains key, or map[key] = value. ++ * ++ * @return <code>true</code> if the restrictions contains multiple contains, contains key, or , ++ * map[key] = value; <code>false</code> otherwise ++ */ ++ public final boolean hasMultipleContains() ++ { ++ int numberOfContains = 0; ++ for (Restriction restriction : restrictions.values()) ++ { ++ if (restriction.isContains()) ++ { ++ Contains contains = (Contains) restriction; ++ numberOfContains += (contains.numberOfValues() + contains.numberOfKeys() + contains.numberOfEntries()); ++ } ++ } ++ return numberOfContains > 1; ++ } ++ ++ @Override ++ public Iterator<Restriction> iterator() ++ { ++ return new LinkedHashSet<>(restrictions.values()).iterator(); ++ } ++} http://git-wip-us.apache.org/repos/asf/cassandra/blob/71778eec/src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java index 3cfe4ab,0000000..e2b31dd mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/Restrictions.java @@@ -1,84 -1,0 +1,84 @@@ +/* + * 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.restrictions; + +import java.util.Collection; +import java.util.List; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.db.IndexExpression; +import org.apache.cassandra.db.index.SecondaryIndexManager; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * Sets of restrictions + */ +interface Restrictions +{ + /** + * Returns the column definitions in position order. + * @return the column definitions in position order. + */ + public Collection<ColumnDefinition> getColumnDefs(); + + /** + * Returns <code>true</code> if one of the restrictions use the specified function. + * + * @param ksName the keyspace name + * @param functionName the function name + * @return <code>true</code> if one of the restrictions use the specified function, <code>false</code> otherwise. + */ + public boolean usesFunction(String ksName, String functionName); + + /** + * Check if the restriction is on indexed columns. + * + * @param indexManager the index manager + * @return <code>true</code> if the restriction is on indexed columns, <code>false</code> + */ + public boolean hasSupportingIndex(SecondaryIndexManager indexManager); + + /** + * Adds to the specified list the <code>IndexExpression</code>s corresponding to this <code>Restriction</code>. + * + * @param expressions the list to add the <code>IndexExpression</code>s to + * @param indexManager the secondary index manager + * @param options the query options + * @throws InvalidRequestException if this <code>Restriction</code> cannot be converted into + * <code>IndexExpression</code>s + */ + public void addIndexExpressionTo(List<IndexExpression> expressions, + SecondaryIndexManager indexManager, + QueryOptions options) + throws InvalidRequestException; + + /** - * Checks if this <code>SingleColumnPrimaryKeyRestrictions</code> is empty or not. ++ * Checks if this <code>PrimaryKeyRestrictionSet</code> is empty or not. + * - * @return <code>true</code> if this <code>SingleColumnPrimaryKeyRestrictions</code> is empty, <code>false</code> otherwise. ++ * @return <code>true</code> if this <code>PrimaryKeyRestrictionSet</code> is empty, <code>false</code> otherwise. + */ + boolean isEmpty(); + + /** + * Returns the number of columns that have a restriction. + * + * @return the number of columns that have a restriction. + */ + public int size(); +}
