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();
 +}

Reply via email to