You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2014/05/22 21:20:24 UTC

[4/6] Merge branch 'cassandra-2.0' into cassandra-2.1

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
index 0000000,2e63272..bc77357
mode 000000,100644..100644
--- a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java
@@@ -1,0 -1,300 +1,413 @@@
+ /*
+  * 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.statements;
+ 
+ import org.apache.cassandra.cql3.*;
++import org.apache.cassandra.db.IndexExpression;
+ import org.apache.cassandra.exceptions.InvalidRequestException;
 -import org.apache.cassandra.thrift.IndexOperator;
+ 
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
+ import java.util.Collections;
+ import java.util.List;
+ 
+ public abstract class SingleColumnRestriction implements Restriction
+ {
+     public boolean isMultiColumn()
+     {
+         return false;
+     }
+ 
+     public static class EQ extends SingleColumnRestriction implements Restriction.EQ
+     {
+         protected final Term value;
+         private final boolean onToken;
+ 
+         public EQ(Term value, boolean onToken)
+         {
+             this.value = value;
+             this.onToken = onToken;
+         }
+ 
 -        public List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException
++        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
+         {
 -            return Collections.singletonList(value.bindAndGet(variables));
++            return Collections.singletonList(value.bindAndGet(options));
+         }
+ 
+         public boolean isSlice()
+         {
+             return false;
+         }
+ 
+         public boolean isEQ()
+         {
+             return true;
+         }
+ 
+         public boolean isIN()
+         {
+             return false;
+         }
+ 
++        public boolean isContains()
++        {
++            return false;
++        }
++
+         public boolean isOnToken()
+         {
+             return onToken;
+         }
+ 
+         @Override
+         public String toString()
+         {
+             return String.format("EQ(%s)%s", value, onToken ? "*" : "");
+         }
+     }
+ 
+     public static class InWithValues extends SingleColumnRestriction implements Restriction.IN
+     {
+         protected final List<Term> values;
+ 
+         public InWithValues(List<Term> values)
+         {
+             this.values = values;
+         }
+ 
 -        public List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException
++        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
+         {
+             List<ByteBuffer> buffers = new ArrayList<>(values.size());
+             for (Term value : values)
 -                buffers.add(value.bindAndGet(variables));
++                buffers.add(value.bindAndGet(options));
+             return buffers;
+         }
+ 
+         public boolean canHaveOnlyOneValue()
+         {
+             return values.size() == 1;
+         }
+ 
+         public boolean isSlice()
+         {
+             return false;
+         }
+ 
+         public boolean isEQ()
+         {
+             return false;
+         }
+ 
+         public boolean isIN()
+         {
+             return true;
+         }
+ 
++        public boolean isContains()
++        {
++            return false;
++        }
++
+         public boolean isOnToken()
+         {
+             return false;
+         }
+ 
+         @Override
+         public String toString()
+         {
+             return String.format("IN(%s)", values);
+         }
+     }
+ 
+     public static class InWithMarker extends SingleColumnRestriction implements Restriction.IN
+     {
+         protected final AbstractMarker marker;
+ 
+         public InWithMarker(AbstractMarker marker)
+         {
+             this.marker = marker;
+         }
+ 
 -        public List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException
++        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
+         {
 -            Term.MultiItemTerminal lval = (Term.MultiItemTerminal)marker.bind(variables);
++            Term.MultiItemTerminal lval = (Term.MultiItemTerminal)marker.bind(options);
+             if (lval == null)
+                 throw new InvalidRequestException("Invalid null value for IN restriction");
+             return lval.getElements();
+         }
+ 
+         public boolean canHaveOnlyOneValue()
+         {
+             return false;
+         }
+ 
+         public boolean isSlice()
+         {
+             return false;
+         }
+ 
+         public boolean isEQ()
+         {
+             return false;
+         }
+ 
+         public boolean isIN()
+         {
+             return true;
+         }
+ 
++        public boolean isContains()
++        {
++            return false;
++        }
++
+         public boolean isOnToken()
+         {
+             return false;
+         }
+ 
+         @Override
+         public String toString()
+         {
+             return "IN ?";
+         }
+     }
+ 
+     public static class Slice extends SingleColumnRestriction implements Restriction.Slice
+     {
+         protected final Term[] bounds;
+         protected final boolean[] boundInclusive;
+         protected final boolean onToken;
+ 
+         public Slice(boolean onToken)
+         {
+             this.bounds = new Term[2];
+             this.boundInclusive = new boolean[2];
+             this.onToken = onToken;
+         }
+ 
+         public boolean isSlice()
+         {
+             return true;
+         }
+ 
+         public boolean isEQ()
+         {
+             return false;
+         }
+ 
+         public boolean isIN()
+         {
+             return false;
+         }
+ 
 -        public List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException
++        public boolean isContains()
++        {
++            return false;
++        }
++
++        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
+         {
+             throw new UnsupportedOperationException();
+         }
+ 
+         public boolean isOnToken()
+         {
+             return onToken;
+         }
+ 
+         /** Returns true if the start or end bound (depending on the argument) is set, false otherwise */
+         public boolean hasBound(Bound b)
+         {
+             return bounds[b.idx] != null;
+         }
+ 
 -        public ByteBuffer bound(Bound b, List<ByteBuffer> variables) throws InvalidRequestException
++        public ByteBuffer bound(Bound b, QueryOptions options) throws InvalidRequestException
+         {
 -            return bounds[b.idx].bindAndGet(variables);
++            return bounds[b.idx].bindAndGet(options);
+         }
+ 
+         /** Returns true if the start or end bound (depending on the argument) is inclusive, false otherwise */
+         public boolean isInclusive(Bound b)
+         {
+             return bounds[b.idx] == null || boundInclusive[b.idx];
+         }
+ 
+         public Relation.Type getRelation(Bound eocBound, Bound inclusiveBound)
+         {
+             switch (eocBound)
+             {
+                 case START:
+                     return boundInclusive[inclusiveBound.idx] ? Relation.Type.GTE : Relation.Type.GT;
+                 case END:
+                     return boundInclusive[inclusiveBound.idx] ? Relation.Type.LTE : Relation.Type.LT;
+             }
+             throw new AssertionError();
+         }
+ 
 -        public IndexOperator getIndexOperator(Bound b)
++        public IndexExpression.Operator getIndexOperator(Bound b)
+         {
+             switch (b)
+             {
+                 case START:
 -                    return boundInclusive[b.idx] ? IndexOperator.GTE : IndexOperator.GT;
++                    return boundInclusive[b.idx] ? IndexExpression.Operator.GTE : IndexExpression.Operator.GT;
+                 case END:
 -                    return boundInclusive[b.idx] ? IndexOperator.LTE : IndexOperator.LT;
++                    return boundInclusive[b.idx] ? IndexExpression.Operator.LTE : IndexExpression.Operator.LT;
+             }
+             throw new AssertionError();
+         }
+ 
 -        public void setBound(Relation.Type type, Term t) throws InvalidRequestException
++        public void setBound(ColumnIdentifier name, Relation.Type type, Term t) throws InvalidRequestException
+         {
+             Bound b;
+             boolean inclusive;
+             switch (type)
+             {
+                 case GT:
+                     b = Bound.START;
+                     inclusive = false;
+                     break;
+                 case GTE:
+                     b = Bound.START;
+                     inclusive = true;
+                     break;
+                 case LT:
+                     b = Bound.END;
+                     inclusive = false;
+                     break;
+                 case LTE:
+                     b = Bound.END;
+                     inclusive = true;
+                     break;
+                 default:
+                     throw new AssertionError();
+             }
+ 
+             if (bounds[b.idx] != null)
+                 throw new InvalidRequestException(String.format(
 -                        "More than one restriction was found for the %s bound", b.name().toLowerCase()));
++                        "More than one restriction was found for the %s bound on %s", b.name().toLowerCase(), name));
+ 
+             bounds[b.idx] = t;
+             boundInclusive[b.idx] = inclusive;
+         }
+ 
+         @Override
+         public String toString()
+         {
+             return String.format("SLICE(%s %s, %s %s)%s", boundInclusive[0] ? ">=" : ">",
+                                  bounds[0],
+                                  boundInclusive[1] ? "<=" : "<",
+                                  bounds[1],
+                                  onToken ? "*" : "");
+         }
+     }
++
++    // This holds both CONTAINS and CONTAINS_KEY restriction because we might want to have both of them.
++    public static class Contains extends SingleColumnRestriction
++    {
++        private List<Term> values; // for CONTAINS
++        private List<Term> keys;   // for CONTAINS_KEY
++
++        public boolean hasContains()
++        {
++            return values != null;
++        }
++
++        public boolean hasContainsKey()
++        {
++            return keys != null;
++        }
++
++        public void add(Term t, boolean isKey)
++        {
++            if (isKey)
++                addKey(t);
++            else
++                addValue(t);
++        }
++
++        public void addValue(Term t)
++        {
++            if (values == null)
++                values = new ArrayList<>();
++            values.add(t);
++        }
++
++        public void addKey(Term t)
++        {
++            if (keys == null)
++                keys = new ArrayList<>();
++            keys.add(t);
++        }
++
++        public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException
++        {
++            if (values == null)
++                return Collections.emptyList();
++
++            List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(values.size());
++            for (Term value : values)
++                buffers.add(value.bindAndGet(options));
++            return buffers;
++        }
++
++        public List<ByteBuffer> keys(QueryOptions options) throws InvalidRequestException
++        {
++            if (keys == null)
++                return Collections.emptyList();
++
++            List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(keys.size());
++            for (Term value : keys)
++                buffers.add(value.bindAndGet(options));
++            return buffers;
++        }
++
++        public boolean isSlice()
++        {
++            return false;
++        }
++
++        public boolean isEQ()
++        {
++            return false;
++        }
++
++        public boolean isIN()
++        {
++            return false;
++        }
++
++        public boolean isContains()
++        {
++            return true;
++        }
++
++        public boolean isOnToken()
++        {
++            return false;
++        }
++
++
++        @Override
++        public String toString()
++        {
++            return String.format("CONTAINS(values=%s, keys=%s)", values, keys);
++        }
++    }
+ }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/db/composites/CBuilder.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/composites/CBuilder.java
index 490fb67,0000000..39035cb
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/composites/CBuilder.java
+++ b/src/java/org/apache/cassandra/db/composites/CBuilder.java
@@@ -1,34 -1,0 +1,36 @@@
 +/*
 + * 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.db.composites;
 +
 +import java.nio.ByteBuffer;
++import java.util.List;
 +
 +/**
 + * A builder of Composite.
 + */
 +public interface CBuilder
 +{
 +    public int remainingCount();
 +
 +    public CBuilder add(ByteBuffer value);
 +    public CBuilder add(Object value);
 +
 +    public Composite build();
 +    public Composite buildWith(ByteBuffer value);
++    public Composite buildWith(List<ByteBuffer> values);
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/db/composites/Composites.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/composites/Composites.java
index 58938c6,0000000..154e9f7
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/composites/Composites.java
+++ b/src/java/org/apache/cassandra/db/composites/Composites.java
@@@ -1,125 -1,0 +1,127 @@@
 +/*
 + * 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.db.composites;
 +
 +import java.nio.ByteBuffer;
++import java.util.List;
 +
 +import org.apache.cassandra.config.CFMetaData;
 +import org.apache.cassandra.db.filter.ColumnSlice;
 +import org.apache.cassandra.utils.memory.AbstractAllocator;
 +import org.apache.cassandra.utils.ByteBufferUtil;
 +
 +public abstract class Composites
 +{
 +    private Composites() {}
 +
 +    public static final Composite EMPTY = new EmptyComposite();
 +
 +    static final CBuilder EMPTY_BUILDER = new CBuilder()
 +    {
 +        public int remainingCount() { return 0; }
 +
 +        public CBuilder add(ByteBuffer value) { throw new IllegalStateException(); }
 +        public CBuilder add(Object value) { throw new IllegalStateException(); }
 +
 +        public Composite build() { return EMPTY; }
 +        public Composite buildWith(ByteBuffer value) { throw new IllegalStateException(); }
++        public Composite buildWith(List<ByteBuffer> values) { throw new IllegalStateException(); }
 +    };
 +
 +    private static class EmptyComposite implements Composite
 +    {
 +        public boolean isEmpty()
 +        {
 +            return true;
 +        }
 +
 +        public int size()
 +        {
 +            return 0;
 +        }
 +
 +        public ByteBuffer get(int i)
 +        {
 +            throw new IndexOutOfBoundsException();
 +        }
 +
 +        public EOC eoc()
 +        {
 +            return EOC.NONE;
 +        }
 +
 +        public Composite start()
 +        {
 +            // Note that SimpleCType/AbstractSimpleCellNameType compare method
 +            // indirectly rely on the fact that EMPTY == EMPTY.start() == EMPTY.end()
 +            // (or more precisely on the fact that the EOC is NONE for all of those).
 +            return this;
 +        }
 +
 +        public Composite end()
 +        {
 +            // Note that SimpleCType/AbstractSimpleCellNameType compare method
 +            // indirectly rely on the fact that EMPTY == EMPTY.start() == EMPTY.end()
 +            // (or more precisely on the fact that the EOC is NONE for all of those).
 +            return this;
 +        }
 +
 +        public Composite withEOC(EOC newEoc)
 +        {
 +            // Note that SimpleCType/AbstractSimpleCellNameType compare method
 +            // indirectly rely on the fact that EMPTY == EMPTY.start() == EMPTY.end()
 +            // (or more precisely on the fact that the EOC is NONE for all of those).
 +            return this;
 +        }
 +
 +        public ColumnSlice slice()
 +        {
 +            return ColumnSlice.ALL_COLUMNS;
 +        }
 +
 +        public ByteBuffer toByteBuffer()
 +        {
 +            return ByteBufferUtil.EMPTY_BYTE_BUFFER;
 +        }
 +
 +        public boolean isStatic()
 +        {
 +            return false;
 +        }
 +
 +        public int dataSize()
 +        {
 +            return 0;
 +        }
 +
 +        public long unsharedHeapSize()
 +        {
 +            return 0;
 +        }
 +
 +        public boolean isPrefixOf(CType type, Composite c)
 +        {
 +            return true;
 +        }
 +
 +        public Composite copy(CFMetaData cfm, AbstractAllocator allocator)
 +        {
 +            return this;
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/db/composites/CompoundCType.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/composites/CompoundCType.java
index 4322055,0000000..0458748
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/composites/CompoundCType.java
+++ b/src/java/org/apache/cassandra/db/composites/CompoundCType.java
@@@ -1,167 -1,0 +1,180 @@@
 +/*
 + * 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.db.composites;
 +
 +import java.nio.ByteBuffer;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.List;
 +
 +import org.apache.cassandra.db.marshal.AbstractType;
 +import org.apache.cassandra.db.marshal.CompositeType;
 +
 +/**
 + * A truly-composite CType.
 + */
 +public class CompoundCType extends AbstractCType
 +{
 +    final List<AbstractType<?>> types;
 +
 +    // It's up to the caller to pass a list that is effectively immutable
 +    public CompoundCType(List<AbstractType<?>> types)
 +    {
 +        super(isByteOrderComparable(types));
 +        this.types = types;
 +    }
 +
 +    public boolean isCompound()
 +    {
 +        return true;
 +    }
 +
 +    public int size()
 +    {
 +        return types.size();
 +    }
 +
 +    public AbstractType<?> subtype(int i)
 +    {
 +        return types.get(i);
 +    }
 +
 +    public Composite fromByteBuffer(ByteBuffer bytes)
 +    {
 +        if (!bytes.hasRemaining())
 +            return Composites.EMPTY;
 +
 +        ByteBuffer[] elements = new ByteBuffer[size()];
 +        int idx = bytes.position(), i = 0;
 +        byte eoc = 0;
 +
 +        boolean isStatic = false;
 +        if (CompositeType.isStaticName(bytes))
 +        {
 +            isStatic = true;
 +            idx += 2;
 +        }
 +
 +        while (idx < bytes.limit())
 +        {
 +            checkRemaining(bytes, idx, 2);
 +            int length = bytes.getShort(idx) & 0xFFFF;
 +            idx += 2;
 +
 +            checkRemaining(bytes, idx, length + 1);
 +            elements[i++] = sliceBytes(bytes, idx, length);
 +            idx += length;
 +            eoc = bytes.get(idx++);
 +        }
 +        return new CompoundComposite(elements, i, isStatic).withEOC(Composite.EOC.from(eoc));
 +    }
 +
 +    public CBuilder builder()
 +    {
 +        return new CompoundCBuilder(this);
 +    }
 +
 +    public CompoundCType setSubtype(int position, AbstractType<?> newType)
 +    {
 +        List<AbstractType<?>> newTypes = new ArrayList<AbstractType<?>>(types);
 +        newTypes.set(position, newType);
 +        return new CompoundCType(newTypes);
 +    }
 +
 +    public AbstractType<?> asAbstractType()
 +    {
 +        return CompositeType.getInstance(types);
 +    }
 +
 +    public static class CompoundCBuilder implements CBuilder
 +    {
 +        private final CType type;
 +        private final ByteBuffer[] values;
 +        private int size;
 +        private boolean built;
 +
 +        public CompoundCBuilder(CType type)
 +        {
 +            this.type = type;
 +            this.values = new ByteBuffer[type.size()];
 +        }
 +
 +        public int remainingCount()
 +        {
 +            return values.length - size;
 +        }
 +
 +        public CBuilder add(ByteBuffer value)
 +        {
 +            if (isDone())
 +                throw new IllegalStateException();
 +            values[size++] = value;
 +            return this;
 +        }
 +
 +        public CBuilder add(Object value)
 +        {
 +            return add(((AbstractType)type.subtype(size)).decompose(value));
 +        }
 +
 +        private boolean isDone()
 +        {
 +            return remainingCount() == 0 || built;
 +        }
 +
 +        public Composite build()
 +        {
 +            if (size == 0)
 +                return Composites.EMPTY;
 +
 +            // We don't allow to add more element to a builder that has been built so
 +            // that we don't have to copy values.
 +            built = true;
 +
 +            // If the builder is full and we're building a dense cell name, then we can
 +            // directly allocate the CellName object as it's complete.
 +            if (size == values.length && type instanceof CellNameType && ((CellNameType)type).isDense())
 +                return new CompoundDenseCellName(values);
 +            return new CompoundComposite(values, size, false);
 +        }
 +
 +        public Composite buildWith(ByteBuffer value)
 +        {
 +            ByteBuffer[] newValues = Arrays.copyOf(values, values.length);
 +            newValues[size] = value;
 +            // Same as above
 +            if (size+1 == newValues.length && type instanceof CellNameType && ((CellNameType)type).isDense())
 +                return new CompoundDenseCellName(newValues);
 +
 +            return new CompoundComposite(newValues, size+1, false);
 +        }
++
++        public Composite buildWith(List<ByteBuffer> newValues)
++        {
++            ByteBuffer[] buffers = Arrays.copyOf(values, values.length);
++            int newSize = size;
++            for (ByteBuffer value : newValues)
++                buffers[newSize++] = value;
++
++            if (newSize == buffers.length && type instanceof CellNameType && ((CellNameType)type).isDense())
++                return new CompoundDenseCellName(buffers);
++
++            return new CompoundComposite(buffers, newSize, false);
++        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/db/composites/SimpleCType.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/db/composites/SimpleCType.java
index 35e14f9,0000000..229d538
mode 100644,000000..100644
--- a/src/java/org/apache/cassandra/db/composites/SimpleCType.java
+++ b/src/java/org/apache/cassandra/db/composites/SimpleCType.java
@@@ -1,160 -1,0 +1,170 @@@
 +/*
 + * 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.db.composites;
 +
 +import java.nio.ByteBuffer;
++import java.util.List;
 +
 +import org.apache.cassandra.db.marshal.AbstractType;
 +import org.apache.cassandra.utils.ByteBufferUtil;
 +
 +/**
 + * A not truly-composite CType.
 + */
 +public class SimpleCType extends AbstractCType
 +{
 +    protected final AbstractType<?> type;
 +
 +    public SimpleCType(AbstractType<?> type)
 +    {
 +        super(type.isByteOrderComparable());
 +        this.type = type;
 +    }
 +
 +    public boolean isCompound()
 +    {
 +        return false;
 +    }
 +
 +    public int size()
 +    {
 +        return 1;
 +    }
 +
 +    public int compare(Composite c1, Composite c2)
 +    {
 +        // This method assumes that simple composites never have an EOC != NONE. This assumption
 +        // stands in particular on the fact that a Composites.EMPTY never has a non-NONE EOC. If
 +        // this ever change, we'll need to update this.
 +
 +        if (isByteOrderComparable)
 +        {
 +            // toByteBuffer is always cheap for simple types, and we keep virtual method calls to a minimum:
 +            // hasRemaining will always be inlined, as will most of the call-stack for BBU.compareUnsigned
 +            ByteBuffer b1 = c1.toByteBuffer();
 +            ByteBuffer b2 = c2.toByteBuffer();
 +            if (!b1.hasRemaining() || !b2.hasRemaining())
 +                return b1.hasRemaining() ? 1 : (b2.hasRemaining() ? -1 : 0);
 +            return ByteBufferUtil.compareUnsigned(b1, b2);
 +        }
 +
 +        boolean c1isEmpty = c1.isEmpty();
 +        boolean c2isEmpty = c2.isEmpty();
 +        if (c1isEmpty || c2isEmpty)
 +            return !c1isEmpty ? 1 : (!c2isEmpty ? -1 : 0);
 +
 +        return type.compare(c1.get(0), c2.get(0));
 +    }
 +
 +    public AbstractType<?> subtype(int i)
 +    {
 +        if (i != 0)
 +            throw new IndexOutOfBoundsException();
 +        return type;
 +    }
 +
 +    public Composite fromByteBuffer(ByteBuffer bytes)
 +    {
 +        return !bytes.hasRemaining() ? Composites.EMPTY : new SimpleComposite(bytes);
 +    }
 +
 +    public CBuilder builder()
 +    {
 +        return new SimpleCBuilder(this);
 +    }
 +
 +    public CType setSubtype(int position, AbstractType<?> newType)
 +    {
 +        if (position != 0)
 +            throw new IndexOutOfBoundsException();
 +        return new SimpleCType(newType);
 +    }
 +
 +    // Use sparingly, it defeats the purpose
 +    public AbstractType<?> asAbstractType()
 +    {
 +        return type;
 +    }
 +
 +    public static class SimpleCBuilder implements CBuilder
 +    {
 +        private final CType type;
 +        private ByteBuffer value;
 +
 +        public SimpleCBuilder(CType type)
 +        {
 +            this.type = type;
 +        }
 +
 +        public int remainingCount()
 +        {
 +            return value == null ? 1 : 0;
 +        }
 +
 +        public CBuilder add(ByteBuffer value)
 +        {
 +            if (this.value != null)
 +                throw new IllegalStateException();
 +            this.value = value;
 +            return this;
 +        }
 +
 +        public CBuilder add(Object value)
 +        {
 +            return add(((AbstractType)type.subtype(0)).decompose(value));
 +        }
 +
 +        public Composite build()
 +        {
 +            if (value == null || !value.hasRemaining())
 +                return Composites.EMPTY;
 +
 +            // If we're building a dense cell name, then we can directly allocate the
 +            // CellName object as it's complete.
 +            if (type instanceof CellNameType && ((CellNameType)type).isDense())
 +                return new SimpleDenseCellName(value);
 +
 +            return new SimpleComposite(value);
 +        }
 +
 +        public Composite buildWith(ByteBuffer value)
 +        {
 +            if (this.value != null)
 +                throw new IllegalStateException();
 +
 +            if (value == null || !value.hasRemaining())
 +                return Composites.EMPTY;
 +
 +            // If we're building a dense cell name, then we can directly allocate the
 +            // CellName object as it's complete.
 +            if (type instanceof CellNameType && ((CellNameType)type).isDense())
 +                return new SimpleDenseCellName(value);
 +
 +            return new SimpleComposite(value);
 +        }
++
++        public Composite buildWith(List<ByteBuffer> values)
++        {
++            if (values.size() > 1)
++                throw new IllegalStateException();
++            if (values.isEmpty())
++                return Composites.EMPTY;
++            return buildWith(values.get(0));
++        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
index 0000000,b728cba..35646be
mode 000000,100644..100644
--- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java
@@@ -1,0 -1,1112 +1,1114 @@@
+ /*
+  * 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;
+ 
+ import org.apache.cassandra.SchemaLoader;
++import org.apache.cassandra.cql3.statements.ParsedStatement;
+ import org.apache.cassandra.db.ConsistencyLevel;
+ import org.apache.cassandra.db.marshal.*;
+ import org.apache.cassandra.exceptions.InvalidRequestException;
+ import org.apache.cassandra.exceptions.RequestExecutionException;
+ import org.apache.cassandra.exceptions.RequestValidationException;
+ import org.apache.cassandra.exceptions.SyntaxException;
+ import org.apache.cassandra.gms.Gossiper;
++import org.apache.cassandra.serializers.CollectionSerializer;
+ import org.apache.cassandra.service.ClientState;
+ import org.apache.cassandra.service.QueryState;
+ import org.apache.cassandra.transport.messages.ResultMessage;
+ import org.apache.cassandra.utils.ByteBufferUtil;
+ import org.apache.cassandra.utils.MD5Digest;
+ import org.junit.AfterClass;
+ import org.junit.BeforeClass;
+ import org.junit.Test;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import java.nio.ByteBuffer;
+ import java.util.*;
+ 
++import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
+ import static org.apache.cassandra.cql3.QueryProcessor.process;
 -import static org.apache.cassandra.cql3.QueryProcessor.processInternal;
+ import static org.junit.Assert.assertTrue;
+ import static org.junit.Assert.assertEquals;
+ import static com.google.common.collect.Lists.newArrayList;
+ import static org.junit.Assert.fail;
+ 
+ public class MultiColumnRelationTest
+ {
+     private static final Logger logger = LoggerFactory.getLogger(MultiColumnRelationTest.class);
+     static ClientState clientState;
+     static String keyspace = "multi_column_relation_test";
+ 
+     @BeforeClass
+     public static void setUpClass() throws Throwable
+     {
+         SchemaLoader.loadSchema();
+         executeSchemaChange("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}");
+         executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.single_partition (a int PRIMARY KEY, b int)");
+         executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.compound_partition (a int, b int, c int, PRIMARY KEY ((a, b)))");
+         executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.single_clustering (a int, b int, c int, PRIMARY KEY (a, b))");
+         executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.multiple_clustering (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))");
+         executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.multiple_clustering_reversed (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d)) WITH CLUSTERING ORDER BY (b DESC, c ASC, d DESC)");
+         clientState = ClientState.forInternalCalls();
+     }
+ 
+     @AfterClass
+     public static void stopGossiper()
+     {
+         Gossiper.instance.stop();
+     }
+ 
+     private static void executeSchemaChange(String query) throws Throwable
+     {
+         try
+         {
+             process(String.format(query, keyspace), ConsistencyLevel.ONE);
+         } catch (RuntimeException exc)
+         {
+             throw exc.getCause();
+         }
+     }
+ 
+     private static UntypedResultSet execute(String query) throws Throwable
+     {
+         try
+         {
 -            return processInternal(String.format(query, keyspace));
++            return executeOnceInternal(String.format(query, keyspace));
+         } catch (RuntimeException exc)
+         {
+             if (exc.getCause() != null)
+                 throw exc.getCause();
+             throw exc;
+         }
+     }
+ 
+     private MD5Digest prepare(String query) throws RequestValidationException
+     {
+         ResultMessage.Prepared prepared = QueryProcessor.prepare(String.format(query, keyspace), clientState, false);
+         return prepared.statementId;
+     }
+ 
+     private UntypedResultSet executePrepared(MD5Digest statementId, QueryOptions options) throws RequestValidationException, RequestExecutionException
+     {
 -        CQLStatement statement = QueryProcessor.instance.getPrepared(statementId);
 -        ResultMessage message = statement.executeInternal(QueryState.forInternalCalls(), options);
++        ParsedStatement.Prepared prepared = QueryProcessor.instance.getPrepared(statementId);
++        ResultMessage message = prepared.statement.executeInternal(QueryState.forInternalCalls(), options);
+ 
+         if (message instanceof ResultMessage.Rows)
 -            return new UntypedResultSet(((ResultMessage.Rows)message).result);
++            return UntypedResultSet.create(((ResultMessage.Rows)message).result);
+         else
+             return null;
+     }
+ 
+     @Test(expected=SyntaxException.class)
+     public void testEmptyIdentifierTuple() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_clustering WHERE () = (1, 2)");
+     }
+ 
+     @Test(expected=SyntaxException.class)
+     public void testEmptyValueTuple() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE (b, c) > ()");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testDifferentTupleLengths() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE (b, c) > (1, 2, 3)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testNullInTuple() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE (b, c) > (1, null)");
+     }
+ 
+     @Test
+     public void testEmptyIN() throws Throwable
+     {
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ()");
+         assertTrue(results.isEmpty());
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testNullInINValues() throws Throwable
+     {
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((1, 2, null))");
+         assertTrue(results.isEmpty());
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPartitionKeyInequality() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_partition WHERE (a) > (1)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPartitionKeyEquality() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_partition WHERE (a) = (0)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testRestrictNonPrimaryKey() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_partition WHERE (b) = (0)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testMixEqualityAndInequality() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (0) AND (b) > (0)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testMixMultipleInequalitiesOnSameBound() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (0) AND (b) > (1)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testClusteringColumnsOutOfOrderInInequality() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (d, c, b) > (0, 0, 0)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testSkipClusteringColumnInEquality() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) = (0, 0)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testSkipClusteringColumnInInequality() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) > (0, 0)");
+     }
+ 
+     @Test
+     public void testSingleClusteringColumnEquality() throws Throwable
+     {
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)");
+         UntypedResultSet results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (1)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (3)");
+         assertEquals(0, results.size());
+     }
+ 
+     @Test
+     public void testMultipleClusteringColumnEquality() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 2, 0, 0)");
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) = (1)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(2, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) = (1, 1)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 1, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) = (1, 1, 1)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 1, 1);
+         execute("DELETE FROM %s.multiple_clustering WHERE a=0 AND b=2 and c=0 and d=0");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPartitionAndClusteringColumnEquality() throws Throwable
+     {
+         execute("SELECT * FROM %s.single_clustering WHERE (a, b) = (0, 0)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testClusteringColumnsOutOfOrderInEquality() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (d, c, b) = (3, 2, 1)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testBadType() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) = (1, 2, 'foobar')");
+     }
+ 
+     @Test(expected=SyntaxException.class)
+     public void testSingleColumnTupleRelation() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND b = (1, 2, 3)");
+     }
+ 
+     @Test
+     public void testMixSingleAndTupleInequalities() throws Throwable
+     {
+         String[] queries = new String[]{
+             "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND b < 1",
+             "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND c < 1",
+             "SELECT * FROM %s.multiple_clustering WHERE a=0 AND b > 1 AND (b, c, d) < (1, 1, 0)",
+             "SELECT * FROM %s.multiple_clustering WHERE a=0 AND c > 1 AND (b, c, d) < (1, 1, 0)",
+         };
+ 
+         for (String query : queries)
+         {
+             try
+             {
+                 execute(query);
+                 fail(String.format("Expected query \"%s\" to throw an InvalidRequestException", query));
+             }
+             catch (InvalidRequestException e) {}
+         }
+     }
+ 
+     @Test
+     public void testSingleClusteringColumnInequality() throws Throwable
+     {
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)");
+ 
+         UntypedResultSet results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (0)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 0);
+         checkRow(1, results, 0, 2, 0);
+ 
+         results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) >= (1)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 0);
+         checkRow(1, results, 0, 2, 0);
+ 
+         results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) < (2)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0);
+         checkRow(1, results, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) <= (1)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0);
+         checkRow(1, results, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (0) AND (b) < (2)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0);
+     }
+ 
+     @Test
+     public void testMultipleClusteringColumnInequality() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)");
+ 
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (0)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(2, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) >= (0)");
+         assertEquals(6, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+         checkRow(3, results, 0, 1, 0, 0);
+         checkRow(4, results, 0, 1, 1, 0);
+         checkRow(5, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > (1, 0)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 1, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) >= (1, 0)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(2, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (1, 1, 0)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) >= (1, 1, 0)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 1, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) < (1)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) <= (1)");
+         assertEquals(6, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+         checkRow(3, results, 0, 1, 0, 0);
+         checkRow(4, results, 0, 1, 1, 0);
+         checkRow(5, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) < (0, 1)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) <= (0, 1)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) < (0, 1, 1)");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) <= (0, 1, 1)");
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND (b) < (1)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c) < (1, 1)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c, d) < (1, 1, 0)");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         // reversed
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(3, results.size());
+         checkRow(2, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) >= (0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(6, results.size());
+         checkRow(5, results, 0, 0, 0, 0);
+         checkRow(4, results, 0, 0, 1, 0);
+         checkRow(3, results, 0, 0, 1, 1);
+         checkRow(2, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > (1, 0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(2, results.size());
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) >= (1, 0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(3, results.size());
+         checkRow(2, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (1, 1, 0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) >= (1, 1, 0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(2, results.size());
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) < (1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(3, results.size());
+         checkRow(2, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) <= (1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(6, results.size());
+         checkRow(5, results, 0, 0, 0, 0);
+         checkRow(4, results, 0, 0, 1, 0);
+         checkRow(3, results, 0, 0, 1, 1);
+         checkRow(2, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) < (0, 1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) <= (0, 1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(3, results.size());
+         checkRow(2, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) < (0, 1, 1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(2, results.size());
+         checkRow(1, results, 0, 0, 0, 0);
+         checkRow(0, results, 0, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) <= (0, 1, 1) ORDER BY b DESC, c DESC, d DESC");
+         checkRow(2, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND (b) < (1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c) < (1, 1) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c, d) < (1, 1, 0) ORDER BY b DESC, c DESC, d DESC");
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+     }
+ 
+     @Test
+     public void testMultipleClusteringColumnInequalityReversedComponents() throws Throwable
+     {
+         // b and d are reversed in the clustering order
+         execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 1, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 1, 1, 1)");
+         execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 1, 1, 0)");
+ 
+         execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 0, 1, 1)");
+         execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 0, 1, 0)");
+ 
+ 
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) > (0)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+         checkRow(2, results, 0, 1, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) >= (0)");
+         assertEquals(6, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+         checkRow(2, results, 0, 1, 1, 0);
+         checkRow(3, results, 0, 0, 0, 0);
+         checkRow(4, results, 0, 0, 1, 1);
+         checkRow(5, results, 0, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) < (1)");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+         checkRow(2, results, 0, 0, 1, 0);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) <= (1)");
+         assertEquals(6, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+         checkRow(2, results, 0, 1, 1, 0);
+         checkRow(3, results, 0, 0, 0, 0);
+         checkRow(4, results, 0, 0, 1, 1);
+         checkRow(5, results, 0, 0, 1, 0);
+ 
+         // preserve pre-6875 behavior (even though the query result is technically incorrect)
+         results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b, c) > (1, 0)");
+         assertEquals(0, results.size());
+     }
+ 
+     @Test
+     public void testLiteralIn() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1, 0), (0, 1, 1))");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         // same query, but reversed order for the IN values
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1, 1), (0, 1, 0))");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN ((0, 1))");
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ((0))");
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testLiteralInWithShortTuple() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1))");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testLiteralInWithLongTuple() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1, 2, 3, 4))");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testLiteralInWithPartitionKey() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE (a, b, c, d) IN ((0, 1, 2, 3))");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testLiteralInSkipsClusteringColumn() throws Throwable
+     {
+         execute("SELECT * FROM %s.multiple_clustering WHERE (c, d) IN ((0, 1))");
+     }
+     @Test
+     public void testPartitionAndClusteringInClauses() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (1, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (1, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (1, 0, 1, 1)");
+ 
+         UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (0, 1) AND (b, c, d) IN ((0, 1, 0), (0, 1, 1))");
+         assertEquals(4, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+         checkRow(2, results, 1, 0, 1, 0);
+         checkRow(3, results, 1, 0, 1, 1);
+ 
+         // same query, but reversed order for the IN values
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (1, 0) AND (b, c, d) IN ((0, 1, 1), (0, 1, 0))");
+         assertEquals(4, results.size());
+         checkRow(0, results, 1, 0, 1, 0);
+         checkRow(1, results, 1, 0, 1, 1);
+         checkRow(2, results, 0, 0, 1, 0);
+         checkRow(3, results, 0, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (0, 1) and (b, c) IN ((0, 1))");
+         assertEquals(4, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+         checkRow(2, results, 1, 0, 1, 0);
+         checkRow(3, results, 1, 0, 1, 1);
+ 
+         results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (0, 1) and (b) IN ((0))");
+         assertEquals(6, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+         checkRow(3, results, 1, 0, 0, 0);
+         checkRow(4, results, 1, 0, 1, 0);
+         checkRow(5, results, 1, 0, 1, 1);
+     }
+ 
+     // prepare statement tests
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPreparePartitionAndClusteringColumnEquality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.single_clustering WHERE (a, b) = (?, ?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareDifferentTupleLengths() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE (b, c) > (?, ?, ?)");
+     }
+ 
+     @Test
+     public void testPrepareEmptyIN() throws Throwable
+     {
+         MD5Digest id = prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ()");
+         UntypedResultSet results = executePrepared(id, makeIntOptions());
+         assertTrue(results.isEmpty());
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPreparePartitionKeyInequality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.single_partition WHERE (a) > (?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPreparePartitionKeyEquality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.single_partition WHERE (a) = (?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareRestrictNonPrimaryKey() throws Throwable
+     {
+         prepare("SELECT * FROM %s.single_partition WHERE (b) = (?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareMixEqualityAndInequality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (?) AND (b) > (?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareMixMultipleInequalitiesOnSameBound() throws Throwable
+     {
+         prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (?) AND (b) > (?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareClusteringColumnsOutOfOrderInInequality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (d, c, b) > (?, ?, ?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareSkipClusteringColumnInEquality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) = (?, ?)");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareSkipClusteringColumnInInequality() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) > (?, ?)");
+     }
+ 
+     @Test
+     public void testPreparedClusteringColumnEquality() throws Throwable
+     {
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)");
+         MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (?)");
+         UntypedResultSet results = executePrepared(id, makeIntOptions(0));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 0);
+     }
+ 
+     @Test
+     public void testPreparedClusteringColumnEqualitySingleMarker() throws Throwable
+     {
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)");
+         MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = ?");
+         UntypedResultSet results = executePrepared(id, options(tuple(0)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 0);
+     }
+ 
+     @Test
+     public void testPreparedSingleClusteringColumnInequality() throws Throwable
+     {
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)");
+ 
+         MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (?)");
+         UntypedResultSet results = executePrepared(id, makeIntOptions(0));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 0);
+         checkRow(1, results, 0, 2, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) >= (?)"), makeIntOptions(1));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 0);
+         checkRow(1, results, 0, 2, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) < (?)"), makeIntOptions(2));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0);
+         checkRow(1, results, 0, 1, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) <= (?)"), makeIntOptions(1));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0);
+         checkRow(1, results, 0, 1, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (?) AND (b) < (?)"), makeIntOptions(0, 2));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0);
+     }
+ 
+     @Test
+     public void testPreparedSingleClusteringColumnInequalitySingleMarker() throws Throwable
+     {
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)");
+         execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)");
+ 
+         MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > ?");
+         UntypedResultSet results = executePrepared(id, options(tuple(0)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 0);
+         checkRow(1, results, 0, 2, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) >= ?"), options(tuple(1)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 0);
+         checkRow(1, results, 0, 2, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) < ?"), options(tuple(2)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0);
+         checkRow(1, results, 0, 1, 0);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) <= ?"), options(tuple(1)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 0);
+         checkRow(1, results, 0, 1, 0);
+ 
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > ? AND (b) < ?"),
+                 options(tuple(0), tuple(2)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0);
+     }
+ 
+     @Test
+     public void testPrepareMultipleClusteringColumnInequality() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)");
+ 
+         UntypedResultSet results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (?)"), makeIntOptions(0));
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(2, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > (?, ?)"), makeIntOptions(1, 0));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 1, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare
+                 ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?)"), makeIntOptions(1, 1, 0));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b) < (?)"),
+                 makeIntOptions(0, 1, 0, 1));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare
+                 ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b, c) < (?, ?)"),
+                 makeIntOptions(0, 1, 1, 1, 1));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b, c, d) < (?, ?, ?)"),
+                 makeIntOptions(0, 1, 1, 1, 1, 0));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         // reversed
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (?) ORDER BY b DESC, c DESC, d DESC"),
+                 makeIntOptions(0));
+         assertEquals(3, results.size());
+         checkRow(2, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b, c) < (?, ?) ORDER BY b DESC, c DESC, d DESC"),
+                 makeIntOptions(0, 1, 1, 1, 1));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+     }
+ 
+     @Test
+     public void testPrepareMultipleClusteringColumnInequalitySingleMarker() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)");
+ 
+         UntypedResultSet results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > ?"), options(tuple(0)));
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(2, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > ?"), options(tuple(1, 0)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 1, 1, 0);
+         checkRow(1, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare
+                 ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ?"), options(tuple(1, 1, 0)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b) < ?"),
+                 options(tuple(0, 1, 0), tuple(1)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare
+                 ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b, c) < ?"),
+                 options(tuple(0, 1, 1), tuple(1, 1)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b, c, d) < ?"),
+                 options(tuple(0, 1, 1), tuple(1, 1, 0)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+ 
+         // reversed
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > ? ORDER BY b DESC, c DESC, d DESC"),
+                 options(tuple(0)));
+         assertEquals(3, results.size());
+         checkRow(2, results, 0, 1, 0, 0);
+         checkRow(1, results, 0, 1, 1, 0);
+         checkRow(0, results, 0, 1, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b, c) < ? ORDER BY b DESC, c DESC, d DESC"),
+                 options(tuple(0, 1, 1), tuple(1, 1)));
+         assertEquals(1, results.size());
+         checkRow(0, results, 0, 1, 0, 0);
+     }
+ 
+     @Test
+     public void testPrepareLiteralIn() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         UntypedResultSet results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))"),
+                 makeIntOptions(0, 1, 0, 0, 1, 1));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         // same query, but reversed order for the IN values
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))"),
+                 makeIntOptions(0, 1, 1, 0, 1, 0));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN ((?, ?))"),
+                 makeIntOptions(0, 1));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ((?))"),
+                 makeIntOptions(0));
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+     }
+ 
+     @Test
+     public void testPrepareInOneMarkerPerTuple() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         UntypedResultSet results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN (?, ?)"),
+                 options(tuple(0, 1, 0), tuple(0, 1, 1)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         // same query, but reversed order for the IN values
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN (?, ?)"),
+                 options(tuple(0, 1, 1), tuple(0, 1, 0)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN (?)"),
+                 options(tuple(0, 1)));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN (?)"),
+                 options(tuple(0)));
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+     }
+ 
+     @Test
+     public void testPrepareInOneMarker() throws Throwable
+     {
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)");
+         execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)");
+ 
+         UntypedResultSet results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ?"),
+                 options(list(tuple(0, 1, 0), tuple(0, 1, 1))));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         // same query, but reversed order for the IN values
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ?"),
+                 options(list(tuple(0, 1, 1), tuple(0, 1, 0))));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare(
+                 "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ?"),
+                 options(list()));
+         assertTrue(results.isEmpty());
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN ?"),
+                 options(list(tuple(0, 1))));
+         assertEquals(2, results.size());
+         checkRow(0, results, 0, 0, 1, 0);
+         checkRow(1, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ?"),
+                 options(list(tuple(0))));
+         assertEquals(3, results.size());
+         checkRow(0, results, 0, 0, 0, 0);
+         checkRow(1, results, 0, 0, 1, 0);
+         checkRow(2, results, 0, 0, 1, 1);
+ 
+         results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ?"),
+                 options(list()));
+         assertTrue(results.isEmpty());
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareLiteralInWithShortTuple() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?))");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareLiteralInWithLongTuple() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?, ?, ?))");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareLiteralInWithPartitionKey() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE (a, b, c, d) IN ((?, ?, ?, ?))");
+     }
+ 
+     @Test(expected=InvalidRequestException.class)
+     public void testPrepareLiteralInSkipsClusteringColumn() throws Throwable
+     {
+         prepare("SELECT * FROM %s.multiple_clustering WHERE (c, d) IN ((?, ?))");
+     }
+ 
+     private static QueryOptions makeIntOptions(Integer... values)
+     {
+         List<ByteBuffer> buffers = new ArrayList<>(values.length);
+         for (int value : values)
+             buffers.add(ByteBufferUtil.bytes(value));
 -        return new QueryOptions(ConsistencyLevel.ONE, buffers);
++        return QueryOptions.forInternalCalls(buffers);
+     }
+ 
+     private static ByteBuffer tuple(Integer... values)
+     {
+         List<AbstractType<?>> types = new ArrayList<>(values.length);
+         ByteBuffer[] buffers = new ByteBuffer[values.length];
+         for (int i = 0; i < values.length; i++)
+         {
+             types.add(Int32Type.instance);
+             buffers[i] = ByteBufferUtil.bytes(values[i]);
+         }
+ 
+         TupleType type = new TupleType(types);
+         return type.buildValue(buffers);
+     }
+ 
+     private static ByteBuffer list(ByteBuffer... values)
+     {
 -        return CollectionType.pack(Arrays.asList(values), values.length);
++        return CollectionSerializer.pack(Arrays.asList(values), values.length, 3);
+     }
+ 
+     private static QueryOptions options(ByteBuffer... buffers)
+     {
 -        return new QueryOptions(ConsistencyLevel.ONE, Arrays.asList(buffers));
++        return QueryOptions.forInternalCalls(Arrays.asList(buffers));
+     }
+ 
+     private static void checkRow(int rowIndex, UntypedResultSet results, Integer... expectedValues)
+     {
+         List<UntypedResultSet.Row> rows = newArrayList(results.iterator());
+         UntypedResultSet.Row row = rows.get(rowIndex);
+         Iterator<ColumnSpecification> columns = row.getColumns().iterator();
+         for (Integer expected : expectedValues)
+         {
+             String columnName = columns.next().name.toString();
+             int actual = row.getInt(columnName);
+             assertEquals(String.format("Expected value %d for column %s in row %d, but got %s", actual, columnName, rowIndex, expected),
+                          (long) expected, actual);
+         }
+     }
+ }