Merge branch 'cassandra-2.0' into cassandra-2.1 Conflicts: CHANGES.txt src/java/org/apache/cassandra/cql3/Cql.g src/java/org/apache/cassandra/cql3/Lists.java src/java/org/apache/cassandra/cql3/QueryProcessor.java src/java/org/apache/cassandra/cql3/Relation.java src/java/org/apache/cassandra/cql3/ResultSet.java src/java/org/apache/cassandra/cql3/statements/BatchStatement.java src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java src/java/org/apache/cassandra/cql3/statements/Restriction.java src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/bf521900 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/bf521900 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/bf521900 Branch: refs/heads/trunk Commit: bf5219000be9c03daa1dc4fb420b031f6ffec01d Parents: 9bd3887 4349638 Author: Tyler Hobbs <ty...@datastax.com> Authored: Thu May 22 14:19:42 2014 -0500 Committer: Tyler Hobbs <ty...@datastax.com> Committed: Thu May 22 14:19:42 2014 -0500 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/cassandra/cql3/AbstractMarker.java | 10 +- .../org/apache/cassandra/cql3/Constants.java | 6 + src/java/org/apache/cassandra/cql3/Cql.g | 109 +- src/java/org/apache/cassandra/cql3/Lists.java | 13 +- .../cassandra/cql3/MultiColumnRelation.java | 144 +++ .../org/apache/cassandra/cql3/Relation.java | 104 +- .../cassandra/cql3/SingleColumnRelation.java | 95 ++ src/java/org/apache/cassandra/cql3/Term.java | 10 + src/java/org/apache/cassandra/cql3/Tuples.java | 349 ++++++ .../cql3/statements/ModificationStatement.java | 20 +- .../cql3/statements/MultiColumnRestriction.java | 137 +++ .../cassandra/cql3/statements/Restriction.java | 395 +------ .../cql3/statements/SelectStatement.java | 805 ++++++++----- .../statements/SingleColumnRestriction.java | 413 +++++++ .../cassandra/db/composites/CBuilder.java | 2 + .../cassandra/db/composites/Composites.java | 2 + .../cassandra/db/composites/CompoundCType.java | 13 + .../cassandra/db/composites/SimpleCType.java | 10 + .../apache/cassandra/db/marshal/TupleType.java | 279 +++++ .../cassandra/cql3/MultiColumnRelationTest.java | 1114 ++++++++++++++++++ 21 files changed, 3263 insertions(+), 768 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 55fc400,c6c51c3..7ab411b --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -64,7 -35,24 +64,8 @@@ Merged from 2.0 * Fix 2ndary index queries with DESC clustering order (CASSANDRA-6950) * Invalid key cache entries on DROP (CASSANDRA-6525) * Fix flapping RecoveryManagerTest (CASSANDRA-7084) + * Add missing iso8601 patterns for date strings (6973) ++ * Support selecting multiple rows in a partition using IN (CASSANDRA-6875) Merged from 1.2: * Add Cloudstack snitch (CASSANDRA-7147) * Update system.peers correctly when relocating tokens (CASSANDRA-7126) http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/AbstractMarker.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/AbstractMarker.java index 2b9c6c9,4329ed9..0b59ed4 --- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java +++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java @@@ -99,10 -103,10 +103,10 @@@ public abstract class AbstractMarker ex } @Override - public AbstractMarker prepare(ColumnSpecification receiver) throws InvalidRequestException + public AbstractMarker prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { if (receiver.type instanceof CollectionType) - throw new InvalidRequestException("Invalid IN relation on collection column"); + throw new InvalidRequestException("Collection columns do not support IN relations"); return new Lists.Marker(bindIndex, makeInReceiver(receiver)); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/Constants.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/Cql.g ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Cql.g index 4c1f2dc,ceb2bde..57b61a5 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@@ -962,44 -894,82 +976,85 @@@ relationType returns [Relation.Type op ; relation[List<Relation> clauses] - : name=cident type=relationType t=term { $clauses.add(new Relation(name, type, t)); } - | K_TOKEN - { List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>(); } - '(' name1=cident { l.add(name1); } ( ',' namen=cident { l.add(namen); })* ')' - type=relationType t=term + : name=cident type=relationType t=term { $clauses.add(new SingleColumnRelation(name, type, t)); } + | K_TOKEN l=tupleOfIdentifiers type=relationType t=term { for (ColumnIdentifier id : l) - $clauses.add(new Relation(id, type, t, true)); + $clauses.add(new SingleColumnRelation(id, type, t, true)); } - | name=cident K_IN { Term.Raw marker = null; } (QMARK { marker = newINBindVariables(null); } | ':' mid=cident { marker = newINBindVariables(mid); }) - { $clauses.add(new Relation(name, Relation.Type.IN, marker)); } - | name=cident K_IN { Relation rel = Relation.createInRelation($name.id); } - '(' ( f1=term { rel.addInValue(f1); } (',' fN=term { rel.addInValue(fN); } )* )? ')' { $clauses.add(rel); } + | name=cident K_IN marker=inMarker + { $clauses.add(new SingleColumnRelation(name, Relation.Type.IN, marker)); } + | name=cident K_IN inValues=singleColumnInValues + { $clauses.add(SingleColumnRelation.createInRelation($name.id, inValues)); } + | name=cident K_CONTAINS { Relation.Type rt = Relation.Type.CONTAINS; } (K_KEY { rt = Relation.Type.CONTAINS_KEY; })? - t=term { $clauses.add(new Relation(name, rt, t)); } - | { - List<ColumnIdentifier> ids = new ArrayList<ColumnIdentifier>(); - List<Term.Raw> terms = new ArrayList<Term.Raw>(); - } - '(' n1=cident { ids.add(n1); } (',' ni=cident { ids.add(ni); })* ')' - type=relationType - '(' t1=term { terms.add(t1); } (',' ti=term { terms.add(ti); })* ')' - { - if (type == Relation.Type.IN) - addRecognitionError("Cannot use IN relation with tuple notation"); - if (ids.size() != terms.size()) - addRecognitionError(String.format("Number of values (" + terms.size() + ") in tuple notation doesn't match the number of column names (" + ids.size() + ")")); - else - for (int i = 0; i < ids.size(); i++) - $clauses.add(new Relation(ids.get(i), type, terms.get(i), i == 0 ? null : ids.get(i-1))); - } ++ t=term { $clauses.add(new SingleColumnRelation(name, rt, t)); } + | ids=tupleOfIdentifiers + ( K_IN + ( '(' ')' + { $clauses.add(MultiColumnRelation.createInRelation(ids, new ArrayList<Tuples.Literal>())); } + | tupleInMarker=inMarkerForTuple /* (a, b, c) IN ? */ + { $clauses.add(MultiColumnRelation.createSingleMarkerInRelation(ids, tupleInMarker)); } + | literals=tupleOfTupleLiterals /* (a, b, c) IN ((1, 2, 3), (4, 5, 6), ...) */ + { + $clauses.add(MultiColumnRelation.createInRelation(ids, literals)); + } + | markers=tupleOfMarkersForTuples /* (a, b, c) IN (?, ?, ...) */ + { $clauses.add(MultiColumnRelation.createInRelation(ids, markers)); } + ) + | type=relationType literal=tupleLiteral /* (a, b, c) > (1, 2, 3) or (a, b, c) > (?, ?, ?) */ + { + $clauses.add(MultiColumnRelation.createNonInRelation(ids, type, literal)); + } + | type=relationType tupleMarker=markerForTuple /* (a, b, c) >= ? */ + { $clauses.add(MultiColumnRelation.createNonInRelation(ids, type, tupleMarker)); } + ) | '(' relation[$clauses] ')' ; + inMarker returns [AbstractMarker.INRaw marker] + : QMARK { $marker = newINBindVariables(null); } + | ':' name=cident { $marker = newINBindVariables(name); } + ; + + tupleOfIdentifiers returns [List<ColumnIdentifier> ids] + @init { $ids = new ArrayList<ColumnIdentifier>(); } + : '(' n1=cident { $ids.add(n1); } (',' ni=cident { $ids.add(ni); })* ')' + ; + + singleColumnInValues returns [List<Term.Raw> terms] + @init { $terms = new ArrayList<Term.Raw>(); } + : '(' ( t1 = term { $terms.add(t1); } (',' ti=term { $terms.add(ti); })* )? ')' + ; + + tupleLiteral returns [Tuples.Literal literal] + @init { List<Term.Raw> terms = new ArrayList<>(); } + : '(' t1=term { terms.add(t1); } (',' ti=term { terms.add(ti); })* ')' { $literal = new Tuples.Literal(terms); } + ; + + tupleOfTupleLiterals returns [List<Tuples.Literal> literals] + @init { $literals = new ArrayList<>(); } + : '(' t1=tupleLiteral { $literals.add(t1); } (',' ti=tupleLiteral { $literals.add(ti); })* ')' + ; + + markerForTuple returns [Tuples.Raw marker] + : QMARK { $marker = newTupleBindVariables(null); } + | ':' name=cident { $marker = newTupleBindVariables(name); } + ; + + tupleOfMarkersForTuples returns [List<Tuples.Raw> markers] + @init { $markers = new ArrayList<Tuples.Raw>(); } + : '(' m1=markerForTuple { $markers.add(m1); } (',' mi=markerForTuple { $markers.add(mi); })* ')' + ; + + inMarkerForTuple returns [Tuples.INRaw marker] + : QMARK { $marker = newTupleINBindVariables(null); } + | ':' name=cident { $marker = newTupleINBindVariables(name); } + ; + -comparatorType returns [CQL3Type t] - : c=native_type { $t = c; } +comparatorType returns [CQL3Type.Raw t] + : n=native_type { $t = CQL3Type.Raw.from(n); } | c=collection_type { $t = c; } + | id=userTypeName { $t = CQL3Type.Raw.userType(id); } | s=STRING_LITERAL { try { http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/Lists.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Lists.java index f12af88,d483dd5..c214fa8 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/Lists.java @@@ -147,25 -144,18 +147,30 @@@ public abstract class List } } - public ByteBuffer get() + public ByteBuffer get(QueryOptions options) { - return CollectionType.pack(elements, elements.size()); + return CollectionSerializer.pack(elements, elements.size(), options.getProtocolVersion()); + } + + public boolean equals(ListType lt, Value v) + { + if (elements.size() != v.elements.size()) + return false; + + for (int i = 0; i < elements.size(); i++) + if (lt.elements.compare(elements.get(i), v.elements.get(i)) != 0) + return false; + + return true; } + + public List<ByteBuffer> getElements() + { + return elements; + } } - /* + /** * Basically similar to a Value, but with some non-pure function (that need * to be evaluated at execution time) in it. * @@@ -223,11 -216,10 +231,10 @@@ assert receiver.type instanceof ListType; } - public Value bind(List<ByteBuffer> values) throws InvalidRequestException + public Value bind(QueryOptions options) throws InvalidRequestException { - ByteBuffer value = values.get(bindIndex); - return value == null ? null : Value.fromSerialized(value, (ListType)receiver.type); + ByteBuffer value = options.getValues().get(bindIndex); + return value == null ? null : Value.fromSerialized(value, (ListType)receiver.type, options.getProtocolVersion()); - } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/Relation.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Relation.java index 2eeef1d,0f1366d..5318907 --- a/src/java/org/apache/cassandra/cql3/Relation.java +++ b/src/java/org/apache/cassandra/cql3/Relation.java @@@ -17,78 -17,35 +17,47 @@@ */ package org.apache.cassandra.cql3; - import java.util.ArrayList; - import java.util.List; + - /** - * Relations encapsulate the relationship between an entity of some kind, and - * a value (term). For example, <key> > "start" or "colname1" = "somevalue". - * - */ - public class Relation - { - private final ColumnIdentifier entity; - private final Type relationType; - private final Term.Raw value; - private final List<Term.Raw> inValues; - public final boolean onToken; + public abstract class Relation { - // Will be null unless for tuple notations (#4851) - public final ColumnIdentifier previousInTuple; + protected Type relationType; public static enum Type { - EQ, LT, LTE, GTE, GT, IN; + EQ, LT, LTE, GTE, GT, IN, CONTAINS, CONTAINS_KEY; + + public boolean allowsIndexQuery() + { + switch (this) + { + case EQ: + case CONTAINS: + case CONTAINS_KEY: + return true; + default: + return false; + } + } - } - - private Relation(ColumnIdentifier entity, Type type, Term.Raw value, List<Term.Raw> inValues, boolean onToken, ColumnIdentifier previousInTuple) - { - this.entity = entity; - this.relationType = type; - this.value = value; - this.inValues = inValues; - this.onToken = onToken; - this.previousInTuple = previousInTuple; - } - /** - * Creates a new relation. - * - * @param entity the kind of relation this is; what the term is being compared to. - * @param type the type that describes how this entity relates to the value. - * @param value the value being compared. - */ - public Relation(ColumnIdentifier entity, Type type, Term.Raw value) - { - this(entity, type, value, null, false, null); - } - - public Relation(ColumnIdentifier entity, Type type, Term.Raw value, boolean onToken) - { - this(entity, type, value, null, onToken, null); - } - - public Relation(ColumnIdentifier entity, Type type, Term.Raw value, ColumnIdentifier previousInTuple) - { - this(entity, type, value, null, false, previousInTuple); - } - - public static Relation createInRelation(ColumnIdentifier entity) - { - return new Relation(entity, Type.IN, null, new ArrayList<Term.Raw>(), false, null); + @Override + public String toString() + { + switch (this) + { + case EQ: + return "="; + case LT: + return "<"; + case LTE: + return "<="; + case GT: + return ">"; + case GTE: + return ">="; - case IN: - return "IN"; + default: + return this.name(); + } + } } public Type operator() http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/Term.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Term.java index de866e1,96b4b71..e5206c8 --- a/src/java/org/apache/cassandra/cql3/Term.java +++ b/src/java/org/apache/cassandra/cql3/Term.java @@@ -88,9 -88,14 +88,14 @@@ public interface Ter * case this RawTerm describe a list index or a map key, etc... * @return the prepared term. */ - public Term prepare(ColumnSpecification receiver) throws InvalidRequestException; + public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException; } + public interface MultiColumnRaw extends Raw + { - public Term prepare(List<? extends ColumnSpecification> receiver) throws InvalidRequestException; ++ public Term prepare(String keyspace, List<? extends ColumnSpecification> receiver) throws InvalidRequestException; + } + /** * A terminal term, one that can be reduced to a byte buffer directly. * http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/Tuples.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/Tuples.java index 0000000,9e86912..3aabbd3 mode 000000,100644..100644 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ b/src/java/org/apache/cassandra/cql3/Tuples.java @@@ -1,0 -1,349 +1,349 @@@ + /* + * 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.db.marshal.AbstractType; + import org.apache.cassandra.db.marshal.CollectionType; + import org.apache.cassandra.db.marshal.ListType; + import org.apache.cassandra.db.marshal.TupleType; + import org.apache.cassandra.exceptions.InvalidRequestException; + import org.apache.cassandra.serializers.MarshalException; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import java.nio.ByteBuffer; + import java.util.*; + + /** + * Static helper methods and classes for tuples. + */ + public class Tuples + { + private static final Logger logger = LoggerFactory.getLogger(Tuples.class); + + /** + * A raw, literal tuple. When prepared, this will become a Tuples.Value or Tuples.DelayedValue, depending + * on whether the tuple holds NonTerminals. + */ + public static class Literal implements Term.MultiColumnRaw + { + private final List<Term.Raw> elements; + + public Literal(List<Term.Raw> elements) + { + this.elements = elements; + } + - public Term prepare(List<? extends ColumnSpecification> receivers) throws InvalidRequestException ++ public Term prepare(String keyspace, List<? extends ColumnSpecification> receivers) throws InvalidRequestException + { + if (elements.size() != receivers.size()) + throw new InvalidRequestException(String.format("Expected %d elements in value tuple, but got %d: %s", receivers.size(), elements.size(), this)); + + List<Term> values = new ArrayList<>(elements.size()); + boolean allTerminal = true; + for (int i = 0; i < elements.size(); i++) + { - Term t = elements.get(i).prepare(receivers.get(i)); ++ Term t = elements.get(i).prepare(keyspace, receivers.get(i)); + if (t instanceof Term.NonTerminal) + allTerminal = false; + + values.add(t); + } + DelayedValue value = new DelayedValue(values); - return allTerminal ? value.bind(Collections.<ByteBuffer>emptyList()) : value; ++ return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; + } + - public Term prepare(ColumnSpecification receiver) ++ public Term prepare(String keyspace, ColumnSpecification receiver) + { + throw new AssertionError("Tuples.Literal instances require a list of receivers for prepare()"); + } + - public boolean isAssignableTo(ColumnSpecification receiver) ++ public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + { + // tuples shouldn't be assignable to anything right now + return false; + } + + @Override + public String toString() + { + return tupleToString(elements); + } + } + + /** + * A tuple of terminal values (e.g (123, 'abc')). + */ + public static class Value extends Term.MultiItemTerminal + { + public final ByteBuffer[] elements; + + public Value(ByteBuffer[] elements) + { + this.elements = elements; + } + + public static Value fromSerialized(ByteBuffer bytes, TupleType type) + { + return new Value(type.split(bytes)); + } + - public ByteBuffer get() ++ public ByteBuffer get(QueryOptions options) + { + throw new UnsupportedOperationException(); + } + + public List<ByteBuffer> getElements() + { + return Arrays.asList(elements); + } + } + + /** + * Similar to Value, but contains at least one NonTerminal, such as a non-pure functions or bind marker. + */ + public static class DelayedValue extends Term.NonTerminal + { + public final List<Term> elements; + + public DelayedValue(List<Term> elements) + { + this.elements = elements; + } + + public boolean containsBindMarker() + { + for (Term term : elements) + if (term.containsBindMarker()) + return true; + + return false; + } + + public void collectMarkerSpecification(VariableSpecifications boundNames) + { + for (Term term : elements) + term.collectMarkerSpecification(boundNames); + } + - public Value bind(List<ByteBuffer> values) throws InvalidRequestException ++ public Value bind(QueryOptions options) throws InvalidRequestException + { + ByteBuffer[] buffers = new ByteBuffer[elements.size()]; + for (int i=0; i < elements.size(); i++) + { - ByteBuffer bytes = elements.get(i).bindAndGet(values); ++ ByteBuffer bytes = elements.get(i).bindAndGet(options); + if (bytes == null) + throw new InvalidRequestException("Tuples may not contain null values"); + - buffers[i] = elements.get(i).bindAndGet(values); ++ buffers[i] = elements.get(i).bindAndGet(options); + } + return new Value(buffers); + } + + @Override + public String toString() + { + return tupleToString(elements); + } + } + + /** + * A terminal value for a list of IN values that are tuples. For example: "SELECT ... WHERE (a, b, c) IN ?" + * This is similar to Lists.Value, but allows us to keep components of the tuples in the list separate. + */ + public static class InValue extends Term.Terminal + { + List<List<ByteBuffer>> elements; + + public InValue(List<List<ByteBuffer>> items) + { + this.elements = items; + } + + public static InValue fromSerialized(ByteBuffer value, ListType type) throws InvalidRequestException + { + try + { + // Collections have this small hack that validate cannot be called on a serialized object, + // but compose does the validation (so we're fine). + List<?> l = (List<?>)type.compose(value); + + assert type.elements instanceof TupleType; + TupleType tupleType = (TupleType) type.elements; + + // type.split(bytes) + List<List<ByteBuffer>> elements = new ArrayList<>(l.size()); + for (Object element : l) + elements.add(Arrays.asList(tupleType.split(type.elements.decompose(element)))); + return new InValue(elements); + } + catch (MarshalException e) + { + throw new InvalidRequestException(e.getMessage()); + } + } + - public ByteBuffer get() ++ public ByteBuffer get(QueryOptions options) + { + throw new UnsupportedOperationException(); + } + + public List<List<ByteBuffer>> getSplitValues() + { + return elements; + } + } + + /** + * A raw placeholder for a tuple of values for different multiple columns, each of which may have a different type. + * For example, "SELECT ... WHERE (col1, col2) > ?". + */ + public static class Raw extends AbstractMarker.Raw implements Term.MultiColumnRaw + { + public Raw(int bindIndex) + { + super(bindIndex); + } + + private static ColumnSpecification makeReceiver(List<? extends ColumnSpecification> receivers) throws InvalidRequestException + { + List<AbstractType<?>> types = new ArrayList<>(receivers.size()); + StringBuilder inName = new StringBuilder("("); + for (int i = 0; i < receivers.size(); i++) + { + ColumnSpecification receiver = receivers.get(i); + inName.append(receiver.name); + if (i < receivers.size() - 1) + inName.append(","); + types.add(receiver.type); + } + inName.append(')'); + + ColumnIdentifier identifier = new ColumnIdentifier(inName.toString(), true); + TupleType type = new TupleType(types); + return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, type); + } + - public AbstractMarker prepare(List<? extends ColumnSpecification> receivers) throws InvalidRequestException ++ public AbstractMarker prepare(String keyspace, List<? extends ColumnSpecification> receivers) throws InvalidRequestException + { + return new Tuples.Marker(bindIndex, makeReceiver(receivers)); + } + + @Override - public AbstractMarker prepare(ColumnSpecification receiver) ++ public AbstractMarker prepare(String keyspace, ColumnSpecification receiver) + { + throw new AssertionError("Tuples.Raw.prepare() requires a list of receivers"); + } + } + + /** + * A raw marker for an IN list of tuples, like "SELECT ... WHERE (a, b, c) IN ?" + */ + public static class INRaw extends AbstractMarker.Raw + { + public INRaw(int bindIndex) + { + super(bindIndex); + } + + private static ColumnSpecification makeInReceiver(List<? extends ColumnSpecification> receivers) throws InvalidRequestException + { + List<AbstractType<?>> types = new ArrayList<>(receivers.size()); + StringBuilder inName = new StringBuilder("in("); + for (int i = 0; i < receivers.size(); i++) + { + ColumnSpecification receiver = receivers.get(i); + inName.append(receiver.name); + if (i < receivers.size() - 1) + inName.append(","); + + if (receiver.type instanceof CollectionType) + throw new InvalidRequestException("Collection columns do not support IN relations"); + types.add(receiver.type); + } + inName.append(')'); + + ColumnIdentifier identifier = new ColumnIdentifier(inName.toString(), true); + TupleType type = new TupleType(types); + return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, ListType.getInstance(type)); + } + - public AbstractMarker prepare(List<? extends ColumnSpecification> receivers) throws InvalidRequestException ++ public AbstractMarker prepare(String keyspace, List<? extends ColumnSpecification> receivers) throws InvalidRequestException + { + return new InMarker(bindIndex, makeInReceiver(receivers)); + } + + @Override - public AbstractMarker prepare(ColumnSpecification receiver) ++ public AbstractMarker prepare(String keyspace, ColumnSpecification receiver) + { + throw new AssertionError("Tuples.INRaw.prepare() requires a list of receivers"); + } + } + + /** + * Represents a marker for a single tuple, like "SELECT ... WHERE (a, b, c) > ?" + */ + public static class Marker extends AbstractMarker + { + public Marker(int bindIndex, ColumnSpecification receiver) + { + super(bindIndex, receiver); + } + - public Value bind(List<ByteBuffer> values) throws InvalidRequestException ++ public Value bind(QueryOptions options) throws InvalidRequestException + { - ByteBuffer value = values.get(bindIndex); ++ ByteBuffer value = options.getValues().get(bindIndex); + if (value == null) + return null; + + return value == null ? null : Value.fromSerialized(value, (TupleType)receiver.type); + } + } + + /** + * Represents a marker for a set of IN values that are tuples, like "SELECT ... WHERE (a, b, c) IN ?" + */ + public static class InMarker extends AbstractMarker + { + protected InMarker(int bindIndex, ColumnSpecification receiver) + { + super(bindIndex, receiver); + assert receiver.type instanceof ListType; + } + - public InValue bind(List<ByteBuffer> values) throws InvalidRequestException ++ public InValue bind(QueryOptions options) throws InvalidRequestException + { - ByteBuffer value = values.get(bindIndex); ++ ByteBuffer value = options.getValues().get(bindIndex); + return value == null ? null : InValue.fromSerialized(value, (ListType)receiver.type); + } + } + + public static String tupleToString(List<?> items) + { + + StringBuilder sb = new StringBuilder("("); + for (int i = 0; i < items.size(); i++) + { + sb.append(items.get(i)); + if (i < items.size() - 1) + sb.append(", "); + } + sb.append(')'); + return sb.toString(); + } + } http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index ad88eaf,11aa0b1..621006b --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@@ -29,10 -31,10 +29,12 @@@ import org.apache.cassandra.config.CFMe import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.*; import org.apache.cassandra.db.*; +import org.apache.cassandra.db.composites.CBuilder; +import org.apache.cassandra.db.composites.Composite; import org.apache.cassandra.db.filter.ColumnSlice; import org.apache.cassandra.db.filter.SliceQueryFilter; + import org.apache.cassandra.db.marshal.CompositeType; + import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.BooleanType; import org.apache.cassandra.exceptions.*; import org.apache.cassandra.service.ClientState; @@@ -218,46 -237,54 +220,53 @@@ public abstract class ModificationState return ifExists; } - private void addKeyValues(CFDefinition.Name name, Restriction values) throws InvalidRequestException + private void addKeyValues(ColumnDefinition def, Restriction values) throws InvalidRequestException { - if (name.kind == CFDefinition.Name.Kind.COLUMN_ALIAS) + if (def.kind == ColumnDefinition.Kind.CLUSTERING_COLUMN) hasNoClusteringColumns = false; - if (processedKeys.put(name.name, values) != null) - throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name.name)); + if (processedKeys.put(def.name, values) != null) + throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", def.name)); } - public void addKeyValue(CFDefinition.Name name, Term value) throws InvalidRequestException + public void addKeyValue(ColumnDefinition def, Term value) throws InvalidRequestException { - addKeyValues(def, new Restriction.EQ(value, false)); - addKeyValues(name, new SingleColumnRestriction.EQ(value, false)); ++ addKeyValues(def, new SingleColumnRestriction.EQ(value, false)); } public void processWhereClause(List<Relation> whereClause, VariableSpecifications names) throws InvalidRequestException { - for (Relation rel : whereClause) - CFDefinition cfDef = cfm.getCfDef(); + for (Relation relation : whereClause) { - if (!(relation instanceof SingleColumnRelation)) ++ if (relation.isMultiColumn()) + { + throw new InvalidRequestException( + String.format("Multi-column relations cannot be used in WHERE clauses for modification statements: %s", relation)); + } + SingleColumnRelation rel = (SingleColumnRelation) relation; + - CFDefinition.Name name = cfDef.get(rel.getEntity()); - if (name == null) + ColumnDefinition def = cfm.getColumnDefinition(rel.getEntity()); + if (def == null) throw new InvalidRequestException(String.format("Unknown key identifier %s", rel.getEntity())); - switch (name.kind) + switch (def.kind) { - case KEY_ALIAS: - case COLUMN_ALIAS: + case PARTITION_KEY: + case CLUSTERING_COLUMN: Restriction restriction; if (rel.operator() == Relation.Type.EQ) { - Term t = rel.getValue().prepare(name); + Term t = rel.getValue().prepare(keyspace(), def); t.collectMarkerSpecification(names); - restriction = new Restriction.EQ(t, false); + restriction = new SingleColumnRestriction.EQ(t, false); } - else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN) + else if (def.kind == ColumnDefinition.Kind.PARTITION_KEY && rel.operator() == Relation.Type.IN) { if (rel.getValue() != null) { - Term t = rel.getValue().prepare(name); + Term t = rel.getValue().prepare(keyspace(), def); t.collectMarkerSpecification(names); - restriction = Restriction.IN.create(t); + restriction = new SingleColumnRestriction.InWithMarker((Lists.Marker)t); } else { @@@ -638,9 -689,9 +647,8 @@@ /** * Convert statement into a list of mutations to apply on the server * - * @param variables value for prepared statement markers + * @param options value for prepared statement markers * @param local if true, any requests (for collections) performed by getMutation should be done locally only. -- * @param cl the consistency to use for the potential reads involved in generating the mutations (for lists set/delete operations) * @param now the current timestamp in microseconds to use if no timestamp is user provided. * * @return list of the mutations http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java index 0000000,f643684..96cb905 mode 000000,100644..100644 --- a/src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/statements/MultiColumnRestriction.java @@@ -1,0 -1,135 +1,137 @@@ + /* + * 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.AbstractMarker; ++import org.apache.cassandra.cql3.QueryOptions; + import org.apache.cassandra.cql3.Term; + import org.apache.cassandra.cql3.Tuples; + import org.apache.cassandra.exceptions.InvalidRequestException; + + import java.nio.ByteBuffer; + import java.util.ArrayList; + import java.util.List; + + public interface MultiColumnRestriction extends Restriction + { + public static class EQ extends SingleColumnRestriction.EQ implements MultiColumnRestriction + { + public EQ(Term value, boolean onToken) + { + super(value, onToken); + } + + public boolean isMultiColumn() + { + return true; + } + - public List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException ++ public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException + { - Tuples.Value t = (Tuples.Value)value.bind(variables); ++ Tuples.Value t = (Tuples.Value)value.bind(options); + return t.getElements(); + } + } + + public interface IN extends MultiColumnRestriction + { - public List<List<ByteBuffer>> splitValues(List<ByteBuffer> variables) throws InvalidRequestException; ++ public 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 SingleColumnRestriction.InWithValues implements MultiColumnRestriction.IN + { + public InWithValues(List<Term> values) + { + super(values); + } + + public boolean isMultiColumn() + { + return true; + } + - public List<List<ByteBuffer>> splitValues(List<ByteBuffer> variables) throws InvalidRequestException ++ public 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(variables); ++ 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 SingleColumnRestriction.InWithMarker implements MultiColumnRestriction.IN + { + public InWithMarker(AbstractMarker marker) + { + super(marker); + } + + public boolean isMultiColumn() + { + return true; + } + - public List<List<ByteBuffer>> splitValues(List<ByteBuffer> variables) throws InvalidRequestException ++ public List<List<ByteBuffer>> splitValues(QueryOptions options) throws InvalidRequestException + { - Tuples.InValue inValue = ((Tuples.InMarker) marker).bind(variables); ++ Tuples.InMarker inMarker = (Tuples.InMarker)marker; ++ Tuples.InValue inValue = inMarker.bind(options); + if (inValue == null) + throw new InvalidRequestException("Invalid null value for IN restriction"); + return inValue.getSplitValues(); + } + } + + public static class Slice extends SingleColumnRestriction.Slice implements MultiColumnRestriction + { + public Slice(boolean onToken) + { + super(onToken); + } + + public boolean isMultiColumn() + { + return true; + } + - public ByteBuffer bound(Bound b, List<ByteBuffer> variables) throws InvalidRequestException ++ public ByteBuffer bound(Bound b, QueryOptions options) throws InvalidRequestException + { + throw new UnsupportedOperationException("Multicolumn slice restrictions do not support bound()"); + } + + /** + * Similar to bounds(), but returns one ByteBuffer per-component in the bound instead of a single + * ByteBuffer to represent the entire bound. + */ - public List<ByteBuffer> componentBounds(Bound b, List<ByteBuffer> variables) throws InvalidRequestException ++ public List<ByteBuffer> componentBounds(Bound b, QueryOptions options) throws InvalidRequestException + { - Tuples.Value value = (Tuples.Value)bounds[b.idx].bind(variables); ++ Tuples.Value value = (Tuples.Value)bounds[b.idx].bind(options); + return value.getElements(); + } + } + } http://git-wip-us.apache.org/repos/asf/cassandra/blob/bf521900/src/java/org/apache/cassandra/cql3/statements/Restriction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/Restriction.java index 4fd02c1,3d33bde..c529a38 --- a/src/java/org/apache/cassandra/cql3/statements/Restriction.java +++ b/src/java/org/apache/cassandra/cql3/statements/Restriction.java @@@ -18,14 -18,10 +18,10 @@@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; - import java.util.ArrayList; - import java.util.Collections; import java.util.List; - import com.google.common.base.Objects; - import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.thrift.IndexOperator; +import org.apache.cassandra.db.IndexExpression; import org.apache.cassandra.cql3.*; /** @@@ -39,398 -35,34 +35,35 @@@ public interface Restrictio public boolean isSlice(); public boolean isEQ(); public boolean isIN(); + public boolean isContains(); + public boolean isMultiColumn(); - // Only supported for EQ and IN, but it's convenient to have here - public List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException; + // Not supported by Slice, but it's convenient to have here + public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException; - public static class EQ implements Restriction - { - private final Term value; - private final boolean onToken; - - public EQ(Term value, boolean onToken) - { - this.value = value; - this.onToken = onToken; - } - - public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException - { - 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 static interface EQ extends Restriction {} - public boolean isOnToken() - { - return onToken; - } - - @Override - public String toString() - { - return String.format("EQ(%s)%s", value, onToken ? "*" : ""); - } - } - - public static abstract class IN implements Restriction + public static interface IN extends Restriction { - public static IN create(List<Term> values) - { - return new WithValues(values); - } - - public static IN create(Term value) - { - assert value instanceof Lists.Marker; // we shouldn't have got there otherwise - return new WithMarker((Lists.Marker)value); - } - - public boolean isSlice() - { - return false; - } - - public boolean isEQ() - { - return false; - } - - public boolean isContains() - { - return false; - } - - public boolean isIN() - { - return true; - } - - // Used when we need to know if it's a IN with just one value before we have - // the bind variables. This is ugly and only there for backward compatiblity - // because we used to treate IN with 1 value like an EQ and need to preserve - // this behavior. - public abstract boolean canHaveOnlyOneValue(); - - public boolean isOnToken() - { - return false; - } - - private static class WithValues extends IN - { - private final List<Term> values; - - private WithValues(List<Term> values) - { - this.values = values; - } - - public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException - { - List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(values.size()); - for (Term value : values) - buffers.add(value.bindAndGet(options)); - return buffers; - } - - public boolean canHaveOnlyOneValue() - { - return values.size() == 1; - } - - @Override - public String toString() - { - return String.format("IN(%s)", values); - } - } - - private static class WithMarker extends IN - { - private final Lists.Marker marker; - - private WithMarker(Lists.Marker marker) - { - this.marker = marker; - } - - public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException - { - Lists.Value lval = marker.bind(options); - if (lval == null) - throw new InvalidRequestException("Invalid null value for IN restriction"); - return lval.elements; - } - - public boolean canHaveOnlyOneValue() - { - return false; - } - - @Override - public String toString() - { - return "IN ?"; - } - } + public boolean canHaveOnlyOneValue(); } - public static class Slice implements Restriction + public static interface Slice extends Restriction { - private final Term[] bounds; - private final boolean[] boundInclusive; - private final boolean onToken; - - // The name of the column that was preceding this one if the tuple notation of #4851 was used - // (note: if it is set for both bound, we'll validate both have the same previous value, but we - // still need to distinguish if it's set or not for both bound) - private final ColumnIdentifier[] previous; - - public Slice(boolean onToken) - { - this.bounds = new Term[2]; - this.boundInclusive = new boolean[2]; - this.previous = new ColumnIdentifier[2]; - this.onToken = onToken; - } - - public boolean isSlice() - { - return true; - } - - public boolean isEQ() - { - return false; - } - - public boolean isIN() - { - return false; - } - - public boolean isContains() - { - return false; - } - - public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException - { - throw new UnsupportedOperationException(); - } - - public boolean isOnToken() - { - return onToken; - } - - public boolean hasBound(Bound b) - { - return bounds[b.idx] != null; - } - - public ByteBuffer bound(Bound b, QueryOptions options) throws InvalidRequestException - { - return bounds[b.idx].bindAndGet(options); - } - - 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 IndexExpression.Operator getIndexOperator(Bound b) - { - switch (b) - { - case START: - return boundInclusive[b.idx] ? IndexExpression.Operator.GTE : IndexExpression.Operator.GT; - case END: - return boundInclusive[b.idx] ? IndexExpression.Operator.LTE : IndexExpression.Operator.LT; - } - throw new AssertionError(); - } - - public void setBound(ColumnIdentifier name, Relation.Type type, Term t, ColumnIdentifier previousName) 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("Invalid restrictions found on %s", name)); - - bounds[b.idx] = t; - boundInclusive[b.idx] = inclusive; - - // If a bound is part of a tuple notation (#4851), the other bound must either also be or must not be set at all, - // and this even if there is a 2ndary index (it's not supported by the 2ndary code). And it's easier to validate - // this here so we do. - Bound reverse = Bound.reverse(b); - if (hasBound(reverse) && !(Objects.equal(previousName, previous[reverse.idx]))) - throw new InvalidRequestException(String.format("Clustering column %s cannot be restricted both inside a tuple notation and outside it", name)); - - previous[b.idx] = previousName; - } - - public boolean isPartOfTuple() - { - return previous[Bound.START.idx] != null || previous[Bound.END.idx] != null; - } - - @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 implements Restriction - { - 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 List<ByteBuffer> values(List<ByteBuffer> variables) throws InvalidRequestException; ++ public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException; - public boolean isEQ() - { - return false; - } + /** Returns true if the start or end bound (depending on the argument) is set, false otherwise */ + public boolean hasBound(Bound b); - public boolean isIN() - { - return false; - } - public ByteBuffer bound(Bound b, List<ByteBuffer> variables) throws InvalidRequestException; ++ public ByteBuffer bound(Bound b, QueryOptions options) throws InvalidRequestException; - public boolean isContains() - { - return true; - } + /** Returns true if the start or end bound (depending on the argument) is inclusive, false otherwise */ + public boolean isInclusive(Bound b); - public boolean isOnToken() - { - return false; - } + public Relation.Type getRelation(Bound eocBound, Bound inclusiveBound); - public IndexOperator getIndexOperator(Bound b); ++ public IndexExpression.Operator getIndexOperator(Bound b); - @Override - public String toString() - { - return String.format("CONTAINS(values=%s, keys=%s)", values, keys); - } - public void setBound(Relation.Type type, Term t) throws InvalidRequestException; ++ public void setBound(ColumnIdentifier name, Relation.Type type, Term t) throws InvalidRequestException; } }