diff --git a/CHANGES.txt b/CHANGES.txt index eefc85784f..728a5d4af2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Simplify the bind marker and Term logic (CASSANDRA-18813) * Limit cassandra startup to supported JDKs, allow higher JDKs by setting CASSANDRA_JDK_UNSUPPORTED (CASSANDRA-18688) * Standardize nodetool tablestats formatting of data units (CASSANDRA-19104) * Make nodetool tablestats use number of significant digits for time and average values consistently (CASSANDRA-19015) diff --git a/src/antlr/Cql.g b/src/antlr/Cql.g index 0eb2ac5848..b25f3944d6 100644 --- a/src/antlr/Cql.g +++ b/src/antlr/Cql.g @@ -44,6 +44,7 @@ import Parser,Lexer; import org.apache.cassandra.cql3.selection.*; import org.apache.cassandra.cql3.statements.*; import org.apache.cassandra.cql3.statements.schema.*; + import org.apache.cassandra.cql3.terms.*; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.SyntaxException; diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index 434a869125..e33e61faa9 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -38,30 +38,16 @@ options { add("bitstring"); }}; - public AbstractMarker.Raw newBindVariables(ColumnIdentifier name) + public Marker.Raw newBindVariables(ColumnIdentifier name) { - AbstractMarker.Raw marker = new AbstractMarker.Raw(bindVariables.size()); + Marker.Raw marker = new Marker.Raw(bindVariables.size()); bindVariables.add(name); return marker; } - public AbstractMarker.INRaw newINBindVariables(ColumnIdentifier name) + public InMarker.Raw newINBindVariables(ColumnIdentifier name) { - AbstractMarker.INRaw marker = new AbstractMarker.INRaw(bindVariables.size()); - bindVariables.add(name); - return marker; - } - - public Tuples.Raw newTupleBindVariables(ColumnIdentifier name) - { - Tuples.Raw marker = new Tuples.Raw(bindVariables.size()); - bindVariables.add(name); - return marker; - } - - public Tuples.INRaw newTupleINBindVariables(ColumnIdentifier name) - { - Tuples.INRaw marker = new Tuples.INRaw(bindVariables.size()); + InMarker.Raw marker = new InMarker.Raw(bindVariables.size()); bindVariables.add(name); return marker; } @@ -113,7 +99,7 @@ options { if (!(entry.left instanceof Constants.Literal)) { String msg = "Invalid property name: " + entry.left; - if (entry.left instanceof AbstractMarker.Raw) + if (entry.left instanceof Marker.Raw) msg += " (bind variables are not supported in DDL queries)"; addRecognitionError(msg); break; @@ -121,7 +107,7 @@ options { if (!(entry.right instanceof Constants.Literal)) { String msg = "Invalid property value: " + entry.right + " for property: " + entry.left; - if (entry.right instanceof AbstractMarker.Raw) + if (entry.right instanceof Marker.Raw) msg += " (bind variables are not supported in DDL queries)"; addRecognitionError(msg); break; @@ -1785,9 +1771,9 @@ relation[WhereClause.Builder clauses] | ids=tupleOfIdentifiers ( K_IN ( '(' ')' - { $clauses.add(MultiColumnRelation.createInRelation(ids, new ArrayList())); } - | tupleInMarker=inMarkerForTuple /* (a, b, c) IN ? */ - { $clauses.add(MultiColumnRelation.createSingleMarkerInRelation(ids, tupleInMarker)); } + { $clauses.add(MultiColumnRelation.createInRelation(ids, Terms.Raw.of(Collections.emptyList()))); } + | tupleInMarker=inMarker /* (a, b, c) IN ? */ + { $clauses.add(MultiColumnRelation.createInRelation(ids, tupleInMarker)); } | literals=tupleOfTupleLiterals /* (a, b, c) IN ((1, 2, 3), (4, 5, 6), ...) */ { $clauses.add(MultiColumnRelation.createInRelation(ids, literals)); @@ -1809,7 +1795,7 @@ containsOperator returns [Operator o] : K_CONTAINS { o = Operator.CONTAINS; } (K_KEY { o = Operator.CONTAINS_KEY; })? ; -inMarker returns [AbstractMarker.INRaw marker] +inMarker returns [InMarker.Raw marker] : QMARK { $marker = newINBindVariables(null); } | ':' name=noncol_ident { $marker = newINBindVariables(name); } ; @@ -1819,29 +1805,27 @@ tupleOfIdentifiers returns [List ids] : '(' n1=cident { $ids.add(n1); } (',' ni=cident { $ids.add(ni); })* ')' ; -singleColumnInValues returns [List terms] - @init { $terms = new ArrayList(); } - : '(' ( t1 = term { $terms.add(t1); } (',' ti=term { $terms.add(ti); })* )? ')' +singleColumnInValues returns [Terms.Raw terms] + @init { List list = new ArrayList<>(); } + @after { $terms = Terms.Raw.of(list); } + : '(' ( t1 = term { list.add(t1); } (',' ti=term { list.add(ti); })* )? ')' ; -tupleOfTupleLiterals returns [List literals] - @init { $literals = new ArrayList<>(); } - : '(' t1=tupleLiteral { $literals.add(t1); } (',' ti=tupleLiteral { $literals.add(ti); })* ')' +tupleOfTupleLiterals returns [Terms.Raw literals] + @init { List list = new ArrayList<>(); } + @after { $literals = Terms.Raw.of(list); } + : '(' t1=tupleLiteral { list.add(t1); } (',' ti=tupleLiteral { list.add(ti); })* ')' ; -markerForTuple returns [Tuples.Raw marker] - : QMARK { $marker = newTupleBindVariables(null); } - | ':' name=noncol_ident { $marker = newTupleBindVariables(name); } +markerForTuple returns [Marker.Raw marker] + : QMARK { $marker = newBindVariables(null); } + | ':' name=noncol_ident { $marker = newBindVariables(name); } ; -tupleOfMarkersForTuples returns [List markers] - @init { $markers = new ArrayList(); } - : '(' m1=markerForTuple { $markers.add(m1); } (',' mi=markerForTuple { $markers.add(mi); })* ')' - ; - -inMarkerForTuple returns [Tuples.INRaw marker] - : QMARK { $marker = newTupleINBindVariables(null); } - | ':' name=noncol_ident { $marker = newTupleINBindVariables(name); } +tupleOfMarkersForTuples returns [Terms.Raw markers] + @init { List list = new ArrayList<>(); } + @after { $markers = Terms.Raw.of(list); } + : '(' m1=markerForTuple { list.add(m1); } (',' mi=markerForTuple { list.add(mi); })* ')' ; comparatorType returns [CQL3Type.Raw t] diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java index c3788dfc96..2bf2094e77 100644 --- a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java @@ -40,7 +40,6 @@ import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ConsistencyLevel; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.InetAddressType; import org.apache.cassandra.db.marshal.ShortType; import org.apache.cassandra.db.marshal.TupleType; @@ -143,9 +142,9 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean Set cidrAsTuples = row.getFrozenSet("cidrs", tupleType); for (ByteBuffer cidrAsTuple : cidrAsTuples) { - ByteBuffer[] splits = tupleType.split(ByteBufferAccessor.instance, cidrAsTuple); - InetAddress ip = InetAddressType.instance.compose(splits[0]); - short netMask = ShortType.instance.compose(splits[1]); + List elements = tupleType.unpack(cidrAsTuple); + InetAddress ip = InetAddressType.instance.compose(elements.get(0)); + short netMask = ShortType.instance.compose(elements.get(1)); cidrs.add(Pair.create(ip, netMask)); } diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java deleted file mode 100644 index 19ce26c6ff..0000000000 --- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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 java.util.List; - -import org.apache.cassandra.cql3.functions.Function; -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.exceptions.InvalidRequestException; - -/** - * A single bind marker. - */ -public abstract class AbstractMarker extends Term.NonTerminal -{ - protected final int bindIndex; - protected final ColumnSpecification receiver; - - protected AbstractMarker(int bindIndex, ColumnSpecification receiver) - { - this.bindIndex = bindIndex; - this.receiver = receiver; - } - - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - boundNames.add(bindIndex, receiver); - } - - public boolean containsBindMarker() - { - return true; - } - - public void addFunctionsTo(List functions) - { - } - - /** - * A parsed, but non prepared, bind marker. - */ - public static class Raw extends Term.Raw - { - protected final int bindIndex; - - public Raw(int bindIndex) - { - this.bindIndex = bindIndex; - } - - public NonTerminal prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException - { - if (receiver.type.isCollection()) - { - switch (((CollectionType) receiver.type).kind) - { - case LIST: - return new Lists.Marker(bindIndex, receiver); - case SET: - return new Sets.Marker(bindIndex, receiver); - case MAP: - return new Maps.Marker(bindIndex, receiver); - default: - throw new AssertionError(); - } - } - else if (receiver.type.isUDT()) - { - return new UserTypes.Marker(bindIndex, receiver); - } - - return new Constants.Marker(bindIndex, receiver); - } - - @Override - public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) - { - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - } - - public AbstractType getExactTypeIfKnown(String keyspace) - { - return null; - } - - @Override - public String getText() - { - return "?"; - } - } - - /** A MultiColumnRaw version of AbstractMarker.Raw */ - public static abstract class MultiColumnRaw extends Term.MultiColumnRaw - { - protected final int bindIndex; - - public MultiColumnRaw(int bindIndex) - { - this.bindIndex = bindIndex; - } - - public NonTerminal prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException - { - throw new AssertionError("MultiColumnRaw..prepare() requires a list of receivers"); - } - - public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) - { - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - } - - @Override - public String getText() - { - return "?"; - } - } - - /** - * A raw placeholder for multiple values of the same type for a single column. - * For example, "SELECT ... WHERE user_id IN ?'. - * - * Because a single type is used, a List is used to represent the values. - */ - public static final class INRaw extends Raw - { - public INRaw(int bindIndex) - { - super(bindIndex); - } - - private static ColumnSpecification makeInReceiver(ColumnSpecification receiver) - { - ColumnIdentifier inName = new ColumnIdentifier("in(" + receiver.name + ")", true); - return new ColumnSpecification(receiver.ksName, receiver.cfName, inName, ListType.getInstance(receiver.type, false)); - } - - @Override - public Lists.Marker prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException - { - return new Lists.Marker(bindIndex, makeInReceiver(receiver)); - } - } -} diff --git a/src/java/org/apache/cassandra/cql3/Attributes.java b/src/java/org/apache/cassandra/cql3/Attributes.java index 91485f471b..492c23672a 100644 --- a/src/java/org/apache/cassandra/cql3/Attributes.java +++ b/src/java/org/apache/cassandra/cql3/Attributes.java @@ -21,6 +21,7 @@ import java.nio.ByteBuffer; import java.util.List; import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.ExpirationDateOverflowHandling; import org.apache.cassandra.db.LivenessInfo; import org.apache.cassandra.db.marshal.Int32Type; diff --git a/src/java/org/apache/cassandra/cql3/CQL3Type.java b/src/java/org/apache/cassandra/cql3/CQL3Type.java index 304363ad1d..ecfbf77318 100644 --- a/src/java/org/apache/cassandra/cql3/CQL3Type.java +++ b/src/java/org/apache/cassandra/cql3/CQL3Type.java @@ -25,6 +25,7 @@ import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.db.marshal.CollectionType.Kind; @@ -61,7 +62,7 @@ public interface CQL3Type return false; } - public AbstractType getType(); + AbstractType getType(); /** * Generates CQL literal from a binary value of this type. @@ -83,10 +84,10 @@ public interface CQL3Type */ default ByteBuffer fromCQLLiteral(String keyspaceName, String literal) { - return Terms.asBytes(keyspaceName, literal, getType()); + return Term.asBytes(keyspaceName, literal, getType()); } - public enum Native implements CQL3Type + enum Native implements CQL3Type { ASCII (AsciiType.instance), BIGINT (LongType.instance), @@ -142,7 +143,7 @@ public interface CQL3Type } } - public static class Custom implements CQL3Type + class Custom implements CQL3Type { private final AbstractType type; @@ -191,7 +192,7 @@ public interface CQL3Type } } - public static class Collection implements CQL3Type + class Collection implements CQL3Type { private final CollectionType type; @@ -322,9 +323,9 @@ public interface CQL3Type } } - public static class UserDefined implements CQL3Type + class UserDefined implements CQL3Type { - // Keeping this separatly from type just to simplify toString() + // Keeping this separately from type just to simplify toString() private final String name; private final UserType type; @@ -419,7 +420,7 @@ public interface CQL3Type } } - public static class Tuple implements CQL3Type + class Tuple implements CQL3Type { private final TupleType type; @@ -522,7 +523,7 @@ public interface CQL3Type } } - public static class Vector implements CQL3Type + class Vector implements CQL3Type { private final VectorType type; @@ -554,7 +555,7 @@ public interface CQL3Type return "null"; buffer = buffer.duplicate(); CQL3Type elementType = type.elementType.asCQL3Type(); - List values = getType().split(buffer); + List values = getType().unpack(buffer); StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < values.size(); i++) @@ -593,7 +594,7 @@ public interface CQL3Type // For UserTypes, we need to know the current keyspace to resolve the // actual type used, so Raw is a "not yet prepared" CQL3Type. - public abstract class Raw + abstract class Raw { protected final boolean frozen; diff --git a/src/java/org/apache/cassandra/cql3/Json.java b/src/java/org/apache/cassandra/cql3/Json.java index d60eabdfed..7f4bc64f66 100644 --- a/src/java/org/apache/cassandra/cql3/Json.java +++ b/src/java/org/apache/cassandra/cql3/Json.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.InvalidRequestException; diff --git a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java index e74b9624d8..8ea4acc59a 100644 --- a/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java +++ b/src/java/org/apache/cassandra/cql3/MultiColumnRelation.java @@ -22,10 +22,11 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; +import org.apache.cassandra.cql3.terms.Tuples; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.cql3.Term.MultiColumnRaw; -import org.apache.cassandra.cql3.Term.Raw; import org.apache.cassandra.cql3.restrictions.MultiColumnRestriction; import org.apache.cassandra.cql3.restrictions.Restriction; import org.apache.cassandra.cql3.statements.Bound; @@ -49,22 +50,19 @@ public class MultiColumnRelation extends Relation { private final List entities; - /** A Tuples.Literal or Tuples.Raw marker */ - private final Term.MultiColumnRaw valuesOrMarker; + /** A literal or raw marker */ + private final Term.Raw value; - /** A list of Tuples.Literal or Tuples.Raw markers */ - private final List inValues; + /** A list of literals and/or raw markers or an IN raw marker */ + private final Terms.Raw inValues; - private final Tuples.INRaw inMarker; - - private MultiColumnRelation(List entities, Operator relationType, Term.MultiColumnRaw valuesOrMarker, List inValues, Tuples.INRaw inMarker) + private MultiColumnRelation(List entities, Operator relationType, Term.Raw value, Terms.Raw inValues) { this.entities = entities; - this.relationType = relationType; - this.valuesOrMarker = valuesOrMarker; + this.operator = relationType; + this.value = value; this.inValues = inValues; - this.inMarker = inMarker; } /** @@ -74,37 +72,25 @@ public class MultiColumnRelation extends Relation * } * @param entities the columns on the LHS of the relation * @param relationType the relation operator - * @param valuesOrMarker a Tuples.Literal instance or a Tuples.Raw marker + * @param value a literal or raw marker * @return a new MultiColumnRelation instance */ - public static MultiColumnRelation createNonInRelation(List entities, Operator relationType, Term.MultiColumnRaw valuesOrMarker) + public static MultiColumnRelation createNonInRelation(List entities, Operator relationType, Term.Raw value) { assert relationType != Operator.IN; - return new MultiColumnRelation(entities, relationType, valuesOrMarker, null, null); + return new MultiColumnRelation(entities, relationType, value, null); } /** - * Creates a multi-column IN relation with a list of IN values or markers. + * Creates a multi-column IN relation with either a marker for the IN values or a list of IN values or markers. * For example: "SELECT ... WHERE (a, b) IN ((0, 1), (2, 3))" * @param entities the columns on the LHS of the relation - * @param inValues a list of Tuples.Literal instances or a Tuples.Raw markers + * @param inValues the IN values as a {@code Terms.Raw} * @return a new MultiColumnRelation instance */ - public static MultiColumnRelation createInRelation(List entities, List inValues) + public static MultiColumnRelation createInRelation(List entities, Terms.Raw inValues) { - return new MultiColumnRelation(entities, Operator.IN, null, inValues, null); - } - - /** - * Creates a multi-column IN relation with a marker for the IN values. - * For example: "SELECT ... WHERE (a, b) IN ?" - * @param entities the columns on the LHS of the relation - * @param inMarker a single IN marker - * @return a new MultiColumnRelation instance - */ - public static MultiColumnRelation createSingleMarkerInRelation(List entities, Tuples.INRaw inMarker) - { - return new MultiColumnRelation(entities, Operator.IN, null, null, inMarker); + return new MultiColumnRelation(entities, Operator.IN, null, inValues); } public List getEntities() @@ -113,31 +99,25 @@ public class MultiColumnRelation extends Relation } /** - * For non-IN relations, returns the Tuples.Literal or Tuples.Raw marker for a single tuple. - * @return a Tuples.Literal for non-IN relations or Tuples.Raw marker for a single tuple. + * For non-IN relations, returns a literal or a raw marker for a single tuple. + * @return a literal or a raw marker for non-IN relations. */ - public Term.MultiColumnRaw getValue() + public Term.Raw getValue() { - return relationType == Operator.IN ? inMarker : valuesOrMarker; + return value; } - public List getInValues() + public Terms.Raw getInValues() { - assert relationType == Operator.IN; + assert operator == Operator.IN; return inValues; } - @Override - public boolean isMultiColumn() - { - return true; - } - @Override protected Restriction newEQRestriction(TableMetadata table, VariableSpecifications boundNames) { List receivers = receivers(table); - Term term = toTerm(receivers, getValue(), table.keyspace, boundNames); + Term term = toTerm(Tuples.makeReceiver(receivers), getValue(), table.keyspace, boundNames); return new MultiColumnRestriction.EQRestriction(receivers, term); } @@ -145,24 +125,19 @@ public class MultiColumnRelation extends Relation protected Restriction newINRestriction(TableMetadata table, VariableSpecifications boundNames) { List receivers = receivers(table); - List terms = toTerms(receivers, inValues, table.keyspace, boundNames); - if (terms == null) - { - Term term = toTerm(receivers, getValue(), table.keyspace, boundNames); - return new MultiColumnRestriction.InRestrictionWithMarker(receivers, (AbstractMarker) term); - } + Terms terms = toTerms(Tuples.makeReceiver(receivers), inValues, table.keyspace, boundNames); - if (terms.size() == 1) - return new MultiColumnRestriction.EQRestriction(receivers, terms.get(0)); + if (terms.containsSingleTerm()) + return new MultiColumnRestriction.EQRestriction(receivers, terms.asSingleTerm()); - return new MultiColumnRestriction.InRestrictionWithValues(receivers, terms); + return new MultiColumnRestriction.INRestriction(receivers, terms); } @Override protected Restriction newSliceRestriction(TableMetadata table, VariableSpecifications boundNames, Bound bound, boolean inclusive) { List receivers = receivers(table); - Term term = toTerm(receivers(table), getValue(), table.keyspace, boundNames); + Term term = toTerm(Tuples.makeReceiver(receivers), getValue(), table.keyspace, boundNames); return new MultiColumnRestriction.SliceRestriction(receivers, bound, inclusive, term); } @@ -185,17 +160,6 @@ public class MultiColumnRelation extends Relation throw invalidRequest("%s cannot be used for multi-column relations", operator()); } - @Override - protected Term toTerm(List receivers, - Raw raw, - String keyspace, - VariableSpecifications boundNames) throws InvalidRequestException - { - Term term = ((MultiColumnRaw) raw).prepare(keyspace, receivers); - term.collectMarkerSpecification(boundNames); - return term; - } - protected List receivers(TableMetadata table) throws InvalidRequestException { List names = new ArrayList<>(getEntities().size()); @@ -223,7 +187,7 @@ public class MultiColumnRelation extends Relation return this; List newEntities = entities.stream().map(e -> e.equals(from) ? to : e).collect(Collectors.toList()); - return new MultiColumnRelation(newEntities, operator(), valuesOrMarker, inValues, inMarker); + return new MultiColumnRelation(newEntities, operator(), value, inValues); } @Override @@ -233,20 +197,20 @@ public class MultiColumnRelation extends Relation if (isIN()) { return builder.append(" IN ") - .append(inMarker != null ? '?' : Tuples.tupleToString(inValues)) + .append(inValues.getText()) .toString(); } - return builder.append(" ") - .append(relationType) - .append(" ") - .append(valuesOrMarker) + return builder.append(' ') + .append(operator) + .append(' ') + .append(value) .toString(); } @Override public int hashCode() { - return Objects.hash(relationType, entities, valuesOrMarker, inValues, inMarker); + return Objects.hash(operator, entities, value, inValues); } @Override @@ -260,9 +224,8 @@ public class MultiColumnRelation extends Relation MultiColumnRelation mcr = (MultiColumnRelation) o; return Objects.equals(entities, mcr.entities) - && Objects.equals(relationType, mcr.relationType) - && Objects.equals(valuesOrMarker, mcr.valuesOrMarker) - && Objects.equals(inValues, mcr.inValues) - && Objects.equals(inMarker, mcr.inMarker); + && operator == mcr.operator + && Objects.equals(value, mcr.value) + && Objects.equals(inValues, mcr.inValues); } } diff --git a/src/java/org/apache/cassandra/cql3/Operation.java b/src/java/org/apache/cassandra/cql3/Operation.java index 51f5d1304e..7a7c0e8420 100644 --- a/src/java/org/apache/cassandra/cql3/Operation.java +++ b/src/java/org/apache/cassandra/cql3/Operation.java @@ -20,6 +20,12 @@ package org.apache.cassandra.cql3; import java.util.List; import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Lists; +import org.apache.cassandra.cql3.terms.Maps; +import org.apache.cassandra.cql3.terms.Sets; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.UserTypes; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.InvalidRequestException; diff --git a/src/java/org/apache/cassandra/cql3/Ordering.java b/src/java/org/apache/cassandra/cql3/Ordering.java index 12d87f1f99..0d8375a133 100644 --- a/src/java/org/apache/cassandra/cql3/Ordering.java +++ b/src/java/org/apache/cassandra/cql3/Ordering.java @@ -20,6 +20,7 @@ package org.apache.cassandra.cql3; import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction; import org.apache.cassandra.cql3.restrictions.SingleRestriction; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; diff --git a/src/java/org/apache/cassandra/cql3/QueryOptions.java b/src/java/org/apache/cassandra/cql3/QueryOptions.java index f69e73c3e8..d3093cbb8e 100644 --- a/src/java/org/apache/cassandra/cql3/QueryOptions.java +++ b/src/java/org/apache/cassandra/cql3/QueryOptions.java @@ -26,6 +26,7 @@ import io.netty.buffer.ByteBuf; import org.apache.cassandra.config.DataStorageSpec; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.marshal.UTF8Type; diff --git a/src/java/org/apache/cassandra/cql3/Relation.java b/src/java/org/apache/cassandra/cql3/Relation.java index e2c3533c76..20dae5854c 100644 --- a/src/java/org/apache/cassandra/cql3/Relation.java +++ b/src/java/org/apache/cassandra/cql3/Relation.java @@ -17,9 +17,8 @@ */ package org.apache.cassandra.cql3; -import java.util.ArrayList; -import java.util.List; - +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.cql3.restrictions.Restriction; import org.apache.cassandra.cql3.statements.Bound; @@ -29,11 +28,11 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq public abstract class Relation { - protected Operator relationType; + protected Operator operator; public Operator operator() { - return relationType; + return operator; } /** @@ -44,17 +43,7 @@ public abstract class Relation /** * Returns the list of raw IN values for this relation, or null if this is not an IN relation. */ - public abstract List getInValues(); - - /** - * Checks if this relation apply to multiple columns. - * - * @return true if this relation apply to multiple columns, false otherwise. - */ - public boolean isMultiColumn() - { - return false; - } + public abstract Terms.Raw getInValues(); /** * Checks if this relation is a token relation (e.g.
token(a) = token(1)
). @@ -73,7 +62,7 @@ public abstract class Relation */ public final boolean isContains() { - return relationType == Operator.CONTAINS; + return operator == Operator.CONTAINS; } /** @@ -83,7 +72,7 @@ public abstract class Relation */ public final boolean isContainsKey() { - return relationType == Operator.CONTAINS_KEY; + return operator == Operator.CONTAINS_KEY; } /** @@ -93,7 +82,7 @@ public abstract class Relation */ public final boolean isIN() { - return relationType == Operator.IN; + return operator == Operator.IN; } /** @@ -103,16 +92,16 @@ public abstract class Relation */ public final boolean isEQ() { - return relationType == Operator.EQ; + return operator == Operator.EQ; } public final boolean isLIKE() { - return relationType == Operator.LIKE_PREFIX - || relationType == Operator.LIKE_SUFFIX - || relationType == Operator.LIKE_CONTAINS - || relationType == Operator.LIKE_MATCHES - || relationType == Operator.LIKE; + return operator == Operator.LIKE_PREFIX + || operator == Operator.LIKE_SUFFIX + || operator == Operator.LIKE_CONTAINS + || operator == Operator.LIKE_MATCHES + || operator == Operator.LIKE; } /** @@ -122,10 +111,10 @@ public abstract class Relation */ public final boolean isSlice() { - return relationType == Operator.GT - || relationType == Operator.GTE - || relationType == Operator.LTE - || relationType == Operator.LT; + return operator == Operator.GT + || operator == Operator.GTE + || operator == Operator.LTE + || operator == Operator.LT; } /** @@ -138,7 +127,7 @@ public abstract class Relation */ public final Restriction toRestriction(TableMetadata table, VariableSpecifications boundNames) { - switch (relationType) + switch (operator) { case EQ: return newEQRestriction(table, boundNames); case LT: return newSliceRestriction(table, boundNames, Bound.END, false); @@ -154,7 +143,7 @@ public abstract class Relation case LIKE_CONTAINS: case LIKE_MATCHES: case LIKE: - return newLikeRestriction(table, boundNames, relationType); + return newLikeRestriction(table, boundNames, operator); case ANN: throw invalidRequest("ANN is only supported in ORDER BY"); default: throw invalidRequest("Unsupported \"!=\" relation: %s", this); @@ -213,7 +202,7 @@ public abstract class Relation /** * Converts the specified Raw into a Term. - * @param receivers the columns to which the values must be associated at + * @param receiver the column to which the values must be associated at * @param raw the raw term to convert * @param keyspace the keyspace name * @param boundNames the variables specification where to collect the bind variables @@ -221,14 +210,19 @@ public abstract class Relation * @return the Term corresponding to the specified Raw * @throws InvalidRequestException if the Raw term is not valid */ - protected abstract Term toTerm(List receivers, - Term.Raw raw, - String keyspace, - VariableSpecifications boundNames); + protected final Term toTerm(ColumnSpecification receiver, + Term.Raw raw, + String keyspace, + VariableSpecifications boundNames) + { + Term term = raw.prepare(keyspace, receiver); + term.collectMarkerSpecification(boundNames); + return term; + } /** - * Converts the specified Raw terms into a Terms. - * @param receivers the columns to which the values must be associated at + * Converts the specified Raw terms into a Terms. + * @param receiver the column to which the values must be associated at * @param raws the raw terms to convert * @param keyspace the keyspace name * @param boundNames the variables specification where to collect the bind variables @@ -236,18 +230,13 @@ public abstract class Relation * @return the Terms corresponding to the specified Raw terms * @throws InvalidRequestException if the Raw terms are not valid */ - protected final List toTerms(List receivers, - List raws, - String keyspace, - VariableSpecifications boundNames) + protected final Terms toTerms(ColumnSpecification receiver, + Terms.Raw raws, + String keyspace, + VariableSpecifications boundNames) { - if (raws == null) - return null; - - List terms = new ArrayList<>(raws.size()); - for (int i = 0, m = raws.size(); i < m; i++) - terms.add(toTerm(receivers, raws.get(i), keyspace, boundNames)); - + Terms terms = raws.prepare(keyspace, receiver); + terms.collectMarkerSpecification(boundNames); return terms; } diff --git a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java index cf1cb69066..57c16c31f8 100644 --- a/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java +++ b/src/java/org/apache/cassandra/cql3/SingleColumnRelation.java @@ -17,14 +17,15 @@ */ package org.apache.cassandra.cql3; -import java.util.Collections; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; + +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.cql3.Term.Raw; import org.apache.cassandra.cql3.restrictions.Restriction; import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction; import org.apache.cassandra.cql3.statements.Bound; @@ -38,52 +39,68 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; /** - * Relations encapsulate the relationship between an entity of some kind, and - * a value (term). For example, {@code > "start" or "colname1" = "somevalue"}. - * + * A filter on a column values. */ public final class SingleColumnRelation extends Relation { - private final ColumnIdentifier entity; + private final ColumnIdentifier column; private final Term.Raw mapKey; private final Term.Raw value; - private final List inValues; + private final Terms.Raw inValues; - private SingleColumnRelation(ColumnIdentifier entity, Term.Raw mapKey, Operator type, Term.Raw value, List inValues) + private SingleColumnRelation(ColumnIdentifier column, + @Nullable Term.Raw mapKey, + Operator operator, + @Nullable Term.Raw value, + @Nullable Terms.Raw inValues) { - this.entity = entity; + // If mapKey is not null the Operator should be EQ + assert mapKey == null || operator == Operator.EQ; + + this.column = column; this.mapKey = mapKey; - this.relationType = type; + this.operator = operator; this.value = value; this.inValues = inValues; - if (type == Operator.IS_NOT) - assert value == Constants.NULL_LITERAL; + assert operator != Operator.IS_NOT || value == Constants.NULL_LITERAL; } /** * Creates a new relation. * - * @param entity the kind of relation this is; what the term is being compared to. - * @param mapKey the key into the entity identifying the value 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. + * @param column the column that need to be filtered + * @param mapKey the map key used for filtering map column using key value + * @param operator the operator used to perform the filtering + * @param value the value to which the column values must be compared */ - public SingleColumnRelation(ColumnIdentifier entity, Term.Raw mapKey, Operator type, Term.Raw value) + public SingleColumnRelation(ColumnIdentifier column, Term.Raw mapKey, Operator operator, Term.Raw value) { - this(entity, mapKey, type, value, null); + this(column, mapKey, operator, value, null); } /** * 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. + * @param column the column that need to be filtered + * @param operator the operator used to perform the filtering + * @param value the value to which the column values must be compared */ - public SingleColumnRelation(ColumnIdentifier entity, Operator type, Term.Raw value) + public SingleColumnRelation(ColumnIdentifier column, Operator operator, Term.Raw value) { - this(entity, null, type, value); + this(column, null, operator, value); + } + + /** + * Creates a new relation. + * + * @param column the column that need to be filtered + * @param operator the operator used to perform the filtering + * @param inValues the IN value being compared. + */ + public SingleColumnRelation(ColumnIdentifier column, Operator operator, Terms.Raw inValues) + { + this(column, null, operator, null, inValues); } public Term.Raw getValue() @@ -91,53 +108,19 @@ public final class SingleColumnRelation extends Relation return value; } - public List getInValues() + public Terms.Raw getInValues() { return inValues; } - public static SingleColumnRelation createInRelation(ColumnIdentifier entity, List inValues) + public static SingleColumnRelation createInRelation(ColumnIdentifier column, Terms.Raw inValues) { - return new SingleColumnRelation(entity, null, Operator.IN, null, inValues); - } - - public ColumnIdentifier getEntity() - { - return entity; - } - - public Term.Raw getMapKey() - { - return mapKey; - } - - @Override - protected Term toTerm(List receivers, - Raw raw, - String keyspace, - VariableSpecifications boundNames) - throws InvalidRequestException - { - assert receivers.size() == 1; - - Term term = raw.prepare(keyspace, receivers.get(0)); - term.collectMarkerSpecification(boundNames); - return term; - } - - public SingleColumnRelation withNonStrictOperator() - { - switch (relationType) - { - case GT: return new SingleColumnRelation(entity, Operator.GTE, value); - case LT: return new SingleColumnRelation(entity, Operator.LTE, value); - default: return this; - } + return new SingleColumnRelation(column, null, Operator.IN, null, inValues); } public Relation renameIdentifier(ColumnIdentifier from, ColumnIdentifier to) { - return entity.equals(from) + return column.equals(from) ? new SingleColumnRelation(to, mapKey, operator(), value, inValues) : this; } @@ -145,20 +128,20 @@ public final class SingleColumnRelation extends Relation @Override public String toCQLString() { - String entityAsString = entity.toCQLString(); + String columnAsString = column.toCQLString(); if (mapKey != null) - entityAsString = String.format("%s[%s]", entityAsString, mapKey); + columnAsString = String.format("%s[%s]", columnAsString, mapKey); if (isIN()) - return String.format("%s IN %s", entityAsString, Tuples.tupleToString(inValues)); + return String.format("%s IN %s", columnAsString, inValues); - return String.format("%s %s %s", entityAsString, relationType, value); + return String.format("%s %s %s", columnAsString, operator, value); } @Override public int hashCode() { - return Objects.hash(relationType, entity, mapKey, value, inValues); + return Objects.hash(operator, column, mapKey, value, inValues); } @Override @@ -171,45 +154,45 @@ public final class SingleColumnRelation extends Relation return false; SingleColumnRelation scr = (SingleColumnRelation) o; - return Objects.equals(entity, scr.entity) - && Objects.equals(relationType, scr.relationType) - && Objects.equals(mapKey, scr.mapKey) - && Objects.equals(value, scr.value) - && Objects.equals(inValues, scr.inValues); + return Objects.equals(column, scr.column) + && operator == scr.operator + && Objects.equals(mapKey, scr.mapKey) + && Objects.equals(value, scr.value) + && Objects.equals(inValues, scr.inValues); } @Override protected Restriction newEQRestriction(TableMetadata table, VariableSpecifications boundNames) { - ColumnMetadata columnDef = table.getExistingColumn(entity); + ColumnMetadata column = table.getExistingColumn(this.column); + validateReceiver(column); if (mapKey == null) { - Term term = toTerm(toReceivers(columnDef), value, table.keyspace, boundNames); - return new SingleColumnRestriction.EQRestriction(columnDef, term); + Term term = toTerm(column, value, table.keyspace, boundNames); + return new SingleColumnRestriction.EQRestriction(column, term); } - List receivers = toReceivers(columnDef); - Term entryKey = toTerm(Collections.singletonList(receivers.get(0)), mapKey, table.keyspace, boundNames); - Term entryValue = toTerm(Collections.singletonList(receivers.get(1)), value, table.keyspace, boundNames); - return new SingleColumnRestriction.ContainsRestriction(columnDef, entryKey, entryValue); + + checkFalse(column.type instanceof ListType, "Indexes on list entries (%s[index] = value) are not supported.", column.name); + checkTrue(column.type instanceof MapType, "Column %s cannot be used as a map", column.name); + checkTrue(column.type.isMultiCell(), "Map-entry equality predicates on frozen map column %s are not supported", column.name); + + Term entryKey = toTerm(makeCollectionReceiver(column, true), mapKey, table.keyspace, boundNames); + Term entryValue = toTerm(makeCollectionReceiver(column, false), value, table.keyspace, boundNames); + return new SingleColumnRestriction.ContainsRestriction(column, entryKey, entryValue); } @Override protected Restriction newINRestriction(TableMetadata table, VariableSpecifications boundNames) { - ColumnMetadata columnDef = table.getExistingColumn(entity); - List receivers = toReceivers(columnDef); - List terms = toTerms(receivers, inValues, table.keyspace, boundNames); - if (terms == null) - { - Term term = toTerm(receivers, value, table.keyspace, boundNames); - return new SingleColumnRestriction.InRestrictionWithMarker(columnDef, (Lists.Marker) term); - } + ColumnMetadata column = table.getExistingColumn(this.column); + validateReceiver(column); + Terms terms = toTerms(column, inValues, table.keyspace, boundNames); - // An IN restrictions with only one element is the same than an EQ restriction - if (terms.size() == 1) - return new SingleColumnRestriction.EQRestriction(columnDef, terms.get(0)); + // An IN restriction with only one element is the same as an EQ restriction + if (terms.containsSingleTerm()) + return new SingleColumnRestriction.EQRestriction(column, terms.asSingleTerm()); - return new SingleColumnRestriction.InRestrictionWithValues(columnDef, terms); + return new SingleColumnRestriction.INRestriction(column, terms); } @Override @@ -218,7 +201,7 @@ public final class SingleColumnRelation extends Relation Bound bound, boolean inclusive) { - ColumnMetadata columnDef = table.getExistingColumn(entity); + ColumnMetadata columnDef = table.getExistingColumn(column); if (columnDef.type.referencesDuration()) { @@ -227,8 +210,9 @@ public final class SingleColumnRelation extends Relation checkFalse(columnDef.type.isUDT(), "Slice restrictions are not supported on UDTs containing durations"); throw invalidRequest("Slice restrictions are not supported on duration columns"); } + validateReceiver(columnDef); - Term term = toTerm(toReceivers(columnDef), value, table.keyspace, boundNames); + Term term = toTerm(columnDef, value, table.keyspace, boundNames); return new SingleColumnRestriction.SliceRestriction(columnDef, bound, inclusive, term); } @@ -237,16 +221,23 @@ public final class SingleColumnRelation extends Relation VariableSpecifications boundNames, boolean isKey) throws InvalidRequestException { - ColumnMetadata columnDef = table.getExistingColumn(entity); - Term term = toTerm(toReceivers(columnDef), value, table.keyspace, boundNames); - return new SingleColumnRestriction.ContainsRestriction(columnDef, term, isKey); + ColumnMetadata column = table.getExistingColumn(this.column); + + checkFalse(isContainsKey() && !(column.type instanceof MapType), "Cannot use CONTAINS KEY on non-map column %s", column.name); + checkFalse(isContains() && !(column.type.isCollection()), "Cannot use CONTAINS on non-collection column %s", column.name); + validateReceiver(column); + + ColumnSpecification receiver = makeCollectionReceiver(column, isKey); + + Term term = toTerm(receiver, value, table.keyspace, boundNames); + return new SingleColumnRestriction.ContainsRestriction(column, term, isKey); } @Override protected Restriction newIsNotRestriction(TableMetadata table, VariableSpecifications boundNames) throws InvalidRequestException { - ColumnMetadata columnDef = table.getExistingColumn(entity); + ColumnMetadata columnDef = table.getExistingColumn(column); // currently enforced by the grammar assert value == Constants.NULL_LITERAL : "Expected null literal for IS NOT relation: " + this.toString(); return new SingleColumnRestriction.IsNotNullRestriction(columnDef); @@ -255,65 +246,35 @@ public final class SingleColumnRelation extends Relation @Override protected Restriction newLikeRestriction(TableMetadata table, VariableSpecifications boundNames, Operator operator) { - if (mapKey != null) - throw invalidRequest("%s can't be used with collections.", operator()); + ColumnMetadata column = table.getExistingColumn(this.column); + validateReceiver(column); + Term term = toTerm(column, value, table.keyspace, boundNames); - ColumnMetadata columnDef = table.getExistingColumn(entity); - Term term = toTerm(toReceivers(columnDef), value, table.keyspace, boundNames); - - return new SingleColumnRestriction.LikeRestriction(columnDef, operator, term); + return new SingleColumnRestriction.LikeRestriction(column, operator, term); } /** - * Returns the receivers for this relation. - * @param columnDef the column definition - * @return the receivers for the specified relation. + * Validate the receiving column for this relation. + * @param receiver the column definition * @throws InvalidRequestException if the relation is invalid */ - private List toReceivers(ColumnMetadata columnDef) throws InvalidRequestException + private void validateReceiver(ColumnMetadata receiver) throws InvalidRequestException { - ColumnSpecification receiver = columnDef; - - checkFalse(isContainsKey() && !(receiver.type instanceof MapType), "Cannot use CONTAINS KEY on non-map column %s", receiver.name); - checkFalse(isContains() && !(receiver.type.isCollection()), "Cannot use CONTAINS on non-collection column %s", receiver.name); - - if (mapKey != null) + if (receiver.type.isMultiCell()) { - checkFalse(receiver.type instanceof ListType, "Indexes on list entries (%s[index] = value) are not currently supported.", receiver.name); - checkTrue(receiver.type instanceof MapType, "Column %s cannot be used as a map", receiver.name); - checkTrue(receiver.type.isMultiCell(), "Map-entry equality predicates on frozen map column %s are not supported", receiver.name); - checkTrue(isEQ(), "Only EQ relations are supported on map entries"); - } + // Non-frozen UDTs don't support any operator + checkFalse(receiver.type.isUDT(), + "Non-frozen UDT column '%s' (%s) cannot be restricted by any relation", + receiver.name, + receiver.type.asCQL3Type()); - // Non-frozen UDTs don't support any operator - checkFalse(receiver.type.isUDT() && receiver.type.isMultiCell(), - "Non-frozen UDT column '%s' (%s) cannot be restricted by any relation", - receiver.name, - receiver.type.asCQL3Type()); - - if (receiver.type.isCollection()) - { // We don't support relations against entire collections (unless they're frozen), like "numbers = {1, 2, 3}" - checkFalse(receiver.type.isMultiCell() && !isLegalRelationForNonFrozenCollection(), + checkFalse(receiver.type.isCollection() && !isLegalRelationForNonFrozenCollection(), "Collection column '%s' (%s) cannot be restricted by a '%s' relation", receiver.name, receiver.type.asCQL3Type(), operator()); - - if (isContainsKey() || isContains()) - { - receiver = makeCollectionReceiver(receiver, isContainsKey()); - } - else if (receiver.type.isMultiCell() && mapKey != null && isEQ()) - { - List receivers = new ArrayList<>(2); - receivers.add(makeCollectionReceiver(receiver, true)); - receivers.add(makeCollectionReceiver(receiver, false)); - return receivers; - } } - - return Collections.singletonList(receiver); } private static ColumnSpecification makeCollectionReceiver(ColumnSpecification receiver, boolean forKey) @@ -330,9 +291,4 @@ public final class SingleColumnRelation extends Relation { return mapKey != null && isEQ(); } - - private boolean canHaveOnlyOneValue() - { - return isEQ() || isLIKE() || (isIN() && inValues != null && inValues.size() == 1); - } } diff --git a/src/java/org/apache/cassandra/cql3/Terms.java b/src/java/org/apache/cassandra/cql3/Terms.java deleted file mode 100644 index c6e110afe0..0000000000 --- a/src/java/org/apache/cassandra/cql3/Terms.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * 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 java.nio.ByteBuffer; -import java.util.*; - -import org.apache.cassandra.cql3.Term.MultiItemTerminal; -import org.apache.cassandra.cql3.Term.Terminal; -import org.apache.cassandra.cql3.functions.Function; -import org.apache.cassandra.db.marshal.*; -import org.apache.cassandra.schema.SchemaConstants; -import org.apache.cassandra.transport.ProtocolVersion; - -/** - * A set of {@code Terms} - */ -public interface Terms -{ - /** - * The {@code List} returned when the list was not set. - */ - @SuppressWarnings("rawtypes") - public static final List UNSET_LIST = new AbstractList() - { - @Override - public Object get(int index) - { - throw new UnsupportedOperationException(); - } - - @Override - public int size() - { - return 0; - } - }; - - /** - * Adds all functions (native and user-defined) used by any of the terms to the specified list. - * @param functions the list to add to - */ - public void addFunctionsTo(List functions); - - /** - * Collects the column specifications for the bind variables in the terms. - * This is obviously a no-op if the terms are Terminal. - * - * @param boundNames the variables specification where to collect the - * bind variables of the terms in. - */ - public void collectMarkerSpecification(VariableSpecifications boundNames); - - /** - * Bind the values in these terms to the values contained in {@code options}. - * This is obviously a no-op if the term is Terminal. - * - * @param options the values to bind markers to. - * @return the result of binding all the variables of these NonTerminals or an {@code UNSET_LIST} if the term - * was unset. - */ - public List bind(QueryOptions options); - - - public List bindAndGet(QueryOptions options); - - /** - * Creates a {@code Terms} for the specified list marker. - * - * @param marker the list marker - * @param type the element type - * @return a {@code Terms} for the specified list marker - */ - public static Terms ofListMarker(final Lists.Marker marker, final AbstractType type) - { - return new Terms() - { - @Override - public void addFunctionsTo(List functions) - { - } - - @Override - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - marker.collectMarkerSpecification(boundNames); - } - - @Override - public List bindAndGet(QueryOptions options) - { - Terminal terminal = marker.bind(options); - - if (terminal == null) - return null; - - if (terminal == Constants.UNSET_VALUE) - return UNSET_LIST; - - return ((MultiItemTerminal) terminal).getElements(); - } - - @Override - public List bind(QueryOptions options) - { - Terminal terminal = marker.bind(options); - - if (terminal == null) - return null; - - if (terminal == Constants.UNSET_VALUE) - return UNSET_LIST; - - java.util.function.Function deserializer = deserializer(options.getProtocolVersion()); - - List boundValues = ((MultiItemTerminal) terminal).getElements(); - List values = new ArrayList<>(boundValues.size()); - for (int i = 0, m = boundValues.size(); i < m; i++) - { - ByteBuffer buffer = boundValues.get(i); - Term.Terminal value = buffer == null ? null : deserializer.apply(buffer); - values.add(value); - } - return values; - } - - public java.util.function.Function deserializer(ProtocolVersion version) - { - if (type.isCollection()) - { - switch (((CollectionType) type).kind) - { - case LIST: - return e -> Lists.Value.fromSerialized(e, (ListType) type); - case SET: - return e -> Sets.Value.fromSerialized(e, (SetType) type); - case MAP: - return e -> Maps.Value.fromSerialized(e, (MapType) type); - } - throw new AssertionError(); - } - return e -> new Constants.Value(e); - } - }; - } - - /** - * Creates a {@code Terms} containing a single {@code Term}. - * - * @param term the {@code Term} - * @return a {@code Terms} containing a single {@code Term}. - */ - public static Terms of(final Term term) - { - return new Terms() - { - @Override - public void addFunctionsTo(List functions) - { - term.addFunctionsTo(functions); - } - - @Override - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - term.collectMarkerSpecification(boundNames); - } - - @Override - public List bindAndGet(QueryOptions options) - { - return Collections.singletonList(term.bindAndGet(options)); - } - - @Override - public List bind(QueryOptions options) - { - return Collections.singletonList(term.bind(options)); - } - }; - } - - /** - * Creates a {@code Terms} containing a set of {@code Term}. - * - * @param term the {@code Term} - * @return a {@code Terms} containing a set of {@code Term}. - */ - public static Terms of(final List terms) - { - return new Terms() - { - @Override - public void addFunctionsTo(List functions) - { - addFunctions(terms, functions); - } - - @Override - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - for (int i = 0, m = terms.size(); i bind(QueryOptions options) - { - int size = terms.size(); - List terminals = new ArrayList<>(size); - for (int i = 0; i < size; i++) - { - Term term = terms.get(i); - terminals.add(term.bind(options)); - } - return terminals; - } - - @Override - public List bindAndGet(QueryOptions options) - { - int size = terms.size(); - List buffers = new ArrayList<>(size); - for (int i = 0; i < size; i++) - { - Term term = terms.get(i); - buffers.add(term.bindAndGet(options)); - } - return buffers; - } - }; - } - - /** - * Adds all functions (native and user-defined) of the specified terms to the list. - * @param functions the list to add to - */ - public static void addFunctions(Iterable terms, List functions) - { - for (Term term : terms) - { - if (term != null) - term.addFunctionsTo(functions); - } - } - - public static ByteBuffer asBytes(String keyspace, String term, AbstractType type) - { - ColumnSpecification receiver = new ColumnSpecification(keyspace, SchemaConstants.DUMMY_KEYSPACE_OR_TABLE_NAME, new ColumnIdentifier("(dummy)", true), type); - Term.Raw rawTerm = CQLFragmentParser.parseAny(CqlParser::term, term, "CQL term"); - return rawTerm.prepare(keyspace, receiver).bindAndGet(QueryOptions.DEFAULT); - } -} diff --git a/src/java/org/apache/cassandra/cql3/TokenRelation.java b/src/java/org/apache/cassandra/cql3/TokenRelation.java index 139c55d358..58f17d14ba 100644 --- a/src/java/org/apache/cassandra/cql3/TokenRelation.java +++ b/src/java/org/apache/cassandra/cql3/TokenRelation.java @@ -18,16 +18,18 @@ package org.apache.cassandra.cql3; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import com.google.common.base.Joiner; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; +import org.apache.cassandra.cql3.terms.Tuples; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.cql3.Term.Raw; import org.apache.cassandra.cql3.restrictions.Restriction; import org.apache.cassandra.cql3.restrictions.TokenRestriction; import org.apache.cassandra.cql3.statements.Bound; @@ -55,7 +57,7 @@ public final class TokenRelation extends Relation public TokenRelation(List entities, Operator type, Term.Raw value) { this.entities = entities; - this.relationType = type; + this.operator = type; this.value = value; } @@ -70,7 +72,7 @@ public final class TokenRelation extends Relation return value; } - public List getInValues() + public Terms.Raw getInValues() { return null; } @@ -118,17 +120,6 @@ public final class TokenRelation extends Relation throw invalidRequest("%s cannot be used with the token function", operator); } - @Override - protected Term toTerm(List receivers, - Raw raw, - String keyspace, - VariableSpecifications boundNames) throws InvalidRequestException - { - Term term = raw.prepare(keyspace, receivers.get(0)); - term.collectMarkerSpecification(boundNames); - return term; - } - @Override public Relation renameIdentifier(ColumnIdentifier from, ColumnIdentifier to) { @@ -142,13 +133,13 @@ public final class TokenRelation extends Relation @Override public String toCQLString() { - return String.format("token%s %s %s", Tuples.tupleToString(entities, ColumnIdentifier::toCQLString), relationType, value); + return String.format("token%s %s %s", Tuples.tupleToString(entities, ColumnIdentifier::toCQLString), operator, value); } @Override public int hashCode() { - return Objects.hash(relationType, entities, value); + return Objects.hash(operator, entities, value); } @Override @@ -161,7 +152,7 @@ public final class TokenRelation extends Relation return false; TokenRelation tr = (TokenRelation) o; - return relationType.equals(tr.relationType) && entities.equals(tr.entities) && value.equals(tr.value); + return operator == tr.operator && entities.equals(tr.entities) && value.equals(tr.value); } /** @@ -180,21 +171,21 @@ public final class TokenRelation extends Relation } /** - * Returns the receivers for this relation. + * Returns the receiver for this relation. * * @param table the table meta data * @param columnDefs the column definitions * @return the receivers for the specified relation. * @throws InvalidRequestException if the relation is invalid */ - private static List toReceivers(TableMetadata table, - List columnDefs) - throws InvalidRequestException + private static ColumnSpecification toReceivers(TableMetadata table, + List columnDefs) + throws InvalidRequestException { if (!columnDefs.equals(table.partitionKeyColumns())) { - checkTrue(columnDefs.containsAll(table.partitionKeyColumns()), + checkTrue(new HashSet<>(columnDefs).containsAll(table.partitionKeyColumns()), "The token() function must be applied to all partition key components or none of them"); checkContainsNoDuplicates(columnDefs, "The token() function contains duplicate partition key components"); @@ -206,9 +197,9 @@ public final class TokenRelation extends Relation } ColumnMetadata firstColumn = columnDefs.get(0); - return Collections.singletonList(new ColumnSpecification(firstColumn.ksName, - firstColumn.cfName, - new ColumnIdentifier("partition key token", true), - table.partitioner.getTokenValidator())); + return new ColumnSpecification(firstColumn.ksName, + firstColumn.cfName, + new ColumnIdentifier("partition key token", true), + table.partitioner.getTokenValidator()); } } diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java b/src/java/org/apache/cassandra/cql3/Tuples.java deleted file mode 100644 index 60f963ce4f..0000000000 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ /dev/null @@ -1,530 +0,0 @@ -/* - * 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 java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.apache.cassandra.cql3.functions.Function; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; -import org.apache.cassandra.db.marshal.ListType; -import org.apache.cassandra.db.marshal.ReversedType; -import org.apache.cassandra.db.marshal.TupleType; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.serializers.MarshalException; -import org.apache.cassandra.transport.ProtocolVersion; -import org.apache.cassandra.utils.ByteBufferUtil; - -import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; - -/** - * Static helper methods and classes for tuples. - */ -public class Tuples -{ - private Tuples() {} - - public static ColumnSpecification componentSpecOf(ColumnSpecification column, int component) - { - return new ColumnSpecification(column.ksName, - column.cfName, - new ColumnIdentifier(String.format("%s[%d]", column.name, component), true), - (getTupleType(column.type)).type(component)); - } - - /** - * 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 extends Term.MultiColumnRaw - { - private final List elements; - - public Literal(List elements) - { - this.elements = elements; - } - - public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException - { - // The parser cannot differentiate between a tuple with one element and a term between parenthesis. - // By consequence, we need to wait until we know the target type to determine which one it is. - if (elements.size() == 1 && !checkIfTupleType(receiver.type)) - return elements.get(0).prepare(keyspace, receiver); - - validateTupleAssignableTo(receiver, elements); - - List values = new ArrayList<>(elements.size()); - boolean allTerminal = true; - for (int i = 0; i < elements.size(); i++) - { - Term value = elements.get(i).prepare(keyspace, componentSpecOf(receiver, i)); - if (value instanceof Term.NonTerminal) - allTerminal = false; - - values.add(value); - } - DelayedValue value = new DelayedValue(getTupleType(receiver.type), values); - return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; - } - - public Term prepare(String keyspace, List 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 values = new ArrayList<>(elements.size()); - List> types = new ArrayList<>(elements.size()); - boolean allTerminal = true; - for (int i = 0; i < elements.size(); i++) - { - Term t = elements.get(i).prepare(keyspace, receivers.get(i)); - if (t instanceof Term.NonTerminal) - allTerminal = false; - - values.add(t); - types.add(receivers.get(i).type); - } - DelayedValue value = new DelayedValue(new TupleType(types), values); - return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; - } - - public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) - { - // The parser cannot differentiate between a tuple with one element and a term between parenthesis. - // By consequence, we need to wait until we know the target type to determine which one it is. - if (elements.size() == 1 && !checkIfTupleType(receiver.type)) - return elements.get(0).testAssignment(keyspace, receiver); - - return testTupleAssignment(receiver, elements); - } - - @Override - public AbstractType getExactTypeIfKnown(String keyspace) - { - List> types = new ArrayList<>(elements.size()); - for (Term.Raw term : elements) - { - AbstractType type = term.getExactTypeIfKnown(keyspace); - if (type == null) - return null; - types.add(type); - } - return new TupleType(types); - } - - public String getText() - { - return tupleToString(elements, Term.Raw::getText); - } - } - - /** - * 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) - { - ByteBuffer[] values = type.split(ByteBufferAccessor.instance, bytes); - if (values.length > type.size()) - { - throw new InvalidRequestException(String.format( - "Tuple value contained too many fields (expected %s, got %s)", type.size(), values.length)); - } - - return new Value(type.split(ByteBufferAccessor.instance, bytes)); - } - - public ByteBuffer get(ProtocolVersion version) - { - return TupleType.buildValue(elements); - } - - public List 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 TupleType type; - public final List elements; - - public DelayedValue(TupleType type, List elements) - { - this.type = type; - 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); - } - - private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException - { - if (elements.size() > type.size()) - throw new InvalidRequestException(String.format( - "Tuple value contained too many fields (expected %s, got %s)", type.size(), elements.size())); - - ByteBuffer[] buffers = new ByteBuffer[elements.size()]; - for (int i = 0; i < elements.size(); i++) - { - buffers[i] = elements.get(i).bindAndGet(options); - // Since A tuple value is always written in its entirety Cassandra can't preserve a pre-existing value by 'not setting' the new value. Reject the query. - if (buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER) - throw new InvalidRequestException(String.format("Invalid unset value for tuple field number %d", i)); - } - return buffers; - } - - public Value bind(QueryOptions options) throws InvalidRequestException - { - return new Value(bindInternal(options)); - } - - @Override - public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException - { - // We don't "need" that override but it saves us the allocation of a Value object if used - return TupleType.buildValue(bindInternal(options)); - } - - @Override - public String toString() - { - return tupleToString(elements); - } - - public void addFunctionsTo(List functions) - { - Terms.addFunctions(elements, functions); - } - } - - /** - * 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> elements; - - public InValue(List> 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 the deserialization does the validation (so we're fine). - List l = type.getSerializer().deserialize(value, ByteBufferAccessor.instance); - - assert type.getElementsType() instanceof TupleType; - TupleType tupleType = Tuples.getTupleType(type.getElementsType()); - - // type.split(bytes) - List> elements = new ArrayList<>(l.size()); - for (T element : l) - elements.add(Arrays.asList(tupleType.split(ByteBufferAccessor.instance, type.getElementsType().decompose(element)))); - return new InValue(elements); - } - catch (MarshalException e) - { - throw new InvalidRequestException(e.getMessage()); - } - } - - public ByteBuffer get(ProtocolVersion version) - { - throw new UnsupportedOperationException(); - } - - public List> getSplitValues() - { - return elements; - } - } - - /** - * A raw placeholder for a tuple of values for different multiple columns, each of which may have a different type. - * {@code - * For example, "SELECT ... WHERE (col1, col2) > ?". - * } - */ - public static class Raw extends AbstractMarker.MultiColumnRaw - { - public Raw(int bindIndex) - { - super(bindIndex); - } - - private static ColumnSpecification makeReceiver(List receivers) - { - List> 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 AbstractType getExactTypeIfKnown(String keyspace) - { - return null; - } - - public AbstractMarker prepare(String keyspace, List receivers) throws InvalidRequestException - { - return new Tuples.Marker(bindIndex, makeReceiver(receivers)); - } - } - - /** - * A raw marker for an IN list of tuples, like "SELECT ... WHERE (a, b, c) IN ?" - */ - public static class INRaw extends AbstractMarker.MultiColumnRaw - { - public INRaw(int bindIndex) - { - super(bindIndex); - } - - private static ColumnSpecification makeInReceiver(List receivers) throws InvalidRequestException - { - List> 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.isCollection() && receiver.type.isMultiCell()) - throw new InvalidRequestException("Non-frozen 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, false)); - } - - public AbstractType getExactTypeIfKnown(String keyspace) - { - return null; - } - - public AbstractMarker prepare(String keyspace, List receivers) throws InvalidRequestException - { - return new InMarker(bindIndex, makeInReceiver(receivers)); - } - } - - /** - * {@code - * 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(QueryOptions options) throws InvalidRequestException - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) - throw new InvalidRequestException(String.format("Invalid unset value for tuple %s", receiver.name)); - return value == null ? null : Value.fromSerialized(value, getTupleType(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(QueryOptions options) throws InvalidRequestException - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) - throw new InvalidRequestException(String.format("Invalid unset value for %s", receiver.name)); - return value == null ? null : InValue.fromSerialized(value, (ListType) receiver.type); - } - } - - /** - * Create a String representation of the tuple containing the specified elements. - * - * @param elements the tuple elements - * @return a String representation of the tuple - */ - public static String tupleToString(List elements) - { - return tupleToString(elements, Object::toString); - } - - /** - * Create a String representation of the tuple from the specified items associated to - * the tuples elements. - * - * @param items items associated to the tuple elements - * @param mapper the mapper used to map the items to the String representation of the tuple elements - * @return a String representation of the tuple - */ - public static String tupleToString(Iterable items, java.util.function.Function mapper) - { - return StreamSupport.stream(items.spliterator(), false) - .map(mapper) - .collect(Collectors.joining(", ", "(", ")")); - } - - /** - * Returns the exact TupleType from the items if it can be known. - * - * @param items the items mapped to the tuple elements - * @param mapper the mapper used to retrieve the element types from the items - * @return the exact TupleType from the items if it can be known or null - */ - public static TupleType getExactTupleTypeIfKnown(List items, - java.util.function.Function> mapper) - { - List> types = new ArrayList<>(items.size()); - for (T item : items) - { - AbstractType type = mapper.apply(item); - if (type == null) - return null; - types.add(type); - } - return new TupleType(types); - } - - /** - * Checks if the tuple with the specified elements can be assigned to the specified column. - * - * @param receiver the receiving column - * @param elements the tuple elements - * @throws InvalidRequestException if the tuple cannot be assigned to the specified column. - */ - public static void validateTupleAssignableTo(ColumnSpecification receiver, - List elements) - { - if (!checkIfTupleType(receiver.type)) - throw invalidRequest("Invalid tuple type literal for %s of type %s", receiver.name, receiver.type.asCQL3Type()); - - TupleType tt = getTupleType(receiver.type); - for (int i = 0; i < elements.size(); i++) - { - if (i >= tt.size()) - { - throw invalidRequest("Invalid tuple literal for %s: too many elements. Type %s expects %d but got %d", - receiver.name, tt.asCQL3Type(), tt.size(), elements.size()); - } - - AssignmentTestable value = elements.get(i); - ColumnSpecification spec = componentSpecOf(receiver, i); - if (!value.testAssignment(receiver.ksName, spec).isAssignable()) - throw invalidRequest("Invalid tuple literal for %s: component %d is not of type %s", - receiver.name, i, spec.type.asCQL3Type()); - } - } - - /** - * Tests that the tuple with the specified elements can be assigned to the specified column. - * - * @param receiver the receiving column - * @param elements the tuple elements - */ - public static AssignmentTestable.TestResult testTupleAssignment(ColumnSpecification receiver, - List elements) - { - try - { - validateTupleAssignableTo(receiver, elements); - return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; - } - catch (InvalidRequestException e) - { - return AssignmentTestable.TestResult.NOT_ASSIGNABLE; - } - } - - public static boolean checkIfTupleType(AbstractType tuple) - { - return (tuple instanceof TupleType) || - (tuple instanceof ReversedType && ((ReversedType) tuple).baseType instanceof TupleType); - - } - - public static TupleType getTupleType(AbstractType tuple) - { - return (tuple instanceof ReversedType ? ((TupleType) ((ReversedType) tuple).baseType) : (TupleType)tuple); - } -} diff --git a/src/java/org/apache/cassandra/cql3/TypeCast.java b/src/java/org/apache/cassandra/cql3/TypeCast.java index 7b2f306b52..9d4ac68bda 100644 --- a/src/java/org/apache/cassandra/cql3/TypeCast.java +++ b/src/java/org/apache/cassandra/cql3/TypeCast.java @@ -17,6 +17,7 @@ */ package org.apache.cassandra.cql3; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; diff --git a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java index 68cf2d3782..5fce9bcb42 100644 --- a/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/conditions/ColumnCondition.java @@ -23,8 +23,13 @@ import java.util.*; import com.google.common.collect.Iterators; import org.apache.cassandra.cql3.*; -import org.apache.cassandra.cql3.Term.Terminal; import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Lists; +import org.apache.cassandra.cql3.terms.Maps; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; +import org.apache.cassandra.cql3.terms.UserTypes; import org.apache.cassandra.db.rows.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.schema.ColumnMetadata; @@ -77,27 +82,21 @@ public abstract class ColumnCondition protected final List bindAndGetTerms(QueryOptions options) { - return filterUnsetValuesIfNeeded(checkValues(terms.bindAndGet(options))); + List buffers = terms.bindAndGet(options); + checkFalse(buffers == null && operator.isIN(), "Invalid null list in IN condition"); + checkFalse(buffers == Terms.UNSET_LIST, "Invalid 'unset' value in condition"); + return filterUnsetValuesIfNeeded(buffers, ByteBufferUtil.UNSET_BYTE_BUFFER); } - protected final List bindTerms(QueryOptions options) + protected final Terms.Terminals bindTerms(QueryOptions options) { - return filterUnsetValuesIfNeeded(checkValues(terms.bind(options))); + Terms.Terminals terminals = terms.bind(options); + checkFalse(terminals == null, "Invalid null list in IN condition"); + checkFalse(terminals == Terms.UNSET_TERMINALS, "Invalid 'unset' value in condition"); + return Terms.Terminals.of(filterUnsetValuesIfNeeded(terminals.asList(), Constants.UNSET_VALUE)); } - /** - * Checks that the output of a bind operations on {@code Terms} is a valid one. - * @param values the list to check - * @return the input list - */ - private List checkValues(List values) - { - checkFalse(values == null && operator.isIN(), "Invalid null list in IN condition"); - checkFalse(values == Terms.UNSET_LIST, "Invalid 'unset' value in condition"); - return values; - } - - private List filterUnsetValuesIfNeeded(List values) + private List filterUnsetValuesIfNeeded(List values, T unsetValue) { if (!operator.isIN()) return values; @@ -106,8 +105,7 @@ public abstract class ColumnCondition for (int i = 0, m = values.size(); i < m; i++) { T value = values.get(i); - // The value can be ByteBuffer or Constants.Value so we need to check the 2 type of UNSET - if (value != ByteBufferUtil.UNSET_BYTE_BUFFER && value != Constants.UNSET_VALUE) + if (value != unsetValue) filtered.add(value); } return filtered; @@ -259,21 +257,21 @@ public abstract class ColumnCondition } } - protected static final Cell getCell(Row row, ColumnMetadata column) + protected static Cell getCell(Row row, ColumnMetadata column) { // If we're asking for a given cell, and we didn't got any row from our read, it's // the same as not having said cell. return row == null ? null : row.getCell(column); } - protected static final Cell getCell(Row row, ColumnMetadata column, CellPath path) + protected static Cell getCell(Row row, ColumnMetadata column, CellPath path) { // If we're asking for a given cell, and we didn't got any row from our read, it's // the same as not having said cell. return row == null ? null : row.getCell(column, path); } - protected static final Iterator> getCells(Row row, ColumnMetadata column) + protected static Iterator> getCells(Row row, ColumnMetadata column) { // If we're asking for a complex cells, and we didn't got any row from our read, it's // the same as not having any cells for that column. @@ -284,7 +282,7 @@ public abstract class ColumnCondition return complexData == null ? Collections.>emptyIterator() : complexData.iterator(); } - protected static final boolean evaluateComparisonWithOperator(int comparison, Operator operator) + protected static boolean evaluateComparisonWithOperator(int comparison, Operator operator) { // called when comparison != 0 switch (operator) @@ -452,9 +450,9 @@ public abstract class ColumnCondition */ private static final class MultiCellCollectionBound extends Bound { - private final List values; + private final Terms.Terminals values; - public MultiCellCollectionBound(ColumnMetadata column, Operator operator, List values) + public MultiCellCollectionBound(ColumnMetadata column, Operator operator, Terms.Terminals values) { super(column, operator); assert column.type.isMultiCell(); @@ -463,10 +461,10 @@ public abstract class ColumnCondition public boolean appliesTo(Row row) { - CollectionType type = (CollectionType)column.type; + CollectionType type = (CollectionType) column.type; // copy iterator contents so that we can properly reuse them for each comparison with an IN value - for (Term.Terminal value : values) + for (Term.Terminal value : values.asList()) { Iterator> iter = getCells(row, column); if (value == null) @@ -496,19 +494,16 @@ public abstract class ColumnCondition return !iter.hasNext(); if(operator.isContains() || operator.isContainsKey()) - return containsAppliesTo(type, iter, value.get(ProtocolVersion.CURRENT), operator); + return containsAppliesTo(type, iter, value.get(), operator); switch (type.kind) { case LIST: - List valueList = ((Lists.Value) value).elements; - return listAppliesTo((ListType)type, iter, valueList, operator); + return listAppliesTo((ListType)type, iter, value.getElements(), operator); case SET: - Set valueSet = ((Sets.Value) value).elements; - return setAppliesTo((SetType)type, iter, valueSet, operator); + return setAppliesTo((SetType)type, iter, value.getElements(), operator); case MAP: - Map valueMap = ((Maps.Value) value).map; - return mapAppliesTo((MapType)type, iter, valueMap, operator); + return mapAppliesTo((MapType)type, iter, value.getElements(), operator); } throw new AssertionError(); } @@ -539,31 +534,31 @@ public abstract class ColumnCondition return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false); } - private static boolean setAppliesTo(SetType type, Iterator> iter, Set elements, Operator operator) + private static boolean setAppliesTo(SetType type, Iterator> iter, List elements, Operator operator) { - ArrayList sortedElements = new ArrayList<>(elements); - Collections.sort(sortedElements, type.getElementsType()); - return setOrListAppliesTo(type.getElementsType(), iter, sortedElements.iterator(), operator, true); + // The elements are alread sorted as expected by the SetType + return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, true); } - private static boolean mapAppliesTo(MapType type, Iterator> iter, Map elements, Operator operator) + private static boolean mapAppliesTo(MapType type, Iterator> iter, List elements, Operator operator) { - Iterator> conditionIter = elements.entrySet().iterator(); + Iterator conditionIter = elements.iterator(); while(iter.hasNext()) { if (!conditionIter.hasNext()) return (operator == Operator.GT) || (operator == Operator.GTE) || (operator == Operator.NEQ); - Map.Entry conditionEntry = conditionIter.next(); + ByteBuffer key = conditionIter.next(); + ByteBuffer value = conditionIter.next(); Cell c = iter.next(); // compare the keys - int comparison = type.getKeysType().compare(c.path().get(0), conditionEntry.getKey()); + int comparison = type.getKeysType().compare(c.path().get(0), key); if (comparison != 0) return evaluateComparisonWithOperator(comparison, operator); // compare the values - comparison = type.getValuesType().compare(c.buffer(), conditionEntry.getValue()); + comparison = type.getValuesType().compare(c.buffer(), value); if (comparison != 0) return evaluateComparisonWithOperator(comparison, operator); } @@ -651,7 +646,7 @@ public abstract class ColumnCondition Cell cell = getCell(row, column); return cell == null ? null - : userType.split(ByteBufferAccessor.instance, cell.buffer())[userType.fieldPosition(field)]; + : userType.unpack(cell.buffer()).get(userType.fieldPosition(field)); } private boolean isSatisfiedBy(ByteBuffer rowValue) @@ -730,8 +725,7 @@ public abstract class ColumnCondition public static class Raw { private final Term.Raw value; - private final List inValues; - private final AbstractMarker.INRaw inMarker; + private final Terms.Raw inValues; // Can be null, only used with the syntax "IF m[e] = ..." (in which case it's 'e') private final Term.Raw collectionElement; @@ -741,12 +735,11 @@ public abstract class ColumnCondition private final Operator operator; - private Raw(Term.Raw value, List inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement, + private Raw(Term.Raw value, Terms.Raw inValues, Term.Raw collectionElement, FieldIdentifier udtField, Operator op) { this.value = value; this.inValues = inValues; - this.inMarker = inMarker; this.collectionElement = collectionElement; this.udtField = udtField; this.operator = op; @@ -755,55 +748,37 @@ public abstract class ColumnCondition /** A condition on a column. For example: "IF col = 'foo'" */ public static Raw simpleCondition(Term.Raw value, Operator op) { - return new Raw(value, null, null, null, null, op); + return new Raw(value, null, null, null, op); } /** An IN condition on a column. For example: "IF col IN ('foo', 'bar', ...)" */ - public static Raw simpleInCondition(List inValues) + public static Raw simpleInCondition(Terms.Raw inValues) { - return new Raw(null, inValues, null, null, null, Operator.IN); - } - - /** An IN condition on a column with a single marker. For example: "IF col IN ?" */ - public static Raw simpleInCondition(AbstractMarker.INRaw inMarker) - { - return new Raw(null, null, inMarker, null, null, Operator.IN); + return new Raw(null, inValues, null, null, Operator.IN); } /** A condition on a collection element. For example: "IF col['key'] = 'foo'" */ public static Raw collectionCondition(Term.Raw value, Term.Raw collectionElement, Operator op) { - return new Raw(value, null, null, collectionElement, null, op); + return new Raw(value, null, collectionElement, null, op); } /** An IN condition on a collection element. For example: "IF col['key'] IN ('foo', 'bar', ...)" */ - public static Raw collectionInCondition(Term.Raw collectionElement, List inValues) + public static Raw collectionInCondition(Term.Raw collectionElement, Terms.Raw inValues) { - return new Raw(null, inValues, null, collectionElement, null, Operator.IN); - } - - /** An IN condition on a collection element with a single marker. For example: "IF col['key'] IN ?" */ - public static Raw collectionInCondition(Term.Raw collectionElement, AbstractMarker.INRaw inMarker) - { - return new Raw(null, null, inMarker, collectionElement, null, Operator.IN); + return new Raw(null, inValues, collectionElement, null, Operator.IN); } /** A condition on a UDT field. For example: "IF col.field = 'foo'" */ public static Raw udtFieldCondition(Term.Raw value, FieldIdentifier udtField, Operator op) { - return new Raw(value, null, null, null, udtField, op); + return new Raw(value, null, null, udtField, op); } /** An IN condition on a collection element. For example: "IF col.field IN ('foo', 'bar', ...)" */ - public static Raw udtFieldInCondition(FieldIdentifier udtField, List inValues) + public static Raw udtFieldInCondition(FieldIdentifier udtField, Terms.Raw inValues) { - return new Raw(null, inValues, null, null, udtField, Operator.IN); - } - - /** An IN condition on a collection element with a single marker. For example: "IF col.field IN ?" */ - public static Raw udtFieldInCondition(FieldIdentifier udtField, AbstractMarker.INRaw inMarker) - { - return new Raw(null, null, inMarker, null, udtField, Operator.IN); + return new Raw(null, inValues, null, udtField, Operator.IN); } public ColumnCondition prepare(String keyspace, ColumnMetadata receiver, TableMetadata cfm) @@ -860,8 +835,7 @@ public abstract class ColumnCondition if (operator.isIN()) { - return inValues == null ? Terms.ofListMarker(inMarker.prepare(keyspace, receiver), receiver.type) - : Terms.of(prepareTerms(keyspace, receiver, inValues)); + return inValues.prepare(keyspace, receiver); } if (operator.isContains() || operator.isContainsKey()) @@ -870,17 +844,6 @@ public abstract class ColumnCondition return Terms.of(value.prepare(keyspace, receiver)); } - private static List prepareTerms(String keyspace, ColumnSpecification receiver, List raws) - { - List terms = new ArrayList<>(raws.size()); - for (int i = 0, m = raws.size(); i < m; i++) - { - Term.Raw raw = raws.get(i); - terms.add(raw.prepare(keyspace, receiver)); - } - return terms; - } - private void validateOperationOnDurations(AbstractType type) { if (type.referencesDuration() && operator.isSlice()) diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index ee5fda7816..30d9d50437 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -26,6 +26,10 @@ import java.util.stream.Collectors; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.statements.RequestValidations; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.serializers.MarshalException; @@ -108,22 +112,9 @@ public class FunctionCall extends Term.NonTerminal { if (result == null) return null; - if (fun.returnType().isCollection()) - { - switch (((CollectionType) fun.returnType()).kind) - { - case LIST: - return Lists.Value.fromSerialized(result, (ListType) fun.returnType()); - case SET: - return Sets.Value.fromSerialized(result, (SetType) fun.returnType()); - case MAP: - return Maps.Value.fromSerialized(result, (MapType) fun.returnType()); - } - } - else if (fun.returnType().isUDT()) - { - return UserTypes.Value.fromSerialized(result, (UserType) fun.returnType()); - } + + if (fun.returnType() instanceof MultiElementType) + return MultiElements.Value.fromSerialized(result, (MultiElementType) fun.returnType()); return new Constants.Value(result); } diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java index 81e97a7591..fd1c901b59 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java @@ -25,7 +25,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.apache.cassandra.cql3.AbstractMarker; +import org.apache.cassandra.cql3.terms.Marker; import org.apache.cassandra.cql3.AssignmentTestable; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnSpecification; @@ -222,7 +222,7 @@ public final class FunctionResolver */ private static boolean containsMarkers(List args) { - return args.stream().anyMatch(AbstractMarker.Raw.class::isInstance); + return args.stream().anyMatch(Marker.Raw.class::isInstance); } /** diff --git a/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java b/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java index de05aa58e2..15e76a316b 100644 --- a/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java +++ b/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java @@ -34,8 +34,7 @@ import org.apache.cassandra.cql3.AssignmentTestable; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.CqlBuilder; -import org.apache.cassandra.cql3.Term; -import org.apache.cassandra.cql3.Terms; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.Arguments; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.FunctionName; @@ -267,7 +266,7 @@ public class ColumnMask { String term = rawPartialArguments.get(i).toString(); AbstractType type = function.argTypes().get(i + 1); - arguments[i] = Terms.asBytes(keyspace, term, type); + arguments[i] = Term.asBytes(keyspace, term, type); } return arguments; diff --git a/src/java/org/apache/cassandra/cql3/restrictions/CustomIndexExpression.java b/src/java/org/apache/cassandra/cql3/restrictions/CustomIndexExpression.java index 569418b3f4..8390d29a91 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/CustomIndexExpression.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/CustomIndexExpression.java @@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.restrictions; import java.util.Objects; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.filter.RowFilter; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.index.Index; diff --git a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java index 5ad6dabd4c..0ba800cb2e 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/MultiColumnRestriction.java @@ -28,21 +28,19 @@ import java.util.Set; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.cassandra.cql3.AbstractMarker; import org.apache.cassandra.cql3.Operator; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Term; -import org.apache.cassandra.cql3.Terms; -import org.apache.cassandra.cql3.Tuples; -import org.apache.cassandra.cql3.Term.Terminal; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; +import org.apache.cassandra.cql3.terms.Term.Terminal; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.db.MultiCBuilder; import org.apache.cassandra.db.filter.RowFilter; +import org.apache.cassandra.db.marshal.ListType; import org.apache.cassandra.index.Index; import org.apache.cassandra.index.IndexRegistry; import org.apache.cassandra.schema.ColumnMetadata; -import org.apache.cassandra.serializers.ListSerializer; import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; @@ -234,7 +232,7 @@ public abstract class MultiColumnRestriction implements SingleRestriction @Override public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options) { - Tuples.Value t = ((Tuples.Value) value.bind(options)); + Term.Terminal t = value.bind(options); List values = t.getElements(); for (int i = 0, m = values.size(); i < m; i++) { @@ -247,7 +245,7 @@ public abstract class MultiColumnRestriction implements SingleRestriction @Override public final void addToRowFilter(RowFilter filter, IndexRegistry indexRegistry, QueryOptions options) { - Tuples.Value t = ((Tuples.Value) value.bind(options)); + Term.Terminal t = value.bind(options); List values = t.getElements(); for (int i = 0, m = columnDefs.size(); i < m; i++) @@ -258,11 +256,20 @@ public abstract class MultiColumnRestriction implements SingleRestriction } } - public abstract static class INRestriction extends MultiColumnRestriction + public static class INRestriction extends MultiColumnRestriction { - public INRestriction(List columnDefs) + private final Terms values; + + public INRestriction(List columnDefs, Terms values) { super(columnDefs); + this.values = values; + } + + @Override + public void addFunctionsTo(List functions) + { + values.addFunctionsTo(functions); } /** @@ -271,8 +278,14 @@ public abstract class MultiColumnRestriction implements SingleRestriction @Override public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options) { - List> splitInValues = splitValues(options); - builder.addAllElementsToAll(splitInValues); + List> elements = values.bindAndGetElements(options); + + if (elements == null) + throw invalidRequest("Invalid null value for in(%s)", ColumnMetadata.toCQLString(columnDefs)); + if (elements == Terms.UNSET_LIST) + throw invalidRequest("Invalid unset value for in(%s)", ColumnMetadata.toCQLString(columnDefs)); + + builder.addAllElementsToAll(elements); if (builder.containsNull()) throw invalidRequest("Invalid null value in condition for columns: %s", ColumnMetadata.toIdentifiers(columnDefs)); @@ -307,12 +320,13 @@ public abstract class MultiColumnRestriction implements SingleRestriction // c IN (x, y, z) and we can perform filtering if (getColumnDefs().size() == 1) { - List> splitValues = splitValues(options); + List> splitValues = values.bindAndGetElements(options); List values = new ArrayList<>(splitValues.size()); for (List splitValue : splitValues) values.add(splitValue.get(0)); - ByteBuffer buffer = ListSerializer.pack(values, values.size()); + ListType type = ListType.getInstance(getFirstColumn().type, false); + ByteBuffer buffer = type.pack(values); filter.add(getFirstColumn(), Operator.IN, buffer); } else @@ -320,82 +334,6 @@ public abstract class MultiColumnRestriction implements SingleRestriction throw invalidRequest("Multicolumn IN filters are not supported"); } } - - protected abstract List> splitValues(QueryOptions options); - } - - /** - * 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 InRestrictionWithValues extends INRestriction - { - protected final List values; - - public InRestrictionWithValues(List columnDefs, List values) - { - super(columnDefs); - this.values = values; - } - - @Override - public void addFunctionsTo(List functions) - { - Terms.addFunctions(values, functions); - } - - @Override - public String toString() - { - return String.format("IN(%s)", values); - } - - @Override - protected List> splitValues(QueryOptions options) - { - List> buffers = new ArrayList<>(values.size()); - for (Term value : values) - { - Term.MultiItemTerminal term = (Term.MultiItemTerminal) value.bind(options); - buffers.add(term.getElements()); - } - return buffers; - } - } - - /** - * An IN restriction that uses a single marker for a set of IN values that are tuples. - * For example: "SELECT ... WHERE (a, b, c) IN ?" - */ - public static class InRestrictionWithMarker extends INRestriction - { - protected final AbstractMarker marker; - - public InRestrictionWithMarker(List columnDefs, AbstractMarker marker) - { - super(columnDefs); - this.marker = marker; - } - - @Override - public void addFunctionsTo(List functions) - { - } - - @Override - public String toString() - { - return "IN ?"; - } - - @Override - protected List> splitValues(QueryOptions options) - { - Tuples.InMarker inMarker = (Tuples.InMarker) marker; - Tuples.InValue inValue = inMarker.bind(options); - checkNotNull(inValue, "Invalid null value for IN restriction"); - return inValue.getSplitValues(); - } } public static class SliceRestriction extends MultiColumnRestriction @@ -566,13 +504,7 @@ public abstract class MultiColumnRestriction implements SingleRestriction return Collections.emptyList(); Terminal terminal = slice.bound(b).bind(options); - - if (terminal instanceof Tuples.Value) - { - return ((Tuples.Value) terminal).getElements(); - } - - return Collections.singletonList(terminal.get(options.getProtocolVersion())); + return terminal.getElements(); } private boolean hasComponent(Bound b, int index, EnumMap> componentBounds) diff --git a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java index d499bdce38..2a8d58fdfc 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/SingleColumnRestriction.java @@ -22,10 +22,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.apache.cassandra.cql3.Operator; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; +import org.apache.cassandra.db.marshal.ListType; import org.apache.cassandra.schema.ColumnMetadata; -import org.apache.cassandra.serializers.ListSerializer; -import org.apache.cassandra.cql3.*; -import org.apache.cassandra.cql3.Term.Terminal; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.db.MultiCBuilder; @@ -216,11 +218,26 @@ public abstract class SingleColumnRestriction implements SingleRestriction } } - public static abstract class INRestriction extends SingleColumnRestriction + public static class INRestriction extends SingleColumnRestriction { - public INRestriction(ColumnMetadata columnDef) + private final Terms values; + + public INRestriction(ColumnMetadata columnDef, Terms values) { super(columnDef); + this.values = values; + } + + @Override + MultiColumnRestriction toMultiColumnRestriction() + { + return new MultiColumnRestriction.INRestriction(Collections.singletonList(columnDef), values); + } + + @Override + public void addFunctionsTo(List functions) + { + values.addFunctionsTo(functions); } @Override @@ -238,7 +255,10 @@ public abstract class SingleColumnRestriction implements SingleRestriction @Override public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options) { - builder.addEachElementToAll(getValues(options)); + List buffers = values.bindAndGet(options); + checkNotNull(buffers, "Invalid null value for column %s", columnDef.name); + checkFalse(buffers == Terms.UNSET_LIST, "Invalid unset value for column %s", columnDef.name); + builder.addEachElementToAll(buffers); checkFalse(builder.containsNull(), "Invalid null value in condition for column %s", columnDef.name); checkFalse(builder.containsUnset(), "Invalid unset value for column %s", columnDef.name); return builder; @@ -249,8 +269,9 @@ public abstract class SingleColumnRestriction implements SingleRestriction IndexRegistry indexRegistry, QueryOptions options) { - List values = getValues(options); - ByteBuffer buffer = ListSerializer.pack(values, values.size()); + List elements = values.bindAndGet(options); + ListType type = ListType.getInstance(columnDef.type, false); + ByteBuffer buffer = type.pack(elements); filter.add(columnDef, Operator.IN, buffer); } @@ -259,84 +280,6 @@ public abstract class SingleColumnRestriction implements SingleRestriction { return index.supportsExpression(columnDef, Operator.IN); } - - protected abstract List getValues(QueryOptions options); - } - - public static class InRestrictionWithValues extends INRestriction - { - protected final List values; - - public InRestrictionWithValues(ColumnMetadata columnDef, List values) - { - super(columnDef); - this.values = values; - } - - @Override - MultiColumnRestriction toMultiColumnRestriction() - { - return new MultiColumnRestriction.InRestrictionWithValues(Collections.singletonList(columnDef), values); - } - - @Override - public void addFunctionsTo(List functions) - { - Terms.addFunctions(values, functions); - } - - @Override - protected List getValues(QueryOptions options) - { - List buffers = new ArrayList<>(values.size()); - for (Term value : values) - buffers.add(value.bindAndGet(options)); - return buffers; - } - - @Override - public String toString() - { - return String.format("IN(%s)", values); - } - } - - public static class InRestrictionWithMarker extends INRestriction - { - protected final AbstractMarker marker; - - public InRestrictionWithMarker(ColumnMetadata columnDef, AbstractMarker marker) - { - super(columnDef); - this.marker = marker; - } - - @Override - public void addFunctionsTo(List functions) - { - } - - @Override - MultiColumnRestriction toMultiColumnRestriction() - { - return new MultiColumnRestriction.InRestrictionWithMarker(Collections.singletonList(columnDef), marker); - } - - @Override - protected List getValues(QueryOptions options) - { - Terminal term = marker.bind(options); - checkNotNull(term, "Invalid null value for column %s", columnDef.name); - checkFalse(term == Constants.UNSET_VALUE, "Invalid unset value for column %s", columnDef.name); - Term.MultiItemTerminal lval = (Term.MultiItemTerminal) term; - return lval.getElements(); - } - - @Override - public String toString() - { - return "IN ?"; - } } public static class SliceRestriction extends SingleColumnRestriction @@ -713,12 +656,6 @@ public abstract class SingleColumnRestriction implements SingleRestriction value.addFunctionsTo(functions); } - @Override - public boolean isEQ() - { - return false; - } - @Override public boolean isLIKE() { diff --git a/src/java/org/apache/cassandra/cql3/restrictions/TermSlice.java b/src/java/org/apache/cassandra/cql3/restrictions/TermSlice.java index 100fcef64b..4e2d70d1aa 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/TermSlice.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/TermSlice.java @@ -21,7 +21,7 @@ import java.util.List; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.cql3.Operator; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.index.Index; diff --git a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java index bf23b336b6..99104d19e5 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/TokenRestriction.java @@ -26,7 +26,7 @@ import org.apache.cassandra.index.Index; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.db.filter.RowFilter; diff --git a/src/java/org/apache/cassandra/cql3/selection/ColumnTimestamps.java b/src/java/org/apache/cassandra/cql3/selection/ColumnTimestamps.java index 698029950e..b3f3fa4ef7 100644 --- a/src/java/org/apache/cassandra/cql3/selection/ColumnTimestamps.java +++ b/src/java/org/apache/cassandra/cql3/selection/ColumnTimestamps.java @@ -27,9 +27,10 @@ import com.google.common.collect.BoundType; import com.google.common.collect.Range; import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.LongType; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.rows.Cell; -import org.apache.cassandra.serializers.CollectionSerializer; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; @@ -303,6 +304,7 @@ abstract class ColumnTimestamps */ private static final class MultipleTimestamps extends ColumnTimestamps { + private static final ListType LONG_LIST_TYPE = ListType.getInstance(LongType.instance, false); private final List timestamps; public MultipleTimestamps(TimestampsType type, int initialCapacity) @@ -382,7 +384,7 @@ abstract class ColumnTimestamps List buffers = new ArrayList<>(timestamps.size()); timestamps.forEach(timestamp -> buffers.add(type.toByteBuffer(timestamp))); - return CollectionSerializer.pack(buffers, timestamps.size()); + return LONG_LIST_TYPE.pack(buffers); } @Override diff --git a/src/java/org/apache/cassandra/cql3/selection/ElementsSelector.java b/src/java/org/apache/cassandra/cql3/selection/ElementsSelector.java index 4644ba2ec8..1c4728a80d 100644 --- a/src/java/org/apache/cassandra/cql3/selection/ElementsSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/ElementsSelector.java @@ -25,7 +25,7 @@ import com.google.common.collect.Range; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.selection.SimpleSelector.SimpleSelectorFactory; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.filter.ColumnFilter; @@ -169,7 +169,7 @@ abstract class ElementsSelector extends Selector } ColumnMetadata column = ((SimpleSelectorFactory) factory).getColumn(); - builder.select(column, CellPath.create(((Term.Terminal)key).get(ProtocolVersion.V3))); + builder.select(column, CellPath.create(((Term.Terminal)key).get())); } }; } @@ -230,8 +230,8 @@ abstract class ElementsSelector extends Selector } ColumnMetadata column = ((SimpleSelectorFactory) factory).getColumn(); - ByteBuffer fromBB = ((Term.Terminal)from).get(ProtocolVersion.V3); - ByteBuffer toBB = ((Term.Terminal)to).get(ProtocolVersion.V3); + ByteBuffer fromBB = ((Term.Terminal)from).get(); + ByteBuffer toBB = ((Term.Terminal)to).get(); builder.slice(column, isUnset(fromBB) ? CellPath.BOTTOM : CellPath.create(fromBB), isUnset(toBB) ? CellPath.TOP : CellPath.create(toBB)); } }; diff --git a/src/java/org/apache/cassandra/cql3/selection/FieldSelector.java b/src/java/org/apache/cassandra/cql3/selection/FieldSelector.java index 20c10292a9..8c34586d1d 100644 --- a/src/java/org/apache/cassandra/cql3/selection/FieldSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/FieldSelector.java @@ -19,6 +19,7 @@ package org.apache.cassandra.cql3.selection; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; import com.google.common.base.Objects; @@ -27,7 +28,6 @@ import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.io.util.DataInputPlus; @@ -109,8 +109,8 @@ final class FieldSelector extends Selector ByteBuffer value = selected.getOutput(protocolVersion); if (value == null) return null; - ByteBuffer[] buffers = type.split(ByteBufferAccessor.instance, value); - return field < buffers.length ? buffers[field] : null; + List buffers = type.unpack(value); + return field < buffers.size() ? buffers.get(field) : null; } @Override diff --git a/src/java/org/apache/cassandra/cql3/selection/ListSelector.java b/src/java/org/apache/cassandra/cql3/selection/ListSelector.java index a777bc5feb..3494b4b831 100644 --- a/src/java/org/apache/cassandra/cql3/selection/ListSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/ListSelector.java @@ -25,7 +25,7 @@ import java.util.List; import com.google.common.base.Objects; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Lists; +import org.apache.cassandra.cql3.terms.Lists; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.filter.ColumnFilter.Builder; import org.apache.cassandra.db.marshal.AbstractType; @@ -33,7 +33,6 @@ import org.apache.cassandra.db.marshal.ListType; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.serializers.CollectionSerializer; import org.apache.cassandra.transport.ProtocolVersion; /** @@ -59,7 +58,7 @@ final class ListSelector extends Selector /** * The list type. */ - private final AbstractType type; + private final ListType type; /** * The list elements @@ -102,7 +101,7 @@ final class ListSelector extends Selector { buffers.add(elements.get(i).getOutput(protocolVersion)); } - return CollectionSerializer.pack(buffers, buffers.size()); + return type.pack(buffers); } public void reset() @@ -136,7 +135,7 @@ final class ListSelector extends Selector private ListSelector(AbstractType type, List elements) { super(Kind.LIST_SELECTOR); - this.type = type; + this.type = (ListType) type; this.elements = elements; } diff --git a/src/java/org/apache/cassandra/cql3/selection/MapSelector.java b/src/java/org/apache/cassandra/cql3/selection/MapSelector.java index 26580e7263..450b64a58b 100644 --- a/src/java/org/apache/cassandra/cql3/selection/MapSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/MapSelector.java @@ -28,7 +28,7 @@ import java.util.stream.Collectors; import com.google.common.base.Objects; import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.Maps; +import org.apache.cassandra.cql3.terms.Maps; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.TypeSizes; @@ -39,7 +39,6 @@ import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.serializers.CollectionSerializer; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.Pair; @@ -218,7 +217,7 @@ final class MapSelector extends Selector buffers.add(entry.getKey()); buffers.add(entry.getValue()); } - return CollectionSerializer.pack(buffers, elements.size()); + return type.pack(buffers); } public void reset() diff --git a/src/java/org/apache/cassandra/cql3/selection/Selectable.java b/src/java/org/apache/cassandra/cql3/selection/Selectable.java index 56f7f85025..4f08ace467 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selectable.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selectable.java @@ -25,6 +25,14 @@ import java.util.stream.Collectors; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.cql3.selection.Selector.Factory; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Lists; +import org.apache.cassandra.cql3.terms.Maps; +import org.apache.cassandra.cql3.terms.Sets; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Tuples; +import org.apache.cassandra.cql3.terms.UserTypes; +import org.apache.cassandra.cql3.terms.Vectors; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.ColumnMetadata; diff --git a/src/java/org/apache/cassandra/cql3/selection/SetSelector.java b/src/java/org/apache/cassandra/cql3/selection/SetSelector.java index de97249af5..a4cc5009af 100644 --- a/src/java/org/apache/cassandra/cql3/selection/SetSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/SetSelector.java @@ -27,7 +27,7 @@ import java.util.TreeSet; import com.google.common.base.Objects; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Sets; +import org.apache.cassandra.cql3.terms.Sets; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.filter.ColumnFilter.Builder; import org.apache.cassandra.db.marshal.AbstractType; @@ -35,7 +35,6 @@ import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.serializers.CollectionSerializer; import org.apache.cassandra.transport.ProtocolVersion; /** @@ -104,7 +103,7 @@ final class SetSelector extends Selector { buffers.add(elements.get(i).getOutput(protocolVersion)); } - return CollectionSerializer.pack(buffers, buffers.size()); + return type.pack(new ArrayList<>(buffers)); } public void reset() diff --git a/src/java/org/apache/cassandra/cql3/selection/TermSelector.java b/src/java/org/apache/cassandra/cql3/selection/TermSelector.java index 19a60ac920..24ed8c41b4 100644 --- a/src/java/org/apache/cassandra/cql3/selection/TermSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/TermSelector.java @@ -26,7 +26,7 @@ import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.io.util.DataInputPlus; diff --git a/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java b/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java index c06d911651..fd3071b1cd 100644 --- a/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java @@ -25,7 +25,7 @@ import java.util.List; import com.google.common.base.Objects; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Tuples; +import org.apache.cassandra.cql3.terms.Tuples; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.filter.ColumnFilter.Builder; import org.apache.cassandra.db.marshal.AbstractType; @@ -59,7 +59,7 @@ final class TupleSelector extends Selector /** * The tuple type. */ - private final AbstractType type; + private final TupleType type; /** * The tuple elements @@ -97,12 +97,12 @@ final class TupleSelector extends Selector public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException { - ByteBuffer[] buffers = new ByteBuffer[elements.size()]; + List buffers = new ArrayList<>(elements.size()); for (int i = 0, m = elements.size(); i < m; i++) { - buffers[i] = elements.get(i).getOutput(protocolVersion); + buffers.add(elements.get(i).getOutput(protocolVersion)); } - return TupleType.buildValue(buffers); + return type.pack(buffers); } public void reset() @@ -136,7 +136,7 @@ final class TupleSelector extends Selector private TupleSelector(AbstractType type, List elements) { super(Kind.TUPLE_SELECTOR); - this.type = type; + this.type = (TupleType) type; this.elements = elements; } diff --git a/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java b/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java index bf71c73ab2..6778cca964 100644 --- a/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java @@ -19,6 +19,7 @@ package org.apache.cassandra.cql3.selection; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,13 +32,12 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.FieldIdentifier; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.UserTypes; +import org.apache.cassandra.cql3.terms.UserTypes; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.filter.ColumnFilter.Builder; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; @@ -68,9 +68,9 @@ final class UserTypeSelector extends Selector }; /** - * The map type. + * The user type. */ - private final AbstractType type; + private final UserType type; /** * The user type fields @@ -191,14 +191,13 @@ final class UserTypeSelector extends Selector public ByteBuffer getOutput(ProtocolVersion protocolVersion) { UserType userType = (UserType) type; - ByteBuffer[] buffers = new ByteBuffer[userType.size()]; + List buffers = new ArrayList<>(userType.size()); for (int i = 0, m = userType.size(); i < m; i++) { Selector selector = fields.get(userType.fieldName(i)); - if (selector != null) - buffers[i] = selector.getOutput(protocolVersion); + buffers.add(selector == null ? null : selector.getOutput(protocolVersion)); } - return TupleType.buildValue(buffers); + return type.pack(buffers); } public void reset() @@ -232,7 +231,7 @@ final class UserTypeSelector extends Selector private UserTypeSelector(AbstractType type, Map fields) { super(Kind.USER_TYPE_SELECTOR); - this.type = type; + this.type = (UserType) type; this.fields = fields; } diff --git a/src/java/org/apache/cassandra/cql3/selection/VectorSelector.java b/src/java/org/apache/cassandra/cql3/selection/VectorSelector.java index 8eeacea940..f61d8d20d6 100644 --- a/src/java/org/apache/cassandra/cql3/selection/VectorSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/VectorSelector.java @@ -26,7 +26,7 @@ import java.util.Objects; import com.google.common.base.Preconditions; -import org.apache.cassandra.cql3.Lists; +import org.apache.cassandra.cql3.terms.Lists; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.marshal.AbstractType; @@ -126,7 +126,7 @@ public class VectorSelector extends Selector for (int i = 0, m = elements.size(); i < m; i++) buffers.add(elements.get(i).getOutput(protocolVersion)); - return type.decomposeRaw(buffers); + return type.pack(buffers); } @Override diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index d0a890a303..224edc90d6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -37,6 +37,7 @@ import org.apache.cassandra.audit.AuditLogContext; import org.apache.cassandra.audit.AuditLogEntryType; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.cql3.restrictions.SingleRestriction; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.index.Index; import org.apache.cassandra.schema.ColumnMetadata; diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java index d3f605a008..0fba12ef50 100644 --- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java @@ -27,6 +27,8 @@ import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.conditions.ColumnCondition; import org.apache.cassandra.cql3.conditions.Conditions; import org.apache.cassandra.cql3.restrictions.StatementRestrictions; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.Slice; import org.apache.cassandra.db.partitions.PartitionUpdate; diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java index 8950f67d9a..6428d85073 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java @@ -36,6 +36,8 @@ import org.apache.cassandra.cql3.functions.ScalarFunction; import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UserFunction; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.schema.UserFunctions.FunctionsDiff; import org.apache.cassandra.schema.KeyspaceMetadata; @@ -164,7 +166,8 @@ public final class CreateAggregateStatement extends AlterSchemaStatement ByteBuffer initialValue = null; if (null != rawInitialValue) { - initialValue = Terms.asBytes(keyspaceName, rawInitialValue.toString(), stateType); + String term = rawInitialValue.toString(); + initialValue = Term.asBytes(keyspaceName, term, stateType); if (null != initialValue) { @@ -180,7 +183,7 @@ public final class CreateAggregateStatement extends AlterSchemaStatement // Converts initcond to a CQL literal and parse it back to avoid another CASSANDRA-11064 String initialValueString = stateType.asCQL3Type().toCQLLiteral(initialValue); - if (!Objects.equal(initialValue, stateType.asCQL3Type().fromCQLLiteral(keyspaceName, initialValueString))) + if (!Objects.equal(initialValue, stateType.asCQL3Type().fromCQLLiteral(initialValueString))) throw new AssertionError(String.format("CQL literal '%s' (from type %s) parsed with a different value", initialValueString, stateType.asCQL3Type())); if (Constants.NULL_LITERAL != rawInitialValue && isNullOrEmpty(stateType, initialValue)) diff --git a/src/java/org/apache/cassandra/cql3/ArrayLiteral.java b/src/java/org/apache/cassandra/cql3/terms/ArrayLiteral.java similarity index 93% rename from src/java/org/apache/cassandra/cql3/ArrayLiteral.java rename to src/java/org/apache/cassandra/cql3/terms/ArrayLiteral.java index 76392fdc01..7cebe6e5ff 100644 --- a/src/java/org/apache/cassandra/cql3/ArrayLiteral.java +++ b/src/java/org/apache/cassandra/cql3/terms/ArrayLiteral.java @@ -16,10 +16,11 @@ * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.util.List; +import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.ListType; import org.apache.cassandra.db.marshal.VectorType; @@ -55,7 +56,7 @@ public class ArrayLiteral extends Term.Raw } @Override - public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) + public TestResult testAssignment(String keyspace, ColumnSpecification receiver) { return forReceiver(receiver).testAssignment(keyspace, receiver); } diff --git a/src/java/org/apache/cassandra/cql3/Constants.java b/src/java/org/apache/cassandra/cql3/terms/Constants.java similarity index 93% rename from src/java/org/apache/cassandra/cql3/Constants.java rename to src/java/org/apache/cassandra/cql3/terms/Constants.java index be81bed3b0..f63d7ce28b 100644 --- a/src/java/org/apache/cassandra/cql3/Constants.java +++ b/src/java/org/apache/cassandra/cql3/terms/Constants.java @@ -15,13 +15,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.CQL3Type; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.Operation; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UpdateParameters; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.marshal.*; @@ -30,7 +36,6 @@ import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.serializers.MarshalException; -import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FastByteOperations; @@ -142,7 +147,7 @@ public abstract class Constants return UNSET_VALUE; } - public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) + public TestResult testAssignment(String keyspace, ColumnSpecification receiver) { return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } @@ -195,6 +200,7 @@ public abstract class Constants public static final Term.Terminal NULL_VALUE = new Value(null) { + // TODO: The bind overriding should be removed. A user does not have to call bind on a Terminal. @Override public Terminal bind(QueryOptions options) { @@ -420,7 +426,7 @@ public abstract class Constants this.bytes = bytes; } - public ByteBuffer get(ProtocolVersion version) + public ByteBuffer get() { return bytes; } @@ -438,41 +444,6 @@ public abstract class Constants } } - public static class Marker extends AbstractMarker - { - protected Marker(int bindIndex, ColumnSpecification receiver) - { - super(bindIndex, receiver); - assert !receiver.type.isCollection(); - } - - @Override - public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException - { - try - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value != null && value != ByteBufferUtil.UNSET_BYTE_BUFFER) - receiver.type.validate(value); - return value; - } - catch (MarshalException e) - { - throw new InvalidRequestException(e.getMessage()); - } - } - - public Value bind(QueryOptions options) throws InvalidRequestException - { - ByteBuffer bytes = bindAndGet(options); - if (bytes == null) - return null; - if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER) - return Constants.UNSET_VALUE; - return new Constants.Value(bytes); - } - } - public static class Setter extends Operation { public Setter(ColumnMetadata column, Term t) diff --git a/src/java/org/apache/cassandra/cql3/terms/InMarker.java b/src/java/org/apache/cassandra/cql3/terms/InMarker.java new file mode 100644 index 0000000000..afc22dd1de --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/terms/InMarker.java @@ -0,0 +1,168 @@ +/* + * 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.terms; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.VariableSpecifications; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.ByteBufferAccessor; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.MultiElementType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.utils.ByteBufferUtil; + +/** + * A CQL named or unnamed bind marker for an {@code IN} restriction. + * For example, 'SELECT ... WHERE pk IN ?' or 'SELECT ... WHERE pk IN :myKey '. + */ +public final class InMarker extends Terms.NonTerminals +{ + private final int bindIndex; + private final ColumnSpecification receiver; + + private InMarker(int bindIndex, ColumnSpecification receiver) + { + this.bindIndex = bindIndex; + this.receiver = receiver; + } + + @Override + public void addFunctionsTo(List functions) {} + + @Override + public void collectMarkerSpecification(VariableSpecifications boundNames) + { + boundNames.add(bindIndex, receiver); + } + + @Override + public Terminals bind(QueryOptions options) + { + ByteBuffer values = options.getValues().get(bindIndex); + if (values == null) + return null; + + if (values == ByteBufferUtil.UNSET_BYTE_BUFFER) + return UNSET_TERMINALS; + + ListType type = (ListType) receiver.type; + return toTerminals(values, type, terminalConverter(type.getElementsType())); + } + + private Terminals toTerminals(ByteBuffer value, + ListType type, + java.util.function.Function terminalConverter) + { + List elements = type.getSerializer().deserialize(value, ByteBufferAccessor.instance); + List terminals = new ArrayList<>(elements.size()); + for (T element : elements) + { + terminals.add(element == null ? null : terminalConverter.apply(type.getElementsType().decompose(element))); + } + return Terminals.of(terminals); + } + + public static java.util.function.Function terminalConverter(AbstractType type) + { + if (type instanceof MultiElementType) + return e -> MultiElements.Value.fromSerialized(e, (MultiElementType) type); + + return Constants.Value::new; + } + + @Override + public List bindAndGet(QueryOptions options) + { + Terminals terminals = bind(options); + return terminals == null ? null : terminals.get(); + } + + @Override + public List> bindAndGetElements(QueryOptions options) + { + Terminals terminals = bind(options); + return terminals == null ? null : terminals.getElements(); + } + + @Override + public boolean containsSingleTerm() + { + return false; + } + + @Override + public Term asSingleTerm() + { + throw new UnsupportedOperationException(); + } + + /** + * A raw placeholder for multiple values of the same type for a single column. + * For example, {@code SELECT ... WHERE user_id IN ?}. + *

+ * Because a single type is used, a List is used to represent the values. + */ + public static final class Raw extends Terms.Raw + { + private final int bindIndex; + + public Raw(int bindIndex) + { + this.bindIndex = bindIndex; + } + + @Override + public InMarker prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException + { + return new InMarker(bindIndex, makeInReceiver(receiver)); + } + + @Override + public TestResult testAssignment(String keyspace, ColumnSpecification receiver) + { + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + + @Override + public AbstractType getExactTypeIfKnown(String keyspace) + { + return null; + } + + @Override + public String getText() + { + return "?"; + } + + private static ColumnSpecification makeInReceiver(ColumnSpecification receiver) + { + ColumnIdentifier inName = new ColumnIdentifier("in(" + receiver.name + ')', true); + return new ColumnSpecification(receiver.ksName, receiver.cfName, inName, ListType.getInstance(receiver.type, false)); + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/cql3/Lists.java b/src/java/org/apache/cassandra/cql3/terms/Lists.java similarity index 76% rename from src/java/org/apache/cassandra/cql3/Lists.java rename to src/java/org/apache/cassandra/cql3/terms/Lists.java index 605a8bee8e..17f39ce26c 100644 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/terms/Lists.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -27,24 +27,28 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.Operation; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UpdateParameters; +import org.apache.cassandra.cql3.VariableSpecifications; import org.apache.cassandra.db.guardrails.Guardrails; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; +import org.apache.cassandra.db.marshal.MultiElementType; import org.apache.cassandra.schema.ColumnMetadata; import com.google.common.annotations.VisibleForTesting; -import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.*; import org.apache.cassandra.db.rows.*; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.db.marshal.ListType; -import org.apache.cassandra.db.marshal.ReversedType; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.serializers.CollectionSerializer; -import org.apache.cassandra.serializers.MarshalException; -import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; -import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; +import static org.apache.cassandra.cql3.terms.Constants.UNSET_VALUE; +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.TimeUUID.Generator.atUnixMillisAsBytes; @@ -65,14 +69,9 @@ public abstract class Lists return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), elementsType(column.type)); } - private static AbstractType unwrap(AbstractType type) - { - return type.isReversed() ? unwrap(((ReversedType) type).baseType) : type; - } - private static AbstractType elementsType(AbstractType type) { - return ((ListType) unwrap(type)).getElementsType(); + return ((ListType) type.unwrap()).getElementsType(); } /** @@ -163,30 +162,29 @@ public abstract class Lists { Term t = rt.prepare(keyspace, valueSpec); - if (t.containsBindMarker()) - throw new InvalidRequestException(String.format("Invalid list literal for %s: bind variables are not supported inside collection literals", receiver.name)); + checkFalse(t.containsBindMarker(), "Invalid list literal for %s: bind variables are not supported inside collection literals", receiver.name); if (t instanceof Term.NonTerminal) allTerminal = false; values.add(t); } - DelayedValue value = new DelayedValue(values); + MultiElements.DelayedValue value = new MultiElements.DelayedValue((MultiElementType) receiver.type.unwrap(), values); return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - AbstractType type = unwrap(receiver.type); + AbstractType type = receiver.type.unwrap(); if (!(type instanceof ListType)) - throw new InvalidRequestException(String.format("Invalid list literal for %s of type %s", receiver.name, receiver.type.asCQL3Type())); + throw invalidRequest("Invalid list literal for %s of type %s", receiver.name, receiver.type.asCQL3Type()); ColumnSpecification valueSpec = Lists.valueSpecOf(receiver); for (Term.Raw rt : elements) { if (!rt.testAssignment(keyspace, valueSpec).isAssignable()) - throw new InvalidRequestException(String.format("Invalid list literal for %s: value %s is not of type %s", receiver.name, rt, valueSpec.type.asCQL3Type())); + throw invalidRequest("Invalid list literal for %s: value %s is not of type %s", receiver.name, rt, valueSpec.type.asCQL3Type()); } } @@ -213,130 +211,6 @@ public abstract class Lists } } - public static class Value extends Term.MultiItemTerminal - { - public final List elements; - - public Value(List elements) - { - this.elements = elements; - } - - public static Value 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 = type.getSerializer().deserialize(value, ByteBufferAccessor.instance); - List elements = new ArrayList<>(l.size()); - for (T element : l) - // elements can be null in lists that represent a set of IN values - elements.add(element == null ? null : type.getElementsType().decompose(element)); - return new Value(elements); - } - catch (MarshalException e) - { - throw new InvalidRequestException(e.getMessage()); - } - } - - public ByteBuffer get(ProtocolVersion version) - { - return CollectionSerializer.pack(elements, elements.size()); - } - - 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.getElementsType().compare(elements.get(i), v.elements.get(i)) != 0) - return false; - - return true; - } - - public List getElements() - { - return elements; - } - } - - /** - * Basically similar to a Value, but with some non-pure function (that need - * to be evaluated at execution time) in it. - * - * Note: this would also work for a list with bind markers, but we don't support - * that because 1) it's not excessively useful and 2) we wouldn't have a good - * column name to return in the ColumnSpecification for those markers (not a - * blocker per-se but we don't bother due to 1)). - */ - public static class DelayedValue extends Term.NonTerminal - { - private final List elements; - - public DelayedValue(List elements) - { - this.elements = elements; - } - - public boolean containsBindMarker() - { - // False since we don't support them in collection - return false; - } - - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - List buffers = new ArrayList(elements.size()); - for (Term t : elements) - { - ByteBuffer bytes = t.bindAndGet(options); - - if (bytes == null) - throw new InvalidRequestException("null is not supported inside collections"); - if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - - buffers.add(bytes); - } - return new Value(buffers); - } - - public void addFunctionsTo(List functions) - { - Terms.addFunctions(elements, functions); - } - } - - /** - * A marker for List values and IN relations - */ - public static class Marker extends AbstractMarker - { - protected Marker(int bindIndex, ColumnSpecification receiver) - { - super(bindIndex, receiver); - assert receiver.type instanceof ListType; - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value == null) - return null; - if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - return Value.fromSerialized(value, (ListType) receiver.type); - } - } - /** * For prepend, we need to be able to generate unique but decreasing time * UUIDs, which is a bit challenging. To do that, given a time in milliseconds, @@ -507,10 +381,12 @@ public abstract class Lists static void doAppend(Term.Terminal value, ColumnMetadata column, UpdateParameters params) throws InvalidRequestException { + ListType type = (ListType) column.type; + if (value == null) { // for frozen lists, we're overwriting the whole cell value - if (!column.type.isMultiCell()) + if (!type.isMultiCell()) params.addTombstone(column); // If we append null, do nothing. Note that for Setter, we've @@ -518,17 +394,17 @@ public abstract class Lists return; } - List elements = ((Value) value).elements; + List elements = value.getElements(); - if (column.type.isMultiCell()) + if (type.isMultiCell()) { - if (elements.size() == 0) + if (elements.isEmpty()) return; // Guardrails about collection size are only checked for the added elements without considering // already existent elements. This is done so to avoid read-before-write, having additional checks // during SSTable write. - Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState); + Guardrails.itemsPerCollection.guard(type.collectionSize(elements), column.name.toString(), false, params.clientState); int dataSize = 0; for (ByteBuffer buffer : elements) @@ -541,8 +417,8 @@ public abstract class Lists } else { - Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState); - Cell cell = params.addCell(column, value.get(ProtocolVersion.CURRENT)); + Guardrails.itemsPerCollection.guard(type.collectionSize(elements), column.name.toString(), false, params.clientState); + Cell cell = params.addCell(column, value.get()); Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), false, params.clientState); } } @@ -562,7 +438,7 @@ public abstract class Lists if (value == null || value == UNSET_VALUE) return; - List toAdd = ((Value) value).elements; + List toAdd = value.getElements(); final int totalCount = toAdd.size(); // we have to obey MAX_NANOS per batch - in the unlikely event a client has decided to prepend a list with @@ -617,7 +493,7 @@ public abstract class Lists // Meaning that if toDiscard is big, converting it to a HashSet might be more efficient. However, // the read-before-write this operation requires limits its usefulness on big lists, so in practice // toDiscard will be small and keeping a list will be more efficient. - List toDiscard = ((Value)value).elements; + List toDiscard = value.getElements(); for (Cell cell : complexData) { if (toDiscard.contains(cell.buffer())) @@ -654,7 +530,7 @@ public abstract class Lists Row existingRow = params.getPrefetchedRow(partitionKey, params.currentClustering()); int existingSize = existingSize(existingRow, column); - int idx = ByteBufferUtil.toInt(index.get(params.options.getProtocolVersion())); + int idx = ByteBufferUtil.toInt(index.get()); if (existingSize == 0) throw new InvalidRequestException("Attempted to delete an element from a list which is null"); if (idx < 0 || idx >= existingSize) diff --git a/src/java/org/apache/cassandra/cql3/Maps.java b/src/java/org/apache/cassandra/cql3/terms/Maps.java similarity index 66% rename from src/java/org/apache/cassandra/cql3/Maps.java rename to src/java/org/apache/cassandra/cql3/terms/Maps.java index 9040d1a8d5..70871e2569 100644 --- a/src/java/org/apache/cassandra/cql3/Maps.java +++ b/src/java/org/apache/cassandra/cql3/terms/Maps.java @@ -15,70 +15,62 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; import java.util.stream.Collectors; -import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.Operation; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UpdateParameters; +import org.apache.cassandra.cql3.VariableSpecifications; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.MapType; -import org.apache.cassandra.db.marshal.ReversedType; +import org.apache.cassandra.db.marshal.MultiElementType; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.ColumnMetadata; -import org.apache.cassandra.serializers.CollectionSerializer; -import org.apache.cassandra.serializers.MarshalException; -import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Pair; -import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; +import static org.apache.cassandra.cql3.terms.Constants.UNSET_VALUE; /** * Static helper methods and classes for maps. */ -public abstract class Maps +public final class Maps { private Maps() {} public static ColumnSpecification keySpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), keysType(column.type)); + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ')', true), keysType(column.type)); } public static ColumnSpecification valueSpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), valuesType(column.type)); - } - - private static AbstractType unwrap(AbstractType type) - { - return type.isReversed() ? unwrap(((ReversedType) type).baseType) : type; + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ')', true), valuesType(column.type)); } private static AbstractType keysType(AbstractType type) { - return ((MapType) unwrap(type)).getKeysType(); + return ((MapType) type.unwrap()).getKeysType(); } private static AbstractType valuesType(AbstractType type) { - return ((MapType) unwrap(type)).getValuesType(); + return ((MapType) type.unwrap()).getValuesType(); } /** @@ -189,7 +181,10 @@ public abstract class Maps ColumnSpecification keySpec = Maps.keySpecOf(receiver); ColumnSpecification valueSpec = Maps.valueSpecOf(receiver); - Map values = new HashMap<>(entries.size()); + // In CQL maps are represented as a list of key value pairs (e.g. {k1 : v1, k2 : v2, ...}). + // Whereas, internally maps are serialized as a lists where each key is followed by its value (e.g. [k1, v1, k2, v2, ...]) + // Therefore, we must go from one format to another. + List values = new ArrayList<>(entries.size() << 1); boolean allTerminal = true; for (Pair entry : entries) { @@ -202,15 +197,16 @@ public abstract class Maps if (k instanceof Term.NonTerminal || v instanceof Term.NonTerminal) allTerminal = false; - values.put(k, v); + values.add(k); + values.add(v); } - DelayedValue value = new DelayedValue(keysType(receiver.type), values); + MultiElements.DelayedValue value = new MultiElements.DelayedValue((MultiElementType) receiver.type.unwrap(), values); return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - AbstractType type = unwrap(receiver.type); + AbstractType type = receiver.type.unwrap(); if (!(type instanceof MapType)) throw new InvalidRequestException(String.format("Invalid map literal for %s of type %s", receiver.name, receiver.type.asCQL3Type())); @@ -249,138 +245,6 @@ public abstract class Maps } } - public static class Value extends Term.Terminal - { - public final SortedMap map; - - public Value(SortedMap map) - { - this.map = map; - } - - public static Value fromSerialized(ByteBuffer value, MapType 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). - Map m = type.getSerializer().deserialize(value, ByteBufferAccessor.instance); - // We depend on Maps to be properly sorted by their keys, so use a sorted map implementation here. - SortedMap map = new TreeMap<>(type.getKeysType()); - for (Map.Entry entry : m.entrySet()) - map.put(type.getKeysType().decompose(entry.getKey()), type.getValuesType().decompose(entry.getValue())); - return new Value(map); - } - catch (MarshalException e) - { - throw new InvalidRequestException(e.getMessage()); - } - } - - @Override - public ByteBuffer get(ProtocolVersion version) - { - List buffers = new ArrayList<>(2 * map.size()); - for (Map.Entry entry : map.entrySet()) - { - buffers.add(entry.getKey()); - buffers.add(entry.getValue()); - } - return CollectionSerializer.pack(buffers, map.size()); - } - - public boolean equals(MapType mt, Value v) - { - if (map.size() != v.map.size()) - return false; - - // We use the fact that we know the maps iteration will both be in comparator order - Iterator> thisIter = map.entrySet().iterator(); - Iterator> thatIter = v.map.entrySet().iterator(); - while (thisIter.hasNext()) - { - Map.Entry thisEntry = thisIter.next(); - Map.Entry thatEntry = thatIter.next(); - if (mt.getKeysType().compare(thisEntry.getKey(), thatEntry.getKey()) != 0 || mt.getValuesType().compare(thisEntry.getValue(), thatEntry.getValue()) != 0) - return false; - } - - return true; - } - } - - // See Lists.DelayedValue - public static class DelayedValue extends Term.NonTerminal - { - private final Comparator comparator; - private final Map elements; - - public DelayedValue(Comparator comparator, Map elements) - { - this.comparator = comparator; - this.elements = elements; - } - - public boolean containsBindMarker() - { - // False since we don't support them in collection - return false; - } - - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - SortedMap buffers = new TreeMap<>(comparator); - for (Map.Entry entry : elements.entrySet()) - { - // We don't support values > 64K because the serialization format encode the length as an unsigned short. - ByteBuffer keyBytes = entry.getKey().bindAndGet(options); - - if (keyBytes == null) - throw new InvalidRequestException("null is not supported inside collections"); - if (keyBytes == ByteBufferUtil.UNSET_BYTE_BUFFER) - throw new InvalidRequestException("unset value is not supported for map keys"); - - ByteBuffer valueBytes = entry.getValue().bindAndGet(options); - if (valueBytes == null) - throw new InvalidRequestException("null is not supported inside collections"); - if (valueBytes == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - - buffers.put(keyBytes, valueBytes); - } - return new Value(buffers); - } - - public void addFunctionsTo(List functions) - { - Terms.addFunctions(elements.keySet(), functions); - Terms.addFunctions(elements.values(), functions); - } - } - - public static class Marker extends AbstractMarker - { - protected Marker(int bindIndex, ColumnSpecification receiver) - { - super(bindIndex, receiver); - assert receiver.type instanceof MapType; - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value == null) - return null; - if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - return Value.fromSerialized(value, (MapType) receiver.type); - } - } - public static class Setter extends Operation { public Setter(ColumnMetadata column, Term t) @@ -458,39 +322,42 @@ public abstract class Maps static void doPut(Term.Terminal value, ColumnMetadata column, UpdateParameters params) throws InvalidRequestException { + MapType type = (MapType) column.type; + if (value == null) { // for frozen maps, we're overwriting the whole cell - if (!column.type.isMultiCell()) + if (!type.isMultiCell()) params.addTombstone(column); return; } - SortedMap elements = ((Value) value).map; + List elements = value.getElements(); - if (column.type.isMultiCell()) + if (type.isMultiCell()) { - if (elements.size() == 0) + if (elements.isEmpty()) return; // Guardrails about collection size are only checked for the added elements without considering // already existent elements. This is done so to avoid read-before-write, having additional checks // during SSTable write. - Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState); + Guardrails.itemsPerCollection.guard(type.collectionSize(elements), column.name.toString(), false, params.clientState); int dataSize = 0; - for (Map.Entry entry : elements.entrySet()) + Iterator iter = elements.iterator(); + while(iter.hasNext()) { - Cell cell = params.addCell(column, CellPath.create(entry.getKey()), entry.getValue()); + Cell cell = params.addCell(column, CellPath.create(iter.next()), iter.next()); dataSize += cell.dataSize(); } Guardrails.collectionSize.guard(dataSize, column.name.toString(), false, params.clientState); } else { - Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState); - Cell cell = params.addCell(column, value.get(ProtocolVersion.CURRENT)); + Guardrails.itemsPerCollection.guard(type.collectionSize(elements), column.name.toString(), false, params.clientState); + Cell cell = params.addCell(column, value.get()); Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), false, params.clientState); } } @@ -512,7 +379,7 @@ public abstract class Maps if (key == Constants.UNSET_VALUE) throw new InvalidRequestException("Invalid unset map key"); - params.addTombstone(column, CellPath.create(key.get(params.options.getProtocolVersion()))); + params.addTombstone(column, CellPath.create(key.get())); } } } diff --git a/src/java/org/apache/cassandra/cql3/terms/Marker.java b/src/java/org/apache/cassandra/cql3/terms/Marker.java new file mode 100644 index 0000000000..ff0702fbc3 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/terms/Marker.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.cql3.terms; + +import java.nio.ByteBuffer; +import java.util.List; + +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.VariableSpecifications; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.MultiElementType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.serializers.MarshalException; +import org.apache.cassandra.utils.ByteBufferUtil; + +import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; + +/** + * A placeholder, also called bind marker, for a single value represented in CQL as {@code ?} for an unnamed marker or {@code :} for a named marker. + * For example, {@code SELECT ... WHERE pk = ?} or {@code SELECT ... WHERE pk = :myKey}. + */ +public final class Marker extends Term.NonTerminal +{ + /** + * The index of the bind variable within the query options. + *

When a query is executed the value of this placeholder is retrieved from the query options values using + * the bindIndex. (see {@link Marker#bind(QueryOptions)}

+ */ + private final int bindIndex; + + private final ColumnSpecification receiver; + + private Marker(int bindIndex, ColumnSpecification receiver) + { + this.bindIndex = bindIndex; + this.receiver = receiver; + } + + @Override + public void collectMarkerSpecification(VariableSpecifications boundNames) + { + boundNames.add(bindIndex, receiver); + } + + @Override + public boolean containsBindMarker() + { + return true; + } + + @Override + public void addFunctionsTo(List functions) + { + } + + @Override + public Term.Terminal bind(QueryOptions options) throws InvalidRequestException + { + try + { + ByteBuffer bytes = options.getValues().get(bindIndex); + if (bytes == null) + return null; + + if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER) + { + checkFalse(receiver.type.isTuple(), "Invalid unset value for tuple %s", receiver.name); + return Constants.UNSET_VALUE; + } + + if (receiver.type instanceof MultiElementType) + return MultiElements.Value.fromSerialized(bytes, (MultiElementType) receiver.type); + + receiver.type.validate(bytes); + return new Constants.Value(bytes); + } + catch (MarshalException e) + { + throw new InvalidRequestException(e.getMessage(), e); + } + } + + /** + * A parsed, but non prepared, bind marker. + */ + public static class Raw extends Term.Raw + { + /** + * The index of the bind variable within the query options. + */ + private final int bindIndex; + + public Raw(int bindIndex) + { + this.bindIndex = bindIndex; + } + + @Override + public NonTerminal prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException + { + return new Marker(bindIndex, receiver); + } + + @Override + public TestResult testAssignment(String keyspace, ColumnSpecification receiver) + { + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + + @Override + public AbstractType getExactTypeIfKnown(String keyspace) + { + return null; + } + + @Override + public String getText() + { + return "?"; + } + } +} diff --git a/src/java/org/apache/cassandra/cql3/terms/MultiElements.java b/src/java/org/apache/cassandra/cql3/terms/MultiElements.java new file mode 100644 index 0000000000..7136640d2c --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/terms/MultiElements.java @@ -0,0 +1,166 @@ +/* + * 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.terms; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.VariableSpecifications; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.db.marshal.MultiElementType; +import org.apache.cassandra.serializers.MarshalException; + +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + +/** + * Static classes for Terms representing individual values of multi-elements data types. + */ +public final class MultiElements +{ + private MultiElements() + { + } + + /** + * The serialized elements of a multi-element type (collection, tuple, udt, ...) + */ + public static class Value extends Term.Terminal + { + /** + * The type represented by this {@code Value} + */ + private final MultiElementType type; + + /** + * The serialized values of the elements composing this value. + */ + private final List elements; + + /** + * Creates a {@code Value} from its serialized representation. + * + * @param value a serialized value from the specified type + * @param type the value type + * @return a {@code Value} + */ + public static Value fromSerialized(ByteBuffer value, MultiElementType type) + { + try + { + // We depend for SetType and MapType on the collections being without duplicated keys and sorted. + return new Value(type, type.filterSortAndValidateElements(type.unpack(value))); + } + catch (MarshalException e) + { + throw invalidRequest(e.getMessage()); + } + } + + public Value(MultiElementType type, List elements) + { + this.type = type; + this.elements = elements; + } + + @Override + public ByteBuffer get() + { + return type.pack(elements); + } + + @Override + public List getElements() + { + return elements; + } + } + + /** + * The terms representing a multi-element value (collection, tuple, udt, ...) where at least one of the terms + * represents a non-pure function or a bind marker. + */ + public static class DelayedValue extends Term.NonTerminal + { + /** + * The type of this value. + */ + private final MultiElementType type; + + /** + * The terms representing the elements composing this value. + */ + private final List elements; + + public DelayedValue(MultiElementType type, List elements) + { + this.type = type; + this.elements = elements; + } + + @Override + public void collectMarkerSpecification(VariableSpecifications boundNames) + { + if (type.supportsElementBindMarkers()) + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).collectMarkerSpecification(boundNames); + } + } + + @Override + public Terminal bind(QueryOptions options) + { + try + { + List buffers = new ArrayList<>(elements.size()); + for (Term t : elements) + { + buffers.add(t.bindAndGet(options)); + } + + buffers = type.filterSortAndValidateElements(buffers); + return new Value(type, buffers); + } + catch (MarshalException e) + { + throw invalidRequest(e.getMessage()); + } + } + + @Override + public boolean containsBindMarker() + { + if (type.supportsElementBindMarkers()) + return false; + + for (Term element : elements) + if (element.containsBindMarker()) + return true; + return false; + } + + @Override + public void addFunctionsTo(List functions) + { + Terms.addFunctions(elements, functions); + } + } +} diff --git a/src/java/org/apache/cassandra/cql3/Sets.java b/src/java/org/apache/cassandra/cql3/terms/Sets.java similarity index 68% rename from src/java/org/apache/cassandra/cql3/Sets.java rename to src/java/org/apache/cassandra/cql3/terms/Sets.java index 00d6870a92..deb4adc45b 100644 --- a/src/java/org/apache/cassandra/cql3/Sets.java +++ b/src/java/org/apache/cassandra/cql3/terms/Sets.java @@ -15,61 +15,53 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; -import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; +import static org.apache.cassandra.cql3.terms.Constants.UNSET_VALUE; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.Operation; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UpdateParameters; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.MapType; -import org.apache.cassandra.db.marshal.ReversedType; +import org.apache.cassandra.db.marshal.MultiElementType; import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.ColumnMetadata; -import org.apache.cassandra.serializers.CollectionSerializer; -import org.apache.cassandra.serializers.MarshalException; -import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; /** * Static helper methods and classes for sets. */ -public abstract class Sets +public final class Sets { private Sets() {} public static ColumnSpecification valueSpecOf(ColumnSpecification column) { - return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), elementsType(column.type)); - } - - private static AbstractType unwrap(AbstractType type) - { - return type.isReversed() ? unwrap(((ReversedType) type).baseType) : type; + return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ')', true), elementsType(column.type)); } private static AbstractType elementsType(AbstractType type) { - return ((SetType) unwrap(type)).getElementsType(); + return ((SetType) type.unwrap()).getElementsType(); } /** @@ -162,10 +154,10 @@ public abstract class Sets // We've parsed empty maps as a set literal to break the ambiguity so // handle that case now if (receiver.type instanceof MapType && elements.isEmpty()) - return new Maps.Value(Collections.emptySortedMap()); + return new MultiElements.Value(((MapType) receiver.type), Collections.emptyList()); ColumnSpecification valueSpec = Sets.valueSpecOf(receiver); - Set values = new HashSet<>(elements.size()); + List values = new ArrayList<>(elements.size()); boolean allTerminal = true; for (Term.Raw rt : elements) { @@ -179,13 +171,13 @@ public abstract class Sets values.add(t); } - DelayedValue value = new DelayedValue(elementsType(receiver.type), values); + MultiElements.DelayedValue value = new MultiElements.DelayedValue((MultiElementType) receiver.type.unwrap(), values); return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - AbstractType type = unwrap(receiver.type); + AbstractType type = receiver.type.unwrap(); if (!(type instanceof SetType)) { @@ -228,118 +220,6 @@ public abstract class Sets } } - public static class Value extends Term.Terminal - { - public final SortedSet elements; - - public Value(SortedSet elements) - { - this.elements = elements; - } - - public static Value fromSerialized(ByteBuffer value, SetType 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). - Set s = type.getSerializer().deserialize(value, ByteBufferAccessor.instance); - SortedSet elements = new TreeSet<>(type.getElementsType()); - for (T element : s) - elements.add(type.getElementsType().decomposeUntyped(element)); - return new Value(elements); - } - catch (MarshalException e) - { - throw new InvalidRequestException(e.getMessage()); - } - } - - public ByteBuffer get(ProtocolVersion version) - { - return CollectionSerializer.pack(elements, elements.size()); - } - - public boolean equals(SetType st, Value v) - { - if (elements.size() != v.elements.size()) - return false; - - Iterator thisIter = elements.iterator(); - Iterator thatIter = v.elements.iterator(); - AbstractType elementsType = st.getElementsType(); - while (thisIter.hasNext()) - if (elementsType.compare(thisIter.next(), thatIter.next()) != 0) - return false; - - return true; - } - } - - // See Lists.DelayedValue - public static class DelayedValue extends Term.NonTerminal - { - private final Comparator comparator; - private final Set elements; - - public DelayedValue(Comparator comparator, Set elements) - { - this.comparator = comparator; - this.elements = elements; - } - - public boolean containsBindMarker() - { - // False since we don't support them in collection - return false; - } - - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - SortedSet buffers = new TreeSet<>(comparator); - for (Term t : elements) - { - ByteBuffer bytes = t.bindAndGet(options); - - if (bytes == null) - throw new InvalidRequestException("null is not supported inside collections"); - if (bytes == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - - buffers.add(bytes); - } - return new Value(buffers); - } - - public void addFunctionsTo(List functions) - { - Terms.addFunctions(elements, functions); - } - } - - public static class Marker extends AbstractMarker - { - protected Marker(int bindIndex, ColumnSpecification receiver) - { - super(bindIndex, receiver); - assert receiver.type instanceof SetType; - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value == null) - return null; - if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - return Value.fromSerialized(value, (SetType) receiver.type); - } - } - public static class Setter extends Operation { public Setter(ColumnMetadata column, Term t) @@ -377,26 +257,28 @@ public abstract class Sets static void doAdd(Term.Terminal value, ColumnMetadata column, UpdateParameters params) throws InvalidRequestException { + SetType type = (SetType) column.type; + if (value == null) { // for frozen sets, we're overwriting the whole cell - if (!column.type.isMultiCell()) + if (!type.isMultiCell()) params.addTombstone(column); return; } - SortedSet elements = ((Value) value).elements; + List elements = value.getElements(); - if (column.type.isMultiCell()) + if (type.isMultiCell()) { - if (elements.size() == 0) + if (elements.isEmpty()) return; // Guardrails about collection size are only checked for the added elements without considering // already existent elements. This is done so to avoid read-before-write, having additional checks // during SSTable write. - Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState); + Guardrails.itemsPerCollection.guard(type.collectionSize(elements), column.name.toString(), false, params.clientState); int dataSize = 0; for (ByteBuffer bb : elements) @@ -411,8 +293,8 @@ public abstract class Sets } else { - Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState); - Cell cell = params.addCell(column, value.get(ProtocolVersion.CURRENT)); + Guardrails.itemsPerCollection.guard(type.collectionSize(elements), column.name.toString(), false, params.clientState); + Cell cell = params.addCell(column, value.get()); Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), false, params.clientState); } } @@ -435,9 +317,7 @@ public abstract class Sets return; // This can be either a set or a single element - Set toDiscard = value instanceof Sets.Value - ? ((Sets.Value)value).elements - : Collections.singleton(value.get(params.options.getProtocolVersion())); + List toDiscard = value.getElements(); for (ByteBuffer bb : toDiscard) params.addTombstone(column, CellPath.create(bb)); @@ -458,7 +338,7 @@ public abstract class Sets if (elt == null) throw new InvalidRequestException("Invalid null set element"); - params.addTombstone(column, CellPath.create(elt.get(params.options.getProtocolVersion()))); + params.addTombstone(column, CellPath.create(elt.get())); } } } diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/terms/Term.java similarity index 52% rename from src/java/org/apache/cassandra/cql3/Term.java rename to src/java/org/apache/cassandra/cql3/terms/Term.java index c94b6141af..494a2c1473 100644 --- a/src/java/org/apache/cassandra/cql3/Term.java +++ b/src/java/org/apache/cassandra/cql3/terms/Term.java @@ -15,21 +15,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.List; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.CQLFragmentParser; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.CqlParser; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.VariableSpecifications; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.schema.SchemaConstants; /** * A CQL3 term, i.e. a column value with or without bind variables. - * - * A Term can be either terminal or non terminal. A term object is one that is typed and is obtained - * from a raw term (Term.Raw) by poviding the actual receiver to which the term is supposed to be a + *

+ * A Term can be either {@link Terminal} or {@link NonTerminal}. A term object is one that is typed and is obtained + * from a raw term (Term.Raw) by providing the actual {@link ColumnSpecification} receiver to which the term is supposed to be a * value of. */ public interface Term @@ -41,55 +49,82 @@ public interface Term * @param boundNames the variables specification where to collect the * bind variables of this term in. */ - public void collectMarkerSpecification(VariableSpecifications boundNames); + void collectMarkerSpecification(VariableSpecifications boundNames); /** - * Bind the values in this term to the values contained in {@code values}. + * Bind the values in this term to the values contained in the {@code options}. * This is obviously a no-op if the term is Terminal. * * @param options the values to bind markers to. - * @return the result of binding all the variables of this NonTerminal (or - * 'this' if the term is terminal). + * @return the {@code Terminal} resulting of binding the values contained in the {@code options}. */ - public Terminal bind(QueryOptions options) throws InvalidRequestException; + Terminal bind(QueryOptions options); /** - * A shorter for bind(values).get(). - * We expose it mainly because for constants it can avoids allocating a temporary + * A shorter for {@code bind(options).get()}. + * We expose it mainly because for constants it can avoid allocating a temporary * object between the bind and the get (note that we still want to be able * to separate bind and get for collections). */ - public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException; + ByteBuffer bindAndGet(QueryOptions options); /** - * Whether or not that term contains at least one bind marker. - * + * A shorter for {@code bind(options).getElements()}. + * We expose it mainly because for constants it can avoid allocating a temporary + * object between the bind and the getElements. + */ + List bindAndGetElements(QueryOptions options); + + /** + * Whether that term contains at least one bind marker. + *

* Note that this is slightly different from being or not a NonTerminal, - * because calls to non pure functions will be NonTerminal (see #5616) + * because calls to non-pure functions will be {@code NonTerminal} (see #5616) * even if they don't have bind markers. */ - public abstract boolean containsBindMarker(); + boolean containsBindMarker(); /** * Whether that term is terminal (this is a shortcut for {@code this instanceof Term.Terminal}). */ - default public boolean isTerminal() + default boolean isTerminal() { return false; // overriden below by Terminal } - public void addFunctionsTo(List functions); + /** + * Adds the functions used by this {@link Term} to the list of functions. + *

+ * This method is used to discover prepare statements function dependencies on schema updates. + * @param functions the list of functions to add to + */ + void addFunctionsTo(List functions); + + /** + * Converts the term represented by the specified {@code String} into its binary representation. + * @param term the term to convert + * @param type the type of the term + * @return the term binary representation + */ + static ByteBuffer asBytes(String keyspace, String term, AbstractType type) + { + ColumnSpecification receiver = new ColumnSpecification(keyspace, SchemaConstants.DUMMY_KEYSPACE_OR_TABLE_NAME, new ColumnIdentifier("(dummy)", true), type); + Term.Raw rawTerm = CQLFragmentParser.parseAny(CqlParser::term, term, "CQL term"); + return rawTerm.prepare(keyspace, receiver).bindAndGet(QueryOptions.DEFAULT); + } /** * A parsed, non prepared (thus untyped) term. - * + *

* This can be one of: - * - a constant - * - a collection literal - * - a function call - * - a marker + *

    + *
  • a constant
  • + *
  • a multi-element type literal
  • + *
  • a function call
  • + *
  • a marker
  • + *
*/ - public abstract class Raw implements AssignmentTestable + abstract class Raw implements AssignmentTestable { /** * This method validates this RawTerm is valid for provided column @@ -144,36 +179,38 @@ public interface Term } } - public abstract class MultiColumnRaw extends Term.Raw - { - public abstract Term prepare(String keyspace, List receiver) throws InvalidRequestException; - } - /** * A terminal term, one that can be reduced to a byte buffer directly. - * + *

* This includes most terms that don't have a bind marker (an exception - * being delayed call for non pure function that are NonTerminal even + * being delayed call for non-pure function that are NonTerminal even * if they don't have bind markers). - * + *

* This can be only one of: - * - a constant value - * - a collection value - * + *

    + *
  • a constant value
  • + *
  • a multi-element value
  • + *
+ *

* Note that a terminal term will always have been type checked, and thus * consumer can (and should) assume so. */ - public abstract class Terminal implements Term + abstract class Terminal implements Term { + @Override public void collectMarkerSpecification(VariableSpecifications boundNames) {} + + @Override public Terminal bind(QueryOptions options) { return this; } + @Override public void addFunctionsTo(List functions) { } // While some NonTerminal may not have bind markers, no Term can be Terminal // with a bind marker + @Override public boolean containsBindMarker() { return false; @@ -188,35 +225,59 @@ public interface Term /** * @return the serialized value of this terminal. */ - public abstract ByteBuffer get(ProtocolVersion version) throws InvalidRequestException; + public abstract ByteBuffer get(); - public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException + /** + * Returns the serialized values of this Term elements, if this term represents a Collection, Tuple or UDT. + * If this term does not represent a multi-elements type it will return a list containing the serialized value + * of this terminal + * @return a list containing serialized values of this Term elements + */ + public List getElements() { - return get(options.getProtocolVersion()); + ByteBuffer value = get(); + return value == null ? Collections.emptyList() : Collections.singletonList(value); } - } - public abstract class MultiItemTerminal extends Terminal - { - public abstract List getElements(); + @Override + public ByteBuffer bindAndGet(QueryOptions options) + { + return get(); + } + + @Override + public List bindAndGetElements(QueryOptions options) + { + return getElements(); + } } /** - * A non terminal term, i.e. a term that can only be reduce to a byte buffer + * A non-terminal term, i.e. a term that can only be reduced to a byte buffer * at execution time. - * + *

* We have the following type of NonTerminal: - * - marker for a constant value - * - marker for a collection value (list, set, map) - * - a function having bind marker - * - a non pure function (even if it doesn't have bind marker - see #5616) + *

    + *
  • marker for a constant value
  • + *
  • marker for a multi-element data type value (list, set, map, tuple, udt, vector)
  • + *
  • a function having bind markers
  • + *
  • a non-pure function (even if it doesn't have bind markers - see #5616)
  • + *
*/ - public abstract class NonTerminal implements Term + abstract class NonTerminal implements Term { + @Override public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException { Terminal t = bind(options); - return t == null ? null : t.get(options.getProtocolVersion()); + return t == null ? null : t.get(); + } + + @Override + public List bindAndGetElements(QueryOptions options) + { + Terminal t = bind(options); + return t == null ? Collections.emptyList() : t.getElements(); } } } diff --git a/src/java/org/apache/cassandra/cql3/terms/Terms.java b/src/java/org/apache/cassandra/cql3/terms/Terms.java new file mode 100644 index 0000000000..0ebb78a615 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/terms/Terms.java @@ -0,0 +1,567 @@ +/* + * 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.terms; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.stream.Collectors; + +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.VariableSpecifications; +import org.apache.cassandra.cql3.terms.Term.NonTerminal; +import org.apache.cassandra.cql3.terms.Term.Terminal; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * A set of {@code Terms} + */ +public interface Terms +{ + /** + * The {@code List} returned when the list was not set. + */ + @SuppressWarnings("rawtypes") + List UNSET_LIST = new AbstractList() + { + @Override + public Object get(int index) + { + throw new UnsupportedOperationException(); + } + + @Override + public int size() + { + throw new UnsupportedOperationException(); + } + }; + + /** + * The terminals returned when they were unset. + */ + Terminals UNSET_TERMINALS = new Terminals() + { + @Override + @SuppressWarnings("unchecked") + public List get() + { + return (List) UNSET_LIST; + } + + @Override + @SuppressWarnings("unchecked") + public List> getElements() + { + return (List>) UNSET_LIST; + } + + @Override + public List asList() + { + throw new UnsupportedOperationException(); + } + + @Override + public void addFunctionsTo(List functions) + { + } + + @Override + public boolean containsSingleTerm() + { + return false; + } + + @Override + public Term asSingleTerm() + { + throw new UnsupportedOperationException(); + } + }; + + /** + * Adds all functions (native and user-defined) used by any of the terms to the specified list. + * @param functions the list to add to + */ + void addFunctionsTo(List functions); + + /** + * Collects the column specifications for the bind variables in the terms. + * This is obviously a no-op if the terms are Terminals. + * + * @param boundNames the variables specification where to collect the + * bind variables of the terms in. + */ + void collectMarkerSpecification(VariableSpecifications boundNames); + + /** + * Bind the values in these terms to the values contained in {@code options}. + * This is obviously a no-op if this {@code Terms} are Terminals. + * + * @param options the query options containing the values to bind markers to. + * @return the result of binding all the variables of these NonTerminals. + */ + Terminals bind(QueryOptions options); + + /** + * A shorter for {@code bind(options).get()}. + * We expose it mainly because for constants it can avoid allocating a temporary + * object between the bind and the get. + * @param options the query options containing the values to bind markers to. + */ + List bindAndGet(QueryOptions options); + + /** + * A shorter for {@code bind(options).getElements()}. + * We expose it mainly because for constants it can avoid allocating a temporary + * object between the {@code bind} and the {@code getElements}. + * @param options the query options containing the values to bind markers to. + */ + List> bindAndGetElements(QueryOptions options); + + /** + * Creates a {@code Terms} containing a single {@code Term}. + * + * @param term the {@code Term} + * @return a {@code Terms} containing a single {@code Term}. + */ + static Terms of(final Term term) + { + if (term.isTerminal()) + return Terminals.of(term == Constants.NULL_VALUE ? null : (Terminal) term); + + return NonTerminals.of((NonTerminal) term); + } + + /** + * Creates a {@code Terms} containing a set of {@code Term}. + * + * @param terms the terms + * @return a {@code Terms} containing a set of {@code Term}. + */ + static Terms of(final List terms) + { + boolean allTerminals = terms.stream().allMatch(Term::isTerminal); + + if (allTerminals) + { + int size = terms.size(); + List terminals = new ArrayList<>(size); + for (int i = 0; i < size; i++) + { + Terminal terminal = (Terminal) terms.get(i); + terminals.add(terminal == Constants.NULL_VALUE ? null : terminal); + } + return Terminals.of(terminals); + } + + return NonTerminals.of(terms); + } + + /** + * Checks if these {@code terms} knows that it contains a single {@code term}. + *

+ * If the instance is a marker it will not know how many terms it represents and will return false. + * @return {@code true} if this {@code terms} know contains a single {@code term}, {@code false} otherwise. + */ + boolean containsSingleTerm(); + + /** + * If this {@code terms} contains a single term it will be returned otherwise an UnsupportedOperationException will be thrown. + * @return a single term representing the single element of this {@code terms}. + * @throws UnsupportedOperationException if this term does not know how many terms it contains or contains more than one term + */ + Term asSingleTerm(); + + /** + * Adds all functions (native and user-defined) of the specified terms to the list. + * @param functions the list to add to + */ + static void addFunctions(Iterable terms, List functions) + { + for (Term term : terms) + { + if (term != null) + term.addFunctionsTo(functions); + } + } + + /** + * A parsed, non prepared (thus untyped) set of terms. + */ + abstract class Raw implements AssignmentTestable + { + /** + * This method validates this {@code Terms.Raw} is valid for the provided column + * specification and "prepare" this {@code Terms.Raw}, returning the resulting {@link Terms}. + * + * @param receiver the "column" the set of terms are supposed to be a value of. Note + * that the ColumnSpecification may not correspond to a real column. + * @return the prepared terms + */ + public abstract Terms prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException; + + /** + * @return a String representation of the raw terms that can be used when reconstructing a CQL query string. + */ + public abstract String getText(); + + /** + * The type of the {@code Terms} if it can be inferred. + * + * @param keyspace the keyspace on which the statement containing these terms is on. + * @return the type of this {@code Terms} if inferrable, or {@code null} + * otherwise (for instance, the type isn't inferrable for a bind marker. Even for + * literals, the exact type is not inferrable since they are valid for many + * different types and so this will return {@code null} too). + */ + public abstract AbstractType getExactTypeIfKnown(String keyspace); + + @Override + public AbstractType getCompatibleTypeIfKnown(String keyspace) + { + return getExactTypeIfKnown(keyspace); + } + + @Override + public String toString() + { + return getText(); + } + + public static Raw of(List raws) + { + return new Raw() + { + @Override + public Terms prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException + { + List terms = new ArrayList<>(raws.size()); + for (Term.Raw raw : raws) + { + terms.add(raw.prepare(keyspace, receiver)); + } + return Terms.of(terms); + } + + @Override + public String getText() + { + return raws.stream().map(Term.Raw::getText) + .collect(Collectors.joining(", ", "(", ")")); + } + + @Override + public AbstractType getExactTypeIfKnown(String keyspace) + { + return null; + } + + @Override + public TestResult testAssignment(String keyspace, ColumnSpecification receiver) + { + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + }; + } + } + + /** + * Set of terms that contains only terminal terms. + */ + abstract class Terminals implements Terms + { + @Override + public void collectMarkerSpecification(VariableSpecifications boundNames) {} + + @Override + public final Terminals bind(QueryOptions options) + { + return this; + } + + @Override + public List bindAndGet(QueryOptions options) + { + return get(); + } + + @Override + public List> bindAndGetElements(QueryOptions options) + { + return getElements(); + } + + /** + * @return the serialized values of this {@code Terminals}. + */ + public abstract List get(); + + /** + * Returns the serialized values of each Term elements, if these term represents a Collection, tuple, UDT or vector. + * If the terms do not represent multi-elements type the method will return a list containing the serialized value of the terminals + * @return a list containing serialized values of each Term elements + */ + public abstract List> getElements(); + + /** + * Converts these {@code Terminals} into a {@code List} of {@code Term.Terminal}. + * @return a {@code List} of {@code Term.Terminal}. + */ + public abstract List asList(); + + /** + * Converts a {@code Terminal} into a {@code Terminals}. + * @param terminal the {@code Terminal} to convert + * @return a {@code Terminals}. + */ + public static Terminals of(Terminal terminal) + { + return new Terminals() + { + @Override + public List get() + { + return Collections.singletonList(terminal == null ? null : terminal.get()); + } + + @Override + public List> getElements() + { + return Collections.singletonList(terminal == null ? null : terminal.getElements()); + } + + @Override + public List asList() + { + return Collections.singletonList(terminal); + } + + @Override + public void addFunctionsTo(List functions) + { + if (terminal != null) + terminal.addFunctionsTo(functions); + } + + @Override + public boolean containsSingleTerm() + { + return true; + } + + @Override + public Term asSingleTerm() + { + return terminal; + } + }; + } + + public static Terminals of(List terminals) + { + return new Terminals() + { + @Override + public List asList() + { + return terminals; + } + + @Override + public List get() + { + int size = terminals.size(); + List buffers = new ArrayList<>(size); + for (int i = 0; i < size; i++) + { + Terminal terminal = terminals.get(i); + buffers.add(terminal == null ? null : terminal.get()); + } + return buffers; + } + + @Override + public List> getElements() + { + int size = terminals.size(); + List> buffers = new ArrayList<>(size); + for (int i = 0; i < size; i++) + { + Terminal terminal = terminals.get(i); + buffers.add(terminal == null ? null : terminal.getElements()); + } + return buffers; + } + + @Override + public void addFunctionsTo(List functions) + { + addFunctions(terminals, functions); + } + + @Override + public boolean containsSingleTerm() + { + return terminals.size() == 1; + } + + @Override + public Terminal asSingleTerm() + { + if (!containsSingleTerm()) + throw new UnsupportedOperationException("This terms content cannot be converted in a single term"); + return terminals.get(0); + } + }; + } + } + + /** + * Set of terms that contains at least one non-terminal term. + */ + abstract class NonTerminals implements Terms + { + public static NonTerminals of(NonTerminal term) + { + return new NonTerminals() + { + @Override + public void addFunctionsTo(List functions) + { + term.addFunctionsTo(functions); + } + + @Override + public void collectMarkerSpecification(VariableSpecifications boundNames) + { + term.collectMarkerSpecification(boundNames); + } + + @Override + public Terminals bind(QueryOptions options) + { + return Terminals.of(term.bind(options)); + } + + @Override + public List bindAndGet(QueryOptions options) + { + return Collections.singletonList(term.bindAndGet(options)); + } + + @Override + public List> bindAndGetElements(QueryOptions options) + { + return Collections.singletonList(term.bindAndGetElements(options)); + } + + @Override + public boolean containsSingleTerm() + { + return true; + } + + @Override + public Term.NonTerminal asSingleTerm() + { + return term; + } + }; + } + + public static NonTerminals of(List terms) + { + return new NonTerminals() + { + @Override + public void addFunctionsTo(List functions) + { + addFunctions(terms, functions); + } + + @Override + public void collectMarkerSpecification(VariableSpecifications boundNames) + { + for (int i = 0, m = terms.size(); i < m; i++) + { + Term term = terms.get(i); + term.collectMarkerSpecification(boundNames); + } + } + + @Override + public Terminals bind(QueryOptions options) + { + int size = terms.size(); + List terminals = new ArrayList<>(size); + for (int i = 0; i < size; i++) + { + Term term = terms.get(i); + terminals.add(term.bind(options)); + } + return Terminals.of(terminals); + } + + @Override + public List bindAndGet(QueryOptions options) + { + int size = terms.size(); + List buffers = new ArrayList<>(size); + for (int i = 0; i < size; i++) + { + Term term = terms.get(i); + buffers.add(term.bindAndGet(options)); + } + return buffers; + } + + @Override + public List> bindAndGetElements(QueryOptions options) + { + int size = terms.size(); + List> buffers = new ArrayList<>(size); + for (int i = 0; i < size; i++) + { + Term term = terms.get(i); + buffers.add(term.bindAndGetElements(options)); + } + return buffers; + } + + @Override + public boolean containsSingleTerm() + { + return terms.size() == 1; + } + + @Override + public Term asSingleTerm() + { + if (!containsSingleTerm()) + throw new UnsupportedOperationException("This Terms cannot be converted in a single Term"); + return terms.get(0); + } + }; + } + } +} diff --git a/src/java/org/apache/cassandra/cql3/terms/Tuples.java b/src/java/org/apache/cassandra/cql3/terms/Tuples.java new file mode 100644 index 0000000000..9458335677 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/terms/Tuples.java @@ -0,0 +1,251 @@ +/* + * 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.terms; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.ReversedType; +import org.apache.cassandra.db.marshal.TupleType; +import org.apache.cassandra.exceptions.InvalidRequestException; + +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + +/** + * Static helper methods and classes for tuples. + */ +public final class Tuples +{ + private Tuples() {} + + public static ColumnSpecification componentSpecOf(ColumnSpecification column, int component) + { + return new ColumnSpecification(column.ksName, + column.cfName, + new ColumnIdentifier(String.format("%s[%d]", column.name, component), true), + (getTupleType(column.type)).type(component)); + } + + public static ColumnSpecification makeReceiver(List receivers) + { + List> 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); + } + + /** + * 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 extends Term.Raw + { + private final List elements; + + public Literal(List elements) + { + this.elements = elements; + } + + public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException + { + // The parser cannot differentiate between a tuple with one element and a term between parenthesis. + // By consequence, we need to wait until we know the target type to determine which one it is. + if (elements.size() == 1 && !checkIfTupleType(receiver.type)) + return elements.get(0).prepare(keyspace, receiver); + + TupleType tupleType = getTupleType(receiver.type); + + if (elements.size() != tupleType.size()) + throw invalidRequest("Expected %d elements in value for tuple %s, but got %d: %s", + tupleType.size(), receiver.name, elements.size(), this); + + validateTupleAssignableTo(receiver, elements); + + List values = new ArrayList<>(elements.size()); + boolean allTerminal = true; + for (int i = 0; i < elements.size(); i++) + { + Term value = elements.get(i).prepare(keyspace, componentSpecOf(receiver, i)); + if (value instanceof Term.NonTerminal) + allTerminal = false; + + values.add(value); + } + + MultiElements.DelayedValue value = new MultiElements.DelayedValue(tupleType, values); + return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; + } + + public TestResult testAssignment(String keyspace, ColumnSpecification receiver) + { + // The parser cannot differentiate between a tuple with one element and a term between parenthesis. + // By consequence, we need to wait until we know the target type to determine which one it is. + if (elements.size() == 1 && !checkIfTupleType(receiver.type)) + return elements.get(0).testAssignment(keyspace, receiver); + + return testTupleAssignment(receiver, elements); + } + + @Override + public AbstractType getExactTypeIfKnown(String keyspace) + { + List> types = new ArrayList<>(elements.size()); + for (Term.Raw term : elements) + { + AbstractType type = term.getExactTypeIfKnown(keyspace); + if (type == null) + return null; + types.add(type); + } + return new TupleType(types); + } + + public String getText() + { + return tupleToString(elements, Term.Raw::getText); + } + } + + /** + * Create a String representation of the tuple containing the specified elements. + * + * @param elements the tuple elements + * @return a String representation of the tuple + */ + public static String tupleToString(List elements) + { + return tupleToString(elements, Object::toString); + } + + /** + * Create a String representation of the tuple from the specified items associated to + * the tuples elements. + * + * @param items items associated to the tuple elements + * @param mapper the mapper used to map the items to the String representation of the tuple elements + * @return a String representation of the tuple + */ + public static String tupleToString(Iterable items, java.util.function.Function mapper) + { + return StreamSupport.stream(items.spliterator(), false) + .map(mapper) + .collect(Collectors.joining(", ", "(", ")")); + } + + /** + * Returns the exact TupleType from the items if it can be known. + * + * @param items the items mapped to the tuple elements + * @param mapper the mapper used to retrieve the element types from the items + * @return the exact TupleType from the items if it can be known or null + */ + public static TupleType getExactTupleTypeIfKnown(List items, + java.util.function.Function> mapper) + { + List> types = new ArrayList<>(items.size()); + for (T item : items) + { + AbstractType type = mapper.apply(item); + if (type == null) + return null; + types.add(type); + } + return new TupleType(types); + } + + /** + * Checks if the tuple with the specified elements can be assigned to the specified column. + * + * @param receiver the receiving column + * @param elements the tuple elements + * @throws InvalidRequestException if the tuple cannot be assigned to the specified column. + */ + public static void validateTupleAssignableTo(ColumnSpecification receiver, + List elements) + { + if (!checkIfTupleType(receiver.type)) + throw invalidRequest("Invalid tuple type literal for %s of type %s", receiver.name, receiver.type.asCQL3Type()); + + TupleType tt = getTupleType(receiver.type); + for (int i = 0; i < elements.size(); i++) + { + if (i >= tt.size()) + { + throw invalidRequest("Invalid tuple literal for %s: too many elements. Type %s expects %d but got %d", + receiver.name, tt.asCQL3Type(), tt.size(), elements.size()); + } + + AssignmentTestable value = elements.get(i); + ColumnSpecification spec = componentSpecOf(receiver, i); + if (!value.testAssignment(receiver.ksName, spec).isAssignable()) + throw invalidRequest("Invalid tuple literal for %s: component %d is not of type %s", + receiver.name, i, spec.type.asCQL3Type()); + } + } + + /** + * Tests that the tuple with the specified elements can be assigned to the specified column. + * + * @param receiver the receiving column + * @param elements the tuple elements + */ + public static AssignmentTestable.TestResult testTupleAssignment(ColumnSpecification receiver, + List elements) + { + try + { + validateTupleAssignableTo(receiver, elements); + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + } + catch (InvalidRequestException e) + { + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; + } + } + + public static boolean checkIfTupleType(AbstractType tuple) + { + return (tuple instanceof TupleType) || + (tuple instanceof ReversedType && ((ReversedType) tuple).baseType instanceof TupleType); + + } + + public static TupleType getTupleType(AbstractType tuple) + { + return (tuple instanceof ReversedType ? ((TupleType) ((ReversedType) tuple).baseType) : (TupleType)tuple); + } +} diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/terms/UserTypes.java similarity index 68% rename from src/java/org/apache/cassandra/cql3/UserTypes.java rename to src/java/org/apache/cassandra/cql3/terms/UserTypes.java index 76276a70de..5fce8e64be 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/terms/UserTypes.java @@ -15,27 +15,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.nio.ByteBuffer; import java.util.*; import java.util.stream.Collectors; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.FieldIdentifier; +import org.apache.cassandra.cql3.Operation; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UpdateParameters; import org.apache.cassandra.schema.ColumnMetadata; -import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.transport.ProtocolVersion; -import org.apache.cassandra.utils.ByteBufferUtil; -import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; +import static org.apache.cassandra.cql3.terms.Constants.UNSET_VALUE; /** * Static helper methods and classes for user types. */ -public abstract class UserTypes +public final class UserTypes { private UserTypes() {} @@ -161,7 +165,7 @@ public abstract class UserTypes } } - DelayedValue value = new DelayedValue(((UserType)receiver.type), values); + MultiElements.DelayedValue value = new MultiElements.DelayedValue(((UserType)receiver.type), values); return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } @@ -203,127 +207,6 @@ public abstract class UserTypes } } - public static class Value extends Term.MultiItemTerminal - { - private final UserType type; - public final ByteBuffer[] elements; - - public Value(UserType type, ByteBuffer[] elements) - { - this.type = type; - this.elements = elements; - } - - public static Value fromSerialized(ByteBuffer bytes, UserType type) - { - type.validate(bytes); - return new Value(type, type.split(ByteBufferAccessor.instance, bytes)); - } - - public ByteBuffer get(ProtocolVersion version) - { - return TupleType.buildValue(elements); - } - - public boolean equals(UserType userType, Value v) - { - if (elements.length != v.elements.length) - return false; - - for (int i = 0; i < elements.length; i++) - if (userType.fieldType(i).compare(elements[i], v.elements[i]) != 0) - return false; - - return true; - } - - public List getElements() - { - return Arrays.asList(elements); - } - } - - public static class DelayedValue extends Term.NonTerminal - { - private final UserType type; - private final List values; - - public DelayedValue(UserType type, List values) - { - this.type = type; - this.values = values; - } - - public void addFunctionsTo(List functions) - { - Terms.addFunctions(values, functions); - } - - public boolean containsBindMarker() - { - for (Term t : values) - if (t.containsBindMarker()) - return true; - return false; - } - - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - for (int i = 0; i < type.size(); i++) - values.get(i).collectMarkerSpecification(boundNames); - } - - private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException - { - if (values.size() > type.size()) - { - throw new InvalidRequestException(String.format( - "UDT value contained too many fields (expected %s, got %s)", type.size(), values.size())); - } - - ByteBuffer[] buffers = new ByteBuffer[values.size()]; - for (int i = 0; i < type.size(); i++) - { - buffers[i] = values.get(i).bindAndGet(options); - // Since a frozen UDT value is always written in its entirety Cassandra can't preserve a pre-existing - // value by 'not setting' the new value. Reject the query. - if (!type.isMultiCell() && buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER) - throw new InvalidRequestException(String.format("Invalid unset value for field '%s' of user defined type %s", type.fieldNameAsString(i), type.getNameAsString())); - } - return buffers; - } - - public Value bind(QueryOptions options) throws InvalidRequestException - { - return new Value(type, bindInternal(options)); - } - - @Override - public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException - { - return UserType.buildValue(bindInternal(options)); - } - } - - public static class Marker extends AbstractMarker - { - protected Marker(int bindIndex, ColumnSpecification receiver) - { - super(bindIndex, receiver); - assert receiver.type.isUDT(); - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - ByteBuffer value = options.getValues().get(bindIndex); - if (value == null) - return null; - if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) - return UNSET_VALUE; - return Value.fromSerialized(value, (UserType) receiver.type); - } - } - public static class Setter extends Operation { public Setter(ColumnMetadata column, Term t) @@ -337,23 +220,23 @@ public abstract class UserTypes if (value == UNSET_VALUE) return; - Value userTypeValue = (Value) value; - if (column.type.isMultiCell()) + UserType type = (UserType) column.type; + if (type.isMultiCell()) { // setting a whole UDT at once means we overwrite all cells, so delete existing cells params.setComplexDeletionTimeForOverwrite(column); if (value == null) return; - Iterator fieldNameIter = userTypeValue.type.fieldNames().iterator(); - for (ByteBuffer buffer : userTypeValue.elements) + Iterator fieldNameIter = type.fieldNames().iterator(); + for (ByteBuffer buffer : value.getElements()) { assert fieldNameIter.hasNext(); FieldIdentifier fieldName = fieldNameIter.next(); if (buffer == null) continue; - CellPath fieldPath = userTypeValue.type.cellPathForField(fieldName); + CellPath fieldPath = type.cellPathForField(fieldName); params.addCell(column, fieldPath, buffer); } } @@ -363,7 +246,7 @@ public abstract class UserTypes if (value == null) params.addTombstone(column); else - params.addCell(column, value.get(params.options.getProtocolVersion())); + params.addCell(column, value.get()); } } } @@ -391,7 +274,7 @@ public abstract class UserTypes if (value == null) params.addTombstone(column, fieldPath); else - params.addCell(column, fieldPath, value.get(params.options.getProtocolVersion())); + params.addCell(column, fieldPath, value.get()); } } diff --git a/src/java/org/apache/cassandra/cql3/Vectors.java b/src/java/org/apache/cassandra/cql3/terms/Vectors.java similarity index 74% rename from src/java/org/apache/cassandra/cql3/Vectors.java rename to src/java/org/apache/cassandra/cql3/terms/Vectors.java index 2c53b9e716..3405ebd5d9 100644 --- a/src/java/org/apache/cassandra/cql3/Vectors.java +++ b/src/java/org/apache/cassandra/cql3/terms/Vectors.java @@ -16,9 +16,8 @@ * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -26,14 +25,15 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.AssignmentTestable; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.VectorType; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.transport.ProtocolVersion; -import org.apache.cassandra.utils.ByteBufferUtil; -public class Vectors +public final class Vectors { private Vectors() {} @@ -136,7 +136,7 @@ public class Vectors values.add(t); } - DelayedValue value = new DelayedValue<>(type, values); + MultiElements.DelayedValue value = new MultiElements.DelayedValue(type, values); return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } @@ -158,72 +158,4 @@ public class Vectors return getPreferredCompatibleType(elements, e -> e.getCompatibleTypeIfKnown(keyspace)); } } - - public static class Value extends Term.MultiItemTerminal - { - public final VectorType type; - public final List elements; - - public Value(VectorType type, List elements) - { - this.type = type; - this.elements = elements; - } - - public ByteBuffer get(ProtocolVersion version) - { - return type.decomposeRaw(elements); - } - - public List getElements() - { - return elements; - } - } - - /** - * Basically similar to a Value, but with some non-pure function (that need - * to be evaluated at execution time) in it. - */ - public static class DelayedValue extends Term.NonTerminal - { - private final VectorType type; - private final List elements; - - public DelayedValue(VectorType type, List elements) - { - this.type = type; - this.elements = elements; - } - - public boolean containsBindMarker() - { - return elements.stream().anyMatch(Term::containsBindMarker); - } - - public void collectMarkerSpecification(VariableSpecifications boundNames) - { - elements.forEach(t -> t.collectMarkerSpecification(boundNames)); - } - - public Terminal bind(QueryOptions options) throws InvalidRequestException - { - List buffers = new ArrayList<>(elements.size()); - for (Term t : elements) - { - ByteBuffer bytes = t.bindAndGet(options); - - if (bytes == null || bytes == ByteBufferUtil.UNSET_BYTE_BUFFER || type.elementType.isNull(bytes)) - throw new InvalidRequestException("null is not supported inside vectors"); - - buffers.add(bytes); - } - return new Value<>(type, buffers); - } - - public void addFunctionsTo(List functions) - { - Terms.addFunctions(elements, functions); - } - } } diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index d751f6ea2c..3dd83ab6d1 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -60,7 +60,6 @@ import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.statements.schema.CreateTableStatement; import org.apache.cassandra.db.commitlog.CommitLogPosition; import org.apache.cassandra.db.compaction.CompactionHistoryTabularData; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.LongType; import org.apache.cassandra.db.marshal.TimeUUIDType; @@ -457,6 +456,7 @@ public final class SystemKeyspace + "PRIMARY KEY (keyspace_name, table_name, top_type))") .build(); + private static final TupleType TOP_TUPLE_TYPE = new TupleType(Arrays.asList(UTF8Type.instance, LongType.instance)); private static final TableMetadata PreparedStatements = parse(PREPARED_STATEMENTS, @@ -1966,8 +1966,7 @@ public final class SystemKeyspace List tupleList = new ArrayList<>(topPartitions.size()); topPartitions.forEach(tp -> { String key = metadata.partitionKeyType.getString(tp.key.getKey()); - tupleList.add(TupleType.buildValue(new ByteBuffer[] { UTF8Type.instance.decompose(key), - LongType.instance.decompose(tp.value)})); + tupleList.add(TOP_TUPLE_TYPE.pack(UTF8Type.instance.decompose(key), LongType.instance.decompose(tp.value))); }); executeInternal(cql, metadata.keyspace, metadata.name, topType, tupleList, Date.from(Instant.ofEpochMilli(lastUpdate))); } @@ -1990,9 +1989,9 @@ public final class SystemKeyspace TupleType tupleType = new TupleType(Lists.newArrayList(UTF8Type.instance, LongType.instance)); for (ByteBuffer bb : top) { - ByteBuffer[] components = tupleType.split(ByteBufferAccessor.instance, bb); - String keyStr = UTF8Type.instance.compose(components[0]); - long value = LongType.instance.compose(components[1]); + List components = tupleType.unpack(bb); + String keyStr = UTF8Type.instance.compose(components.get(0)); + long value = LongType.instance.compose(components.get(1)); topPartitions.add(new TopPartitionTracker.TopPartition(metadata.partitioner.decorateKey(metadata.partitionKeyType.fromString(keyStr)), value)); } return new TopPartitionTracker.StoredTopPartitions(topPartitions, lastUpdated); diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractCompositeType.java b/src/java/org/apache/cassandra/db/marshal/AbstractCompositeType.java index 170363594f..66059cc3d4 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractCompositeType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractCompositeType.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.regex.Pattern; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractTimeUUIDType.java b/src/java/org/apache/cassandra/db/marshal/AbstractTimeUUIDType.java index 35778aff24..6326b2a8c0 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractTimeUUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractTimeUUIDType.java @@ -21,9 +21,9 @@ import java.nio.ByteBuffer; import java.util.UUID; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.Duration; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.UUIDSerializer; import org.apache.cassandra.utils.TimeUUID; diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java index fe8b549837..c55d9afd44 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java @@ -31,7 +31,7 @@ import java.util.Objects; import org.apache.cassandra.cql3.AssignmentTestable; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.exceptions.SyntaxException; @@ -305,7 +305,7 @@ public abstract class AbstractType implements Comparator, Assignm public AbstractType unwrap() { - return isReversed() ? ((ReversedType) this).baseType.unwrap() : this; + return this; } public static AbstractType parseDefaultParameters(AbstractType baseType, TypeParser parser) throws SyntaxException diff --git a/src/java/org/apache/cassandra/db/marshal/AsciiType.java b/src/java/org/apache/cassandra/db/marshal/AsciiType.java index 119965abeb..896f17e99c 100644 --- a/src/java/org/apache/cassandra/db/marshal/AsciiType.java +++ b/src/java/org/apache/cassandra/db/marshal/AsciiType.java @@ -24,10 +24,10 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; import io.netty.util.concurrent.FastThreadLocal; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.TypeSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/BooleanType.java b/src/java/org/apache/cassandra/db/marshal/BooleanType.java index 471aa8dc2d..a273fe3ed4 100644 --- a/src/java/org/apache/cassandra/db/marshal/BooleanType.java +++ b/src/java/org/apache/cassandra/db/marshal/BooleanType.java @@ -20,8 +20,8 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.BooleanSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/ByteType.java b/src/java/org/apache/cassandra/db/marshal/ByteType.java index 614bdf9052..c03040d30e 100644 --- a/src/java/org/apache/cassandra/db/marshal/ByteType.java +++ b/src/java/org/apache/cassandra/db/marshal/ByteType.java @@ -22,8 +22,8 @@ import java.nio.ByteBuffer; import org.apache.commons.lang3.mutable.MutableByte; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.ByteSerializer; import org.apache.cassandra.serializers.MarshalException; diff --git a/src/java/org/apache/cassandra/db/marshal/BytesType.java b/src/java/org/apache/cassandra/db/marshal/BytesType.java index 7ad0280e55..5ee02fcc6e 100644 --- a/src/java/org/apache/cassandra/db/marshal/BytesType.java +++ b/src/java/org/apache/cassandra/db/marshal/BytesType.java @@ -20,8 +20,8 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.BytesSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java b/src/java/org/apache/cassandra/db/marshal/CollectionType.java index 0f140fea06..e6d6851f75 100644 --- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java +++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java @@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Iterator; import java.util.Objects; @@ -27,9 +28,9 @@ import java.util.function.Consumer; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.Lists; -import org.apache.cassandra.cql3.Maps; -import org.apache.cassandra.cql3.Sets; +import org.apache.cassandra.cql3.terms.Lists; +import org.apache.cassandra.cql3.terms.Maps; +import org.apache.cassandra.cql3.terms.Sets; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; @@ -49,7 +50,7 @@ import org.apache.cassandra.utils.bytecomparable.ByteSourceInverse; * Please note that this comparator shouldn't be used "manually" (as a custom * type for instance). */ -public abstract class CollectionType extends AbstractType +public abstract class CollectionType extends MultiElementType { public static CellPath.Serializer cellPathSerializer = new CollectionPathSerializer(); @@ -101,6 +102,12 @@ public abstract class CollectionType extends AbstractType return kind.makeCollectionReceiver(collection, isKey); } + @Override + public ByteBuffer decompose(T value) + { + return super.decompose(value); + } + public String getString(V value, ValueAccessor accessor) { return BytesType.instance.getString(value, accessor); @@ -155,18 +162,11 @@ public abstract class CollectionType extends AbstractType return true; } - // Overrided by maps - protected int collectionSize(List values) - { - return values.size(); - } - public ByteBuffer serializeForNativeProtocol(Iterator> cells) { assert isMultiCell(); List values = serializedValues(cells); - int size = collectionSize(values); - return CollectionSerializer.pack(values, ByteBufferAccessor.instance, size); + return getSerializer().pack(values); } @Override @@ -291,10 +291,10 @@ public abstract class CollectionType extends AbstractType return Integer.compare(sizeL, sizeR); } - static ByteSource asComparableBytesListOrSet(AbstractType elementsComparator, - ValueAccessor accessor, - V data, - ByteComparable.Version version) + ByteSource asComparableBytesListOrSet(AbstractType elementsComparator, + ValueAccessor accessor, + V data, + ByteComparable.Version version) { if (accessor.isEmpty(data)) return null; @@ -312,10 +312,10 @@ public abstract class CollectionType extends AbstractType return ByteSource.withTerminatorMaybeLegacy(version, 0x00, srcs); } - static V fromComparableBytesListOrSet(ValueAccessor accessor, - ByteSource.Peekable comparableBytes, - ByteComparable.Version version, - AbstractType elementType) + V fromComparableBytesListOrSet(ValueAccessor accessor, + ByteSource.Peekable comparableBytes, + ByteComparable.Version version, + AbstractType elementType) { if (comparableBytes == null) return accessor.empty(); @@ -331,13 +331,50 @@ public abstract class CollectionType extends AbstractType buffers.add(null); separator = comparableBytes.next(); } - return CollectionSerializer.pack(buffers, accessor, buffers.size()); + return getSerializer().pack(buffers, accessor); + } + + @Override + public ByteBuffer pack(List elements) + { + return getSerializer().pack(elements); + } + + @Override + public List unpack(ByteBuffer input) + { + return getSerializer().unpack(input); + } + + /** + * Returns the size of the collections from the number of serialized elements. + * + * @param elements the serialized elements + * @return the size of the collections from the number of serialized elements. + */ + public int collectionSize(Collection elements) + { + return getSerializer().collectionSize(elements); + } + + /** + * Checks if this type of collection support bind markers + *

+ * At this point Collections do not support bind markers. The two reasons for that are: + * 1) it's not excessively useful and 2) we wouldn't have a good column name to return in the ColumnSpecification for those markers (not a + * blocker per-se but we don't bother due to 1). + * @return {@code false} + */ + @Override + public boolean supportsElementBindMarkers() + { + return false; } public static String setOrListToJsonString(ByteBuffer buffer, AbstractType elementsType, ProtocolVersion protocolVersion) { ByteBuffer value = buffer.duplicate(); - StringBuilder sb = new StringBuilder("["); + StringBuilder sb = new StringBuilder().append('['); int size = CollectionSerializer.readCollectionSize(value, ByteBufferAccessor.instance); int offset = CollectionSerializer.sizeOfCollectionSize(); for (int i = 0; i < size; i++) @@ -348,7 +385,7 @@ public abstract class CollectionType extends AbstractType offset += CollectionSerializer.sizeOfValue(element, ByteBufferAccessor.instance); sb.append(elementsType.toJSONString(element, protocolVersion)); } - return sb.append("]").toString(); + return sb.append(']').toString(); } private static class CollectionPathSerializer implements CellPath.Serializer diff --git a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java index c0e1465bf1..c8a3b6a49c 100644 --- a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java +++ b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java @@ -20,7 +20,7 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.db.context.CounterContext; import org.apache.cassandra.serializers.CounterSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/DateType.java b/src/java/org/apache/cassandra/db/marshal/DateType.java index 9491901c43..e45d756dbc 100644 --- a/src/java/org/apache/cassandra/db/marshal/DateType.java +++ b/src/java/org/apache/cassandra/db/marshal/DateType.java @@ -20,8 +20,8 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.util.Date; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/java/org/apache/cassandra/db/marshal/DecimalType.java b/src/java/org/apache/cassandra/db/marshal/DecimalType.java index 00da39ac35..cbb849f366 100644 --- a/src/java/org/apache/cassandra/db/marshal/DecimalType.java +++ b/src/java/org/apache/cassandra/db/marshal/DecimalType.java @@ -27,8 +27,8 @@ import java.util.Objects; import com.google.common.primitives.Ints; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.DecimalSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/DoubleType.java b/src/java/org/apache/cassandra/db/marshal/DoubleType.java index eff3bf744d..988ebfc691 100644 --- a/src/java/org/apache/cassandra/db/marshal/DoubleType.java +++ b/src/java/org/apache/cassandra/db/marshal/DoubleType.java @@ -22,8 +22,8 @@ import java.nio.ByteBuffer; import org.apache.commons.lang3.mutable.MutableDouble; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.DoubleSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/DurationType.java b/src/java/org/apache/cassandra/db/marshal/DurationType.java index 0c46617554..548088bd63 100644 --- a/src/java/org/apache/cassandra/db/marshal/DurationType.java +++ b/src/java/org/apache/cassandra/db/marshal/DurationType.java @@ -20,9 +20,9 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.Duration; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.DurationSerializer; import org.apache.cassandra.serializers.MarshalException; diff --git a/src/java/org/apache/cassandra/db/marshal/DynamicCompositeType.java b/src/java/org/apache/cassandra/db/marshal/DynamicCompositeType.java index 658a147bc7..9317cdfa4e 100644 --- a/src/java/org/apache/cassandra/db/marshal/DynamicCompositeType.java +++ b/src/java/org/apache/cassandra/db/marshal/DynamicCompositeType.java @@ -36,7 +36,7 @@ import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; diff --git a/src/java/org/apache/cassandra/db/marshal/EmptyType.java b/src/java/org/apache/cassandra/db/marshal/EmptyType.java index d9c7c22815..07ea3ff373 100644 --- a/src/java/org/apache/cassandra/db/marshal/EmptyType.java +++ b/src/java/org/apache/cassandra/db/marshal/EmptyType.java @@ -24,8 +24,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; diff --git a/src/java/org/apache/cassandra/db/marshal/FloatType.java b/src/java/org/apache/cassandra/db/marshal/FloatType.java index 216b077811..ba48cf16c9 100644 --- a/src/java/org/apache/cassandra/db/marshal/FloatType.java +++ b/src/java/org/apache/cassandra/db/marshal/FloatType.java @@ -22,8 +22,8 @@ import java.nio.ByteBuffer; import org.apache.commons.lang3.mutable.MutableFloat; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.FloatSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/FrozenType.java b/src/java/org/apache/cassandra/db/marshal/FrozenType.java index 03a80d60fa..003b7d93e3 100644 --- a/src/java/org/apache/cassandra/db/marshal/FrozenType.java +++ b/src/java/org/apache/cassandra/db/marshal/FrozenType.java @@ -20,7 +20,7 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.util.List; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; diff --git a/src/java/org/apache/cassandra/db/marshal/InetAddressType.java b/src/java/org/apache/cassandra/db/marshal/InetAddressType.java index 64e178d989..3f31d0852b 100644 --- a/src/java/org/apache/cassandra/db/marshal/InetAddressType.java +++ b/src/java/org/apache/cassandra/db/marshal/InetAddressType.java @@ -22,8 +22,8 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.InetAddressSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/Int32Type.java b/src/java/org/apache/cassandra/db/marshal/Int32Type.java index c76d310764..339eedd182 100644 --- a/src/java/org/apache/cassandra/db/marshal/Int32Type.java +++ b/src/java/org/apache/cassandra/db/marshal/Int32Type.java @@ -23,8 +23,8 @@ import java.util.Objects; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.Int32Serializer; import org.apache.cassandra.serializers.MarshalException; diff --git a/src/java/org/apache/cassandra/db/marshal/IntegerType.java b/src/java/org/apache/cassandra/db/marshal/IntegerType.java index 2dc0ae2238..6d25dada24 100644 --- a/src/java/org/apache/cassandra/db/marshal/IntegerType.java +++ b/src/java/org/apache/cassandra/db/marshal/IntegerType.java @@ -23,8 +23,8 @@ import java.nio.ByteBuffer; import java.util.Objects; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.IntegerSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java b/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java index e37f8e2f21..e30ef3361c 100644 --- a/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/LexicalUUIDType.java @@ -20,8 +20,8 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.util.UUID; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.MarshalException; diff --git a/src/java/org/apache/cassandra/db/marshal/ListType.java b/src/java/org/apache/cassandra/db/marshal/ListType.java index 71f400dbb1..f0c7fe214f 100644 --- a/src/java/org/apache/cassandra/db/marshal/ListType.java +++ b/src/java/org/apache/cassandra/db/marshal/ListType.java @@ -25,8 +25,8 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -import org.apache.cassandra.cql3.Lists; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; @@ -234,7 +234,7 @@ public class ListType extends CollectionType> terms.add(elements.fromJSONObject(element)); } - return new Lists.DelayedValue(terms); + return new MultiElements.DelayedValue(this, terms); } public ByteBuffer getSliceFromSerialized(ByteBuffer collection, ByteBuffer from, ByteBuffer to) @@ -260,4 +260,16 @@ public class ListType extends CollectionType> { return decompose(Collections.emptyList()); } + + @Override + public List filterSortAndValidateElements(List buffers) + { + for (ByteBuffer buffer: buffers) + { + if (buffer == null) + throw new MarshalException("null is not supported inside collections"); + elements.validate(buffer); + } + return buffers; + } } diff --git a/src/java/org/apache/cassandra/db/marshal/LongType.java b/src/java/org/apache/cassandra/db/marshal/LongType.java index 97b9f75468..d3a122c804 100644 --- a/src/java/org/apache/cassandra/db/marshal/LongType.java +++ b/src/java/org/apache/cassandra/db/marshal/LongType.java @@ -23,8 +23,8 @@ import java.util.Objects; import org.apache.commons.lang3.mutable.MutableLong; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.LongSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/MapType.java b/src/java/org/apache/cassandra/db/marshal/MapType.java index 16d533ec5c..72765d6863 100644 --- a/src/java/org/apache/cassandra/db/marshal/MapType.java +++ b/src/java/org/apache/cassandra/db/marshal/MapType.java @@ -21,15 +21,16 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -import org.apache.cassandra.cql3.Maps; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; @@ -38,7 +39,6 @@ import org.apache.cassandra.serializers.MapSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.JsonUtils; -import org.apache.cassandra.utils.bytecomparable.ByteComparable; import org.apache.cassandra.utils.bytecomparable.ByteComparable.Version; import org.apache.cassandra.utils.bytecomparable.ByteSource; import org.apache.cassandra.utils.bytecomparable.ByteSourceInverse; @@ -234,21 +234,6 @@ public class MapType extends CollectionType> @Override public ByteSource asComparableBytes(ValueAccessor accessor, T data, Version version) - { - return asComparableBytesMap(getKeysType(), getValuesType(), accessor, data, version); - } - - @Override - public T fromComparableBytes(ValueAccessor accessor, ByteSource.Peekable comparableBytes, Version version) - { - return fromComparableBytesMap(accessor, comparableBytes, version, getKeysType(), getValuesType()); - } - - static ByteSource asComparableBytesMap(AbstractType keysComparator, - AbstractType valuesComparator, - ValueAccessor accessor, - V data, - Version version) { if (accessor.isEmpty(data)) return null; @@ -259,40 +244,37 @@ public class MapType extends CollectionType> ByteSource[] srcs = new ByteSource[size * 2]; for (int i = 0; i < size; ++i) { - V k = CollectionSerializer.readValue(data, accessor, offset); + T k = CollectionSerializer.readValue(data, accessor, offset); offset += CollectionSerializer.sizeOfValue(k, accessor); - srcs[i * 2 + 0] = keysComparator.asComparableBytes(accessor, k, version); - V v = CollectionSerializer.readValue(data, accessor, offset); + srcs[i * 2 + 0] = keys.asComparableBytes(accessor, k, version); + T v = CollectionSerializer.readValue(data, accessor, offset); offset += CollectionSerializer.sizeOfValue(v, accessor); - srcs[i * 2 + 1] = valuesComparator.asComparableBytes(accessor, v, version); + srcs[i * 2 + 1] = values.asComparableBytes(accessor, v, version); } return ByteSource.withTerminatorMaybeLegacy(version, 0x00, srcs); } - static V fromComparableBytesMap(ValueAccessor accessor, - ByteSource.Peekable comparableBytes, - Version version, - AbstractType keysComparator, - AbstractType valuesComparator) + @Override + public T fromComparableBytes(ValueAccessor accessor, ByteSource.Peekable comparableBytes, Version version) { if (comparableBytes == null) return accessor.empty(); - assert version != ByteComparable.Version.LEGACY; // legacy translation is not reversible + assert version != Version.LEGACY; // legacy translation is not reversible - List buffers = new ArrayList<>(); + List buffers = new ArrayList<>(); int separator = comparableBytes.next(); while (separator != ByteSource.TERMINATOR) { buffers.add(ByteSourceInverse.nextComponentNull(separator) ? null - : keysComparator.fromComparableBytes(accessor, comparableBytes, version)); + : keys.fromComparableBytes(accessor, comparableBytes, version)); separator = comparableBytes.next(); buffers.add(ByteSourceInverse.nextComponentNull(separator) ? null - : valuesComparator.fromComparableBytes(accessor, comparableBytes, version)); + : values.fromComparableBytes(accessor, comparableBytes, version)); separator = comparableBytes.next(); } - return CollectionSerializer.pack(buffers, accessor,buffers.size() / 2); + return getSerializer().pack(buffers, accessor); } @Override @@ -301,22 +283,16 @@ public class MapType extends CollectionType> return serializer; } - @Override - protected int collectionSize(List values) - { - return values.size() / 2; - } - public String toString(boolean ignoreFreezing) { boolean includeFrozenType = !ignoreFreezing && !isMultiCell(); StringBuilder sb = new StringBuilder(); if (includeFrozenType) - sb.append(FrozenType.class.getName()).append("("); + sb.append(FrozenType.class.getName()).append('('); sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Arrays.asList(keys, values), ignoreFreezing || !isMultiCell)); if (includeFrozenType) - sb.append(")"); + sb.append(')'); return sb.toString(); } @@ -344,7 +320,7 @@ public class MapType extends CollectionType> "Expected a map, but got a %s: %s", parsed.getClass().getSimpleName(), parsed)); Map map = (Map) parsed; - Map terms = new HashMap<>(map.size()); + List terms = new ArrayList<>(map.size() << 1); for (Map.Entry entry : map.entrySet()) { if (entry.getKey() == null) @@ -353,9 +329,10 @@ public class MapType extends CollectionType> if (entry.getValue() == null) throw new MarshalException("Invalid null value in map"); - terms.put(keys.fromJSONObject(entry.getKey()), values.fromJSONObject(entry.getValue())); + terms.add(keys.fromJSONObject(entry.getKey())); + terms.add(values.fromJSONObject(entry.getValue())); } - return new Maps.DelayedValue(keys, terms); + return new MultiElements.DelayedValue(this, terms); } @Override @@ -398,4 +375,33 @@ public class MapType extends CollectionType> { return decompose(Collections.emptyMap()); } + + @Override + public List filterSortAndValidateElements(List buffers) + { + // We depend on Maps to be properly sorted by their keys, so use a sorted map implementation here. + SortedMap map = new TreeMap<>(getKeysType()); + Iterator iter = buffers.iterator(); + while (iter.hasNext()) + { + ByteBuffer keyBytes = iter.next(); + ByteBuffer valueBytes = iter.next(); + + if (keyBytes == null || valueBytes == null) + throw new MarshalException("null is not supported inside collections"); + + getKeysType().validate(keyBytes); + getValuesType().validate(valueBytes); + + map.put(keyBytes, valueBytes); + } + + List sortedBuffers = new ArrayList<>(map.size() << 1); + for (Map.Entry entry : map.entrySet()) + { + sortedBuffers.add(entry.getKey()); + sortedBuffers.add(entry.getValue()); + } + return sortedBuffers; + } } diff --git a/src/java/org/apache/cassandra/db/marshal/MultiElementType.java b/src/java/org/apache/cassandra/db/marshal/MultiElementType.java new file mode 100644 index 0000000000..ed4f88cc74 --- /dev/null +++ b/src/java/org/apache/cassandra/db/marshal/MultiElementType.java @@ -0,0 +1,70 @@ +/* + * 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.marshal; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Base type for the types being composed of multi-elements like Collections, Tuples, UDTs or Vectors. + * This class unifies the methods used by the CQL layer to work with those types, + * and it includes both frozen and non-frozen multi-element types. + */ +public abstract class MultiElementType extends AbstractType +{ + protected MultiElementType(ComparisonType comparisonType) + { + super(comparisonType); + } + + /** + * Returns the serialized representation of the value composed of the specified elements. + * + * @param elements the serialized values of the elements + * @return the serialized representation of the value composed of the specified elements. + */ + public abstract ByteBuffer pack(List elements); + + /** + * Returns the serialized representation of the elements composing the specified value. + * + * @param value a serialized value of this type + * @return the serialized representation of the elements composing the specified value. + */ + public abstract List unpack(ByteBuffer value); + + /** + * Checks if this type supports bind markers for its elements when the type value is provided through a literal. + * @return {@code true} if this type supports bind markers for its elements, {@code false} otherwise. + */ + public boolean supportsElementBindMarkers() + { + return true; + } + + /** + * Filter and sort the elements, if needed, before validating them. + *

+ * This method takes as input a list of elements, eliminates duplicates and reorders them if needed (e.g. {@code SetType} and {@code MapType}) and validate them. + * @param buffers the elements of this type + * @return the elements filtered and sorted as they are used for serialization. + */ + public abstract List filterSortAndValidateElements(List buffers); +} + diff --git a/src/java/org/apache/cassandra/db/marshal/PartitionerDefinedOrder.java b/src/java/org/apache/cassandra/db/marshal/PartitionerDefinedOrder.java index 3186e6e08b..65f67154a7 100644 --- a/src/java/org/apache/cassandra/db/marshal/PartitionerDefinedOrder.java +++ b/src/java/org/apache/cassandra/db/marshal/PartitionerDefinedOrder.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; import java.util.Objects; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.PartitionPosition; diff --git a/src/java/org/apache/cassandra/db/marshal/ReversedType.java b/src/java/org/apache/cassandra/db/marshal/ReversedType.java index 89d1adb039..3d5e11d0d7 100644 --- a/src/java/org/apache/cassandra/db/marshal/ReversedType.java +++ b/src/java/org/apache/cassandra/db/marshal/ReversedType.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.serializers.MarshalException; @@ -232,4 +232,10 @@ public class ReversedType extends AbstractType { return baseType.getMaskedValue(); } + + @Override + public AbstractType unwrap() + { + return baseType.unwrap(); + } } diff --git a/src/java/org/apache/cassandra/db/marshal/SetType.java b/src/java/org/apache/cassandra/db/marshal/SetType.java index 0a12bfa2fc..39ac241e1b 100644 --- a/src/java/org/apache/cassandra/db/marshal/SetType.java +++ b/src/java/org/apache/cassandra/db/marshal/SetType.java @@ -22,8 +22,8 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -import org.apache.cassandra.cql3.Sets; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; @@ -215,7 +215,7 @@ public class SetType extends CollectionType> "Expected a list (representing a set), but got a %s: %s", parsed.getClass().getSimpleName(), parsed)); List list = (List) parsed; - Set terms = new HashSet<>(list.size()); + List terms = new ArrayList<>(list.size()); for (Object element : list) { if (element == null) @@ -223,7 +223,7 @@ public class SetType extends CollectionType> terms.add(elements.fromJSONObject(element)); } - return new Sets.DelayedValue(elements, terms); + return new MultiElements.DelayedValue(this, terms); } @Override @@ -243,4 +243,18 @@ public class SetType extends CollectionType> { return decompose(Collections.emptySet()); } + + @Override + public List filterSortAndValidateElements(List buffers) + { + SortedSet sorted = new TreeSet<>(elements); + for (ByteBuffer buffer: buffers) + { + if (buffer == null) + throw new MarshalException("null is not supported inside collections"); + elements.validate(buffer); + sorted.add(buffer); + } + return new ArrayList<>(sorted); + } } diff --git a/src/java/org/apache/cassandra/db/marshal/ShortType.java b/src/java/org/apache/cassandra/db/marshal/ShortType.java index 96047daf73..247b083db7 100644 --- a/src/java/org/apache/cassandra/db/marshal/ShortType.java +++ b/src/java/org/apache/cassandra/db/marshal/ShortType.java @@ -23,8 +23,8 @@ import java.util.Objects; import org.apache.commons.lang3.mutable.MutableShort; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.ShortSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java b/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java index a474d39a81..e7f23158b5 100644 --- a/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java +++ b/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java @@ -20,9 +20,9 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.Duration; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.SimpleDateSerializer; import org.apache.cassandra.serializers.TypeSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/TimeType.java b/src/java/org/apache/cassandra/db/marshal/TimeType.java index 67cf7dbceb..346b81c2a9 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimeType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimeType.java @@ -21,8 +21,8 @@ import java.nio.ByteBuffer; import java.time.LocalTime; import java.time.ZoneOffset; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TimeSerializer; import org.apache.cassandra.cql3.CQL3Type; diff --git a/src/java/org/apache/cassandra/db/marshal/TimestampType.java b/src/java/org/apache/cassandra/db/marshal/TimestampType.java index 54d88bd0da..0f8ce87ae0 100644 --- a/src/java/org/apache/cassandra/db/marshal/TimestampType.java +++ b/src/java/org/apache/cassandra/db/marshal/TimestampType.java @@ -20,9 +20,9 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.util.Date; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.Duration; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/java/org/apache/cassandra/db/marshal/TupleType.java b/src/java/org/apache/cassandra/db/marshal/TupleType.java index 0a03936da7..490bcdc845 100644 --- a/src/java/org/apache/cassandra/db/marshal/TupleType.java +++ b/src/java/org/apache/cassandra/db/marshal/TupleType.java @@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; @@ -28,11 +29,16 @@ import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.serializers.*; import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.bytecomparable.ByteComparable; import org.apache.cassandra.utils.bytecomparable.ByteSource; @@ -45,7 +51,7 @@ import static com.google.common.collect.Iterables.transform; * This is essentially like a CompositeType, but it's not primarily meant for comparison, just * to pack multiple values together so has a more friendly encoding. */ -public class TupleType extends AbstractType +public class TupleType extends MultiElementType { private static final String COLON = ":"; private static final Pattern COLON_PAT = Pattern.compile(COLON); @@ -227,10 +233,10 @@ public class TupleType extends AbstractType if (accessor.isEmpty(data)) return null; - V[] bufs = split(accessor, data); // this may be shorter than types.size -- other srcs remain null in that case + List bufs = unpack(data, accessor); // this may be shorter than types.size -- other srcs remain null in that case ByteSource[] srcs = new ByteSource[types.size()]; - for (int i = 0; i < bufs.length; ++i) - srcs[i] = bufs[i] != null ? types.get(i).asComparableBytes(accessor, bufs[i], ByteComparable.Version.LEGACY) : null; + for (int i = 0; i < bufs.size(); ++i) + srcs[i] = bufs.get(i) != null ? types.get(i).asComparableBytes(accessor, bufs.get(i), ByteComparable.Version.LEGACY) : null; // We always have a fixed number of sources, with the trailing ones possibly being nulls. // This can only result in a prefix if the last type in the tuple allows prefixes. Since that type is required @@ -243,15 +249,15 @@ public class TupleType extends AbstractType if (accessor.isEmpty(data)) return null; - V[] bufs = split(accessor, data); + List bufs = unpack(data, accessor); int lengthWithoutTrailingNulls = 0; - for (int i = 0; i < bufs.length; ++i) - if (bufs[i] != null) + for (int i = 0; i < bufs.size(); ++i) + if (bufs.get(i) != null) lengthWithoutTrailingNulls = i + 1; ByteSource[] srcs = new ByteSource[lengthWithoutTrailingNulls]; for (int i = 0; i < lengthWithoutTrailingNulls; ++i) - srcs[i] = bufs[i] != null ? types.get(i).asComparableBytes(accessor, bufs[i], version) : null; + srcs[i] = bufs.get(i) != null ? types.get(i).asComparableBytes(accessor, bufs.get(i), version) : null; // Because we stop early when there are trailing nulls, there needs to be an explicit terminator to make the // type prefix-free. @@ -282,32 +288,30 @@ public class TupleType extends AbstractType assert terminator == ByteSource.TERMINATOR : String.format("Expected TERMINATOR (0x%2x) after %d components", ByteSource.TERMINATOR, types.size()); - return buildValue(accessor, componentBuffers); + return pack(accessor, Arrays.asList(componentBuffers)); } - /** - * Split a tuple value into its component values. - */ - public V[] split(ValueAccessor accessor, V value) + @Override + public List unpack(ByteBuffer value) { - return split(accessor, value, size(), this); + return unpack(value, ByteBufferAccessor.instance); } - /** - * Split a tuple value into its component values. - */ - public static V[] split(ValueAccessor accessor, V value, int numberOfElements, TupleType type) + public List unpack(V value, ValueAccessor accessor) { - V[] components = accessor.createArray(numberOfElements); + int numberOfElements = size(); + List components = new ArrayList<>(numberOfElements); int length = accessor.size(value); int position = 0; for (int i = 0; i < numberOfElements; i++) { if (position == length) - return Arrays.copyOfRange(components, 0, i); + { + return components; + } if (position + 4 > length) - throw new MarshalException(String.format("Not enough bytes to read %dth component", i)); + throw new MarshalException(String.format("Not enough bytes to read %dth %s", i, componentOrFieldName(i))); int size = accessor.getInt(value, position); position += 4; @@ -315,29 +319,36 @@ public class TupleType extends AbstractType // size < 0 means null value if (size >= 0) { - if (position + size > length) - throw new MarshalException(String.format("Not enough bytes to read %dth component", i)); + if (length - position < size) + throw new MarshalException(String.format("Not enough bytes to read %dth %s", i, componentOrFieldName(i))); - components[i] = accessor.slice(value, position, size); + components.add(accessor.slice(value, position, size)); position += size; } else - components[i] = null; + components.add(null); } // error out if we got more values in the tuple/UDT than we expected if (position < length) { - throw new MarshalException(String.format("Expected %s %s for %s column, but got more", - numberOfElements, numberOfElements == 1 ? "value" : "values", - type.asCQL3Type())); + throw new MarshalException(String.format("Invalid remaining data after end of %s value", isTuple() ? "tuple" : "UDT")); } return components; } - @SafeVarargs - public static V buildValue(ValueAccessor accessor, V... components) + /** + * Returns the name used for the specified component or field if the type is a Tuple. + * @param i the component/field index + * @return the name used for the specified component or field if the type is a Tuple. + */ + protected String componentOrFieldName(int i) + { + return "component"; + } + + public static V pack(ValueAccessor accessor, Collection components) { int totalLength = 0; for (V component : components) @@ -361,9 +372,35 @@ public class TupleType extends AbstractType return result; } - public static ByteBuffer buildValue(ByteBuffer... components) + @Override + public ByteBuffer pack(List components) { - return buildValue(ByteBufferAccessor.instance, components); + return pack(ByteBufferAccessor.instance, components); + } + + public ByteBuffer pack(ByteBuffer... components) + { + return pack(Arrays.asList(components)); + } + + @Override + public List filterSortAndValidateElements(List buffers) + { + if (buffers.size() > size()) + throw new MarshalException(String.format("Tuple value contains too many fields (expected %s, got %s)", size(), buffers.size())); + + for (int i = 0; i < buffers.size(); i++) + { + // Since A tuple value is always written in its entirety Cassandra can't preserve a pre-existing value by 'not setting' the new value. Reject the query. + ByteBuffer buffer = buffers.get(i); + if (buffer == null) + continue; + if (buffer == ByteBufferUtil.UNSET_BYTE_BUFFER) + throw new InvalidRequestException(String.format("Invalid unset value for tuple field number %d", i)); + type(i).validate(buffer); + } + + return buffers; } @Override @@ -410,20 +447,24 @@ public class TupleType extends AbstractType throw new MarshalException(String.format("Invalid tuple literal: too many elements. Type %s expects %d but got %d", asCQL3Type(), size(), fieldStrings.size())); - ByteBuffer[] fields = new ByteBuffer[fieldStrings.size()]; + List fields = new ArrayList<>(fieldStrings.size()); for (int i = 0; i < fieldStrings.size(); i++) { String fieldString = fieldStrings.get(i); // We use @ to represent nulls if (fieldString.equals("@")) - continue; - - AbstractType type = type(i); - fieldString = ESCAPED_COLON_PAT.matcher(fieldString).replaceAll(COLON); - fieldString = ESCAPED_AT_PAT.matcher(fieldString).replaceAll(AT); - fields[i] = type.fromString(fieldString); + { + fields.add(null); + } + else + { + AbstractType type = type(i); + fieldString = ESCAPED_COLON_PAT.matcher(fieldString).replaceAll(COLON); + fieldString = ESCAPED_AT_PAT.matcher(fieldString).replaceAll(AT); + fields.add(type.fromString(fieldString)); + } } - return buildValue(fields); + return pack(fields); } @Override @@ -458,7 +499,7 @@ public class TupleType extends AbstractType } } - return new Tuples.DelayedValue(this, terms); + return new MultiElements.DelayedValue(this, terms); } @Override @@ -560,13 +601,10 @@ public class TupleType extends AbstractType @Override public ByteBuffer getMaskedValue() { - ByteBuffer[] buffers = new ByteBuffer[types.size()]; - for (int i = 0; i < types.size(); i++) - { - AbstractType type = types.get(i); - buffers[i] = type.getMaskedValue(); - } + List buffers = new ArrayList<>(types.size()); + for (AbstractType type : types) + buffers.add(type.getMaskedValue()); - return serializer.serialize(buildValue(buffers)); + return serializer.serialize(pack(buffers)); } } diff --git a/src/java/org/apache/cassandra/db/marshal/UTF8Type.java b/src/java/org/apache/cassandra/db/marshal/UTF8Type.java index ca668f7b7b..5258321bee 100644 --- a/src/java/org/apache/cassandra/db/marshal/UTF8Type.java +++ b/src/java/org/apache/cassandra/db/marshal/UTF8Type.java @@ -21,10 +21,10 @@ import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.TypeSerializer; diff --git a/src/java/org/apache/cassandra/db/marshal/UUIDType.java b/src/java/org/apache/cassandra/db/marshal/UUIDType.java index a5abd92234..0c72af9e8b 100644 --- a/src/java/org/apache/cassandra/db/marshal/UUIDType.java +++ b/src/java/org/apache/cassandra/db/marshal/UUIDType.java @@ -24,8 +24,8 @@ import java.util.regex.Pattern; import com.google.common.primitives.UnsignedLongs; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Constants; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.functions.ArgumentDeserializer; import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.MarshalException; diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java index c2308e7b15..d12a4d20b1 100644 --- a/src/java/org/apache/cassandra/db/marshal/UserType.java +++ b/src/java/org/apache/cassandra/db/marshal/UserType.java @@ -28,6 +28,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.schema.Difference; @@ -173,25 +176,24 @@ public class UserType extends TupleType implements SchemaElement { assert isMultiCell; - ByteBuffer[] components = new ByteBuffer[size()]; - short fieldPosition = 0; + List components = new ArrayList<>(size()); while (cells.hasNext()) { Cell cell = cells.next(); // handle null fields that aren't at the end short fieldPositionOfCell = ByteBufferUtil.toShort(cell.path().get(0)); - while (fieldPosition < fieldPositionOfCell) - components[fieldPosition++] = null; + while (components.size() < fieldPositionOfCell) + components.add(null); - components[fieldPosition++] = cell.buffer(); + components.add(cell.buffer()); } // append trailing nulls for missing cells - while (fieldPosition < size()) - components[fieldPosition++] = null; + while (components.size() < size()) + components.add(null); - return TupleType.buildValue(components); + return pack(components); } public void validateCell(Cell cell) throws MarshalException @@ -254,13 +256,13 @@ public class UserType extends TupleType implements SchemaElement } } - return new UserTypes.DelayedValue(this, terms); + return new MultiElements.DelayedValue(this, terms); } @Override public String toJSONString(ByteBuffer buffer, ProtocolVersion protocolVersion) { - ByteBuffer[] buffers = split(ByteBufferAccessor.instance, buffer); + List buffers = unpack(buffer); StringBuilder sb = new StringBuilder("{"); for (int i = 0; i < types.size(); i++) { @@ -275,7 +277,7 @@ public class UserType extends TupleType implements SchemaElement sb.append(JsonUtils.quoteAsJsonString(name)); sb.append("\": "); - ByteBuffer valueBuffer = (i >= buffers.length) ? null : buffers[i]; + ByteBuffer valueBuffer = (i >= buffers.size()) ? null : buffers.get(i); if (valueBuffer == null) sb.append("null"); else @@ -458,6 +460,27 @@ public class UserType extends TupleType implements SchemaElement return serializer; } + @Override + public List filterSortAndValidateElements(List buffers) + { + if (buffers.size() > size()) + throw new MarshalException(String.format("UDT value contained too many fields (expected %s, got %s)", size(), buffers.size())); + + for (int i = 0; i < buffers.size(); i++) + { + // Since a frozen UDT value is always written in its entirety Cassandra can't preserve a pre-existing + // value by 'not setting' the new value. Reject the query. + ByteBuffer buffer = buffers.get(i); + if (buffer == null) + continue; + if (!isMultiCell() && buffer == ByteBufferUtil.UNSET_BYTE_BUFFER) + throw new MarshalException(String.format("Invalid unset value for field '%s' of user defined type %s", fieldNameAsString(i), getNameAsString())); + type(i).validate(buffer); + } + + return buffers; + } + @Override public SchemaElementType elementType() { @@ -512,6 +535,12 @@ public class UserType extends TupleType implements SchemaElement return builder.toString(); } + @Override + protected String componentOrFieldName(int i) + { + return "field " + fieldName(i); + } + private enum ConflictBehavior { LOG { diff --git a/src/java/org/apache/cassandra/db/marshal/VectorType.java b/src/java/org/apache/cassandra/db/marshal/VectorType.java index d922d37b42..455f2425fc 100644 --- a/src/java/org/apache/cassandra/db/marshal/VectorType.java +++ b/src/java/org/apache/cassandra/db/marshal/VectorType.java @@ -29,8 +29,8 @@ import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import org.apache.cassandra.cql3.CQL3Type; -import org.apache.cassandra.cql3.Term; -import org.apache.cassandra.cql3.Vectors; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.serializers.MarshalException; @@ -41,7 +41,7 @@ import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.bytecomparable.ByteComparable; import org.apache.cassandra.utils.bytecomparable.ByteSource; -public final class VectorType extends AbstractType> +public final class VectorType extends MultiElementType> { private static class Key { @@ -136,14 +136,15 @@ public final class VectorType extends AbstractType> return serializer; } - public List split(ByteBuffer buffer) + @Override + public List unpack(ByteBuffer buffer) { - return split(buffer, ByteBufferAccessor.instance); + return unpack(buffer, ByteBufferAccessor.instance); } - public List split(V buffer, ValueAccessor accessor) + public List unpack(V buffer, ValueAccessor accessor) { - return getSerializer().split(buffer, accessor); + return getSerializer().unpack(buffer, accessor); } public float[] composeAsFloat(ByteBuffer input) @@ -191,14 +192,34 @@ public final class VectorType extends AbstractType> return buffer; } - public ByteBuffer decomposeRaw(List elements) + public ByteBuffer pack(List elements) { - return decomposeRaw(elements, ByteBufferAccessor.instance); + return pack(elements, ByteBufferAccessor.instance); } - public V decomposeRaw(List elements, ValueAccessor accessor) + public V pack(List elements, ValueAccessor accessor) { - return getSerializer().serializeRaw(elements, accessor); + return getSerializer().pack(elements, accessor); + } + + @Override + public List filterSortAndValidateElements(List buffers) + { + // We only filter and validate for this type. + if (buffers == null) + return null; + + for (ByteBuffer buffer: buffers) + { + if (buffer == null || elementType.isNull(buffer)) + throw new MarshalException("null is not supported inside vectors"); + + if (buffer == ByteBufferUtil.UNSET_BYTE_BUFFER ) + throw new InvalidRequestException("unset is not supported inside vectors"); + + elementType.validate(buffer); + } + return buffers; } @Override @@ -207,7 +228,7 @@ public final class VectorType extends AbstractType> if (isNull(value, accessor)) return null; ByteSource[] srcs = new ByteSource[dimension]; - List split = split(value, accessor); + List split = unpack(value, accessor); for (int i = 0; i < dimension; i++) srcs[i] = elementType.asComparableBytes(accessor, split.get(i), version); return ByteSource.withTerminatorMaybeLegacy(version, 0x00, srcs); @@ -228,7 +249,7 @@ public final class VectorType extends AbstractType> buffers.add(elementType.fromComparableBytes(accessor, comparableBytes, version)); separator = comparableBytes.next(); } - return decomposeRaw(buffers, accessor); + return pack(buffers, accessor); } @Override @@ -279,7 +300,7 @@ public final class VectorType extends AbstractType> { StringBuilder sb = new StringBuilder(); sb.append('['); - List split = split(value, accessor); + List split = unpack(value, accessor); for (int i = 0; i < dimension; i++) { if (i > 0) @@ -311,7 +332,7 @@ public final class VectorType extends AbstractType> terms.add(elementType.fromJSONObject(element)); } - return new Vectors.DelayedValue<>(this, terms); + return new MultiElements.DelayedValue(this, terms); } @Override @@ -371,15 +392,15 @@ public final class VectorType extends AbstractType> public ByteBuffer getMaskedValue() { List values = Collections.nCopies(dimension, elementType.getMaskedValue()); - return serializer.serializeRaw(values, ByteBufferAccessor.instance); + return serializer.pack(values, ByteBufferAccessor.instance); } public abstract class VectorSerializer extends TypeSerializer> { public abstract int compareCustom(VL left, ValueAccessor accessorL, VR right, ValueAccessor accessorR); - public abstract List split(V buffer, ValueAccessor accessor); - public abstract V serializeRaw(List elements, ValueAccessor accessor); + public abstract List unpack(V buffer, ValueAccessor accessor); + public abstract V pack(List elements, ValueAccessor accessor); @Override public String toString(List value) @@ -442,13 +463,18 @@ public final class VectorType extends AbstractType> } @Override - public List split(V buffer, ValueAccessor accessor) + public List unpack(V buffer, ValueAccessor accessor) { + if (isNull(buffer, accessor)) + return null; List result = new ArrayList<>(dimension); int offset = 0; int elementLength = elementType.valueLengthIfFixed(); for (int i = 0; i < dimension; i++) { + if (accessor.sizeFromOffset(buffer, offset) < elementLength) + throw new MarshalException(String.format("Not enough bytes to read a vector<%s, %d>", elementType.asCQL3Type(), dimension)); + V bb = accessor.slice(buffer, offset, elementLength); offset += elementLength; elementSerializer.validate(bb, accessor); @@ -460,7 +486,7 @@ public final class VectorType extends AbstractType> } @Override - public V serializeRaw(List value, ValueAccessor accessor) + public V pack(List value, ValueAccessor accessor) { if (value == null) rejectNullOrEmptyValue(); @@ -564,7 +590,7 @@ public final class VectorType extends AbstractType> { int size = accessor.getUnsignedVInt32(input, offset); if (size < 0) - throw new AssertionError("Invalidate data at offset " + offset + "; saw size of " + size + " but only >= 0 is expected"); + throw new MarshalException(String.format("Not enough bytes to read a vector<%s, %d>", elementType.asCQL3Type(), dimension)); return accessor.slice(input, offset + TypeSizes.sizeofUnsignedVInt(size), size); } @@ -584,8 +610,10 @@ public final class VectorType extends AbstractType> } @Override - public List split(V buffer, ValueAccessor accessor) + public List unpack(V buffer, ValueAccessor accessor) { + if (isNull(buffer, accessor)) + return null; List result = new ArrayList<>(dimension); int offset = 0; for (int i = 0; i < dimension; i++) @@ -601,7 +629,7 @@ public final class VectorType extends AbstractType> } @Override - public V serializeRaw(List value, ValueAccessor accessor) + public V pack(List value, ValueAccessor accessor) { if (value == null) rejectNullOrEmptyValue(); @@ -626,7 +654,7 @@ public final class VectorType extends AbstractType> List bbs = new ArrayList<>(dimension); for (int i = 0; i < dimension; i++) bbs.add(elementSerializer.serialize(value.get(i))); - return serializeRaw(bbs, ByteBufferAccessor.instance); + return pack(bbs, ByteBufferAccessor.instance); } @Override diff --git a/src/java/org/apache/cassandra/schema/ColumnMetadata.java b/src/java/org/apache/cassandra/schema/ColumnMetadata.java index 7350c40e7f..f6c5bf0ced 100644 --- a/src/java/org/apache/cassandra/schema/ColumnMetadata.java +++ b/src/java/org/apache/cassandra/schema/ColumnMetadata.java @@ -388,6 +388,12 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta return isMasked(); } + @Override + public ColumnSpecification specForElementOrSlice(Selectable selected, ColumnSpecification receiver, CollectionType.Kind kind, String selectionType) + { + return Selectable.super.specForElementOrSlice(selected, receiver, kind, selectionType); + } + /** * Converts the specified column definitions into column identifiers. * diff --git a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java index 56cd13ce45..8b748050e3 100644 --- a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java +++ b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java @@ -37,6 +37,7 @@ import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.cql3.functions.masking.ColumnMask; import org.apache.cassandra.cql3.statements.schema.CreateTableStatement; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.*; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.marshal.*; @@ -1347,7 +1348,16 @@ public final class SchemaKeyspace FunctionName finalFunc = row.has("final_func") ? new FunctionName(ksName, row.getString("final_func")) : null; AbstractType stateType = row.has("state_type") ? CQLTypeParser.parse(ksName, row.getString("state_type"), types) : null; - ByteBuffer initcond = row.has("initcond") ? Terms.asBytes(ksName, row.getString("initcond"), stateType) : null; + ByteBuffer initcond; + if (row.has("initcond")) + { + String term = row.getString("initcond"); + initcond = Term.asBytes(ksName, term, stateType); + } + else + { + initcond = null; + } return UDAggregate.create(functions, name, argTypes, returnType, stateFunc, finalFunc, stateType, initcond); } diff --git a/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java b/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java index a2b0f2435b..12cc9094b9 100644 --- a/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java +++ b/src/java/org/apache/cassandra/serializers/AbstractTextSerializer.java @@ -53,7 +53,8 @@ public abstract class AbstractTextSerializer extends TypeSerializer } - public String toString(String value) + @Override + public String toString(String value) { return value; } diff --git a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java index b514d2490b..74b790bf29 100644 --- a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java +++ b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java @@ -20,8 +20,10 @@ package org.apache.cassandra.serializers; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.function.Consumer; import com.google.common.collect.Range; @@ -35,28 +37,27 @@ import org.apache.cassandra.utils.ByteBufferUtil; public abstract class CollectionSerializer extends TypeSerializer { protected abstract List serializeValues(T value); - protected abstract int getElementCount(T value); @Override public ByteBuffer serialize(T input) { List values = serializeValues(input); - return pack(values, ByteBufferAccessor.instance, getElementCount(input)); + return pack(values, ByteBufferAccessor.instance); } - public static ByteBuffer pack(Collection values, int elements) + public ByteBuffer pack(Collection values) { - return pack(values, ByteBufferAccessor.instance, elements); + return pack(values, ByteBufferAccessor.instance); } - public static V pack(Collection values, ValueAccessor accessor, int elements) + public V pack(Collection values, ValueAccessor accessor) { int size = 0; for (V value : values) size += sizeOfValue(value, accessor); ByteBuffer result = ByteBuffer.allocate(sizeOfCollectionSize() + size); - writeCollectionSize(result, elements); + writeCollectionSize(result, collectionSize(values)); for (V value : values) { writeValue(result, value, accessor); @@ -64,6 +65,78 @@ public abstract class CollectionSerializer extends TypeSerializer return accessor.valueOf((ByteBuffer) result.flip()); } + public List unpack(ByteBuffer input) + { + return unpack(input, ByteBufferAccessor.instance); + } + + public List unpack(V input, ValueAccessor accessor) + { + try + { + int elements = numberOfSerializedElements(readCollectionSize(input, accessor)); + if (elements < 0) + throw new MarshalException("The data cannot be deserialized as a " + getCollectionName()); + + // If the received bytes are not corresponding to a collection, the size might be a huge number. + // In such a case we do not want to initialize the list with that size as it can result + // in an OOM. On the other hand we do not want to have to resize the list + // if we can avoid it, so we put a reasonable limit on the initialCapacity. + List values = new ArrayList<>(Math.min(elements, 256)); + int offset = sizeOfCollectionSize(); + + for (int i = 0; i < elements; i++) + { + V value = readValue(input, accessor, offset); + offset += sizeOfValue(value, accessor); + + values.add(value); + } + + if (!accessor.isEmptyFromOffset(input, offset)) + throw new MarshalException("Unexpected extraneous bytes after " + getCollectionName() + " value"); + + return values; + } + catch (BufferUnderflowException | IndexOutOfBoundsException e) + { + throw new MarshalException("Not enough bytes to read a " + getCollectionName()); + } + } + + /** + * Return the collection name for error messages. + * @return the collection name for error messages. + */ + private String getCollectionName() + { + return getType().getSimpleName().toLowerCase(Locale.US); + } + + /** + * Returns the size of the collections from the number of serialized elements. + * + * @param the value type, ByteBuffer or byte[] + * @param elements the serialized elements + * @return the size of the collections from the number of serialized elements. + */ + public int collectionSize(Collection elements) + { + return elements.size(); + } + + /** + * Returns the number of serialized elements from the collection size. + * + * @param collectionSize the collection size + * @return the number of serialized elements from the collection size. + */ + protected int numberOfSerializedElements(int collectionSize) + { + return collectionSize; + } + + protected static void writeCollectionSize(ByteBuffer output, int elements) { output.putInt(elements); @@ -226,7 +299,7 @@ public abstract class CollectionSerializer extends TypeSerializer } catch (BufferUnderflowException | IndexOutOfBoundsException e) { - throw new MarshalException("Not enough bytes to read a set"); + throw new MarshalException("Not enough bytes to read a " + getCollectionName()); } } } diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java b/src/java/org/apache/cassandra/serializers/ListSerializer.java index c443c84a91..2ce3b89af1 100644 --- a/src/java/org/apache/cassandra/serializers/ListSerializer.java +++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java @@ -63,12 +63,6 @@ public class ListSerializer extends CollectionSerializer> return output; } - @Override - public int getElementCount(List value) - { - return value.size(); - } - @Override public void validate(V input, ValueAccessor accessor) { diff --git a/src/java/org/apache/cassandra/serializers/MapSerializer.java b/src/java/org/apache/cassandra/serializers/MapSerializer.java index d19849d861..854d822e76 100644 --- a/src/java/org/apache/cassandra/serializers/MapSerializer.java +++ b/src/java/org/apache/cassandra/serializers/MapSerializer.java @@ -21,6 +21,7 @@ package org.apache.cassandra.serializers; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -78,10 +79,15 @@ public class MapSerializer extends AbstractMapSerializer> return buffers; } - @Override - public int getElementCount(Map value) + public int collectionSize(Collection elements) { - return value.size(); + return elements.size() >> 1; + } + + @Override + protected int numberOfSerializedElements(int collectionSize) + { + return collectionSize * 2; // keys and values } @Override @@ -91,9 +97,6 @@ public class MapSerializer extends AbstractMapSerializer> throw new MarshalException("Not enough bytes to read a map"); try { - // Empty values are still valid. - if (accessor.isEmpty(input)) return; - int n = readCollectionSize(input, accessor); int offset = sizeOfCollectionSize(); for (int i = 0; i < n; i++) diff --git a/src/java/org/apache/cassandra/serializers/SetSerializer.java b/src/java/org/apache/cassandra/serializers/SetSerializer.java index f03f586798..72a652d4a8 100644 --- a/src/java/org/apache/cassandra/serializers/SetSerializer.java +++ b/src/java/org/apache/cassandra/serializers/SetSerializer.java @@ -67,12 +67,6 @@ public class SetSerializer extends AbstractMapSerializer> return buffers; } - @Override - public int getElementCount(Set value) - { - return value.size(); - } - @Override public void validate(V input, ValueAccessor accessor) { @@ -80,9 +74,6 @@ public class SetSerializer extends AbstractMapSerializer> throw new MarshalException("Not enough bytes to read a set"); try { - // Empty values are still valid. - if (accessor.isEmpty(input)) return; - int n = readCollectionSize(input, accessor); int offset = sizeOfCollectionSize(); for (int i = 0; i < n; i++) diff --git a/src/java/org/apache/cassandra/service/paxos/PaxosRepairHistory.java b/src/java/org/apache/cassandra/service/paxos/PaxosRepairHistory.java index d9546d479c..d9e52a7409 100644 --- a/src/java/org/apache/cassandra/service/paxos/PaxosRepairHistory.java +++ b/src/java/org/apache/cassandra/service/paxos/PaxosRepairHistory.java @@ -29,7 +29,6 @@ import com.google.common.collect.ImmutableList; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.TypeSizes; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.dht.Range; @@ -171,7 +170,7 @@ public class PaxosRepairHistory { List tuples = new ArrayList<>(size() + 1); for (int i = 0 ; i < 1 + size() ; ++i) - tuples.add(TupleType.buildValue(new ByteBuffer[] { TOKEN_FACTORY.toByteArray(tokenInclusiveUpperBound(i)), ballotLowBound[i].toBytes() })); + tuples.add(TYPE.pack(TOKEN_FACTORY.toByteArray(tokenInclusiveUpperBound(i)), ballotLowBound[i].toBytes())); return tuples; } @@ -181,10 +180,10 @@ public class PaxosRepairHistory Ballot[] ballotLowBounds = new Ballot[tuples.size()]; for (int i = 0 ; i < tuples.size() ; ++i) { - ByteBuffer[] split = TYPE.split(ByteBufferAccessor.instance, tuples.get(i)); + List elements = TYPE.unpack(tuples.get(i)); if (i < tokenInclusiveUpperBounds.length) - tokenInclusiveUpperBounds[i] = TOKEN_FACTORY.fromByteArray(split[0]); - ballotLowBounds[i] = Ballot.deserialize(split[1]); + tokenInclusiveUpperBounds[i] = TOKEN_FACTORY.fromByteArray(elements.get(0)); + ballotLowBounds[i] = Ballot.deserialize(elements.get(1)); } return new PaxosRepairHistory(tokenInclusiveUpperBounds, ballotLowBounds); diff --git a/test/distributed/org/apache/cassandra/distributed/test/AlterTest.java b/test/distributed/org/apache/cassandra/distributed/test/AlterTest.java index 553f9d26f8..15f7204458 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/AlterTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/AlterTest.java @@ -26,7 +26,7 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.cql3.Lists; +import org.apache.cassandra.cql3.terms.Lists; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.Feature; diff --git a/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java b/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java index 1d8970b683..6bd41a7e1b 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java +++ b/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java @@ -22,9 +22,11 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Function; @@ -120,10 +122,16 @@ public class TestBaseImpl extends DistributedTestBase public static ByteBuffer tuple(Object... values) { - ByteBuffer[] bbs = new ByteBuffer[values.length]; - for (int i = 0; i < values.length; i++) - bbs[i] = makeByteBuffer(values[i]); - return TupleType.buildValue(bbs); + List> types = new ArrayList<>(values.length); + List bbs = new ArrayList<>(values.length); + for (Object value : values) + { + AbstractType type = typeFor(value); + types.add(type); + bbs.add(value == null ? null : type.decompose(value)); + } + TupleType tupleType = new TupleType(types); + return tupleType.pack(bbs); } public static String batch(String... queries) diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index 04b9b7b7e7..24dd6724df 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -51,7 +51,6 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.Nullable; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; @@ -112,7 +111,6 @@ import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.BooleanType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.ByteType; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.CollectionType; @@ -1725,8 +1723,8 @@ public abstract class CQLTester // If the user writes a null for each column, then the whole tuple is null if (type.isUDT() && actualValue == null) { - ByteBuffer[] cells = ((TupleType) type).split(ByteBufferAccessor.instance, expectedByteValue); - return Stream.of(cells).allMatch(b -> b == null); + List cells = ((TupleType) type).unpack(expectedByteValue); + return cells.stream().allMatch(java.util.Objects::isNull); } return false; } @@ -2689,10 +2687,15 @@ public abstract class CQLTester public ByteBuffer toByteBuffer() { - ByteBuffer[] bbs = new ByteBuffer[values.length]; - for (int i = 0; i < values.length; i++) - bbs[i] = makeByteBuffer(values[i], typeFor(values[i])); - return TupleType.buildValue(bbs); + List> types = new ArrayList<>(values.length); + List bbs = new ArrayList<>(values.length); + for (Object value : values) + { + AbstractType type = typeFor(value); + types.add(type); + bbs.add(makeByteBuffer(value, type)); + } + return new TupleType(types).pack(bbs); } public String toCQLString() diff --git a/test/unit/org/apache/cassandra/cql3/EmptyValuesTest.java b/test/unit/org/apache/cassandra/cql3/EmptyValuesTest.java index babecffed2..139132ea0e 100644 --- a/test/unit/org/apache/cassandra/cql3/EmptyValuesTest.java +++ b/test/unit/org/apache/cassandra/cql3/EmptyValuesTest.java @@ -100,7 +100,7 @@ public class EmptyValuesTest extends CQLTester execute("TRUNCATE %s"); // In most cases we cannot insert empty value when we do not bind variables - // This is due to the current implementation of org.apache.cassandra.cql3.Constants.Literal.testAssignment + // This is due to the current implementation of org.apache.cassandra.cql3.terms.Constants.Literal.testAssignment // execute("INSERT INTO %s (id, v) VALUES (1, '" + emptyValue + "')"); execute("INSERT INTO %s (id, v) VALUES (1, ?)", ByteBufferUtil.EMPTY_BYTE_BUFFER); flush(); diff --git a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java index dd40cf81db..40e5516cf3 100644 --- a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java +++ b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java @@ -24,6 +24,9 @@ import org.junit.Assert; import org.junit.Test; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Terms; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.db.marshal.ListType; @@ -129,8 +132,9 @@ public class ColumnConditionTest private static boolean conditionApplies(List rowValue, Operator op, List conditionValue) { - ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true)); - ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Lists.Value(conditionValue))); + ListType type = ListType.getInstance(Int32Type.instance, true); + ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", type); + ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new MultiElements.Value(type, conditionValue))); ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT); return bound.appliesTo(newRow(definition, rowValue)); } @@ -153,8 +157,9 @@ public class ColumnConditionTest private static boolean conditionApplies(SortedSet rowValue, Operator op, SortedSet conditionValue) { - ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", SetType.getInstance(Int32Type.instance, true)); - ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Sets.Value(conditionValue))); + SetType type = SetType.getInstance(Int32Type.instance, true); + ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", type); + ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new MultiElements.Value(type, new ArrayList<>(conditionValue)))); ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT); return bound.appliesTo(newRow(definition, rowValue)); } @@ -169,8 +174,15 @@ public class ColumnConditionTest private static boolean conditionApplies(SortedMap rowValue, Operator op, SortedMap conditionValue) { - ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", MapType.getInstance(Int32Type.instance, Int32Type.instance, true)); - ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new Maps.Value(conditionValue))); + MapType type = MapType.getInstance(Int32Type.instance, Int32Type.instance, true); + ColumnMetadata definition = ColumnMetadata.regularColumn("ks", "cf", "c", type); + List value = new ArrayList<>(conditionValue.size() * 2); + for (Map.Entry entry: conditionValue.entrySet()) + { + value.add(entry.getKey()); + value.add(entry.getValue()); + } + ColumnCondition condition = ColumnCondition.condition(definition, op, Terms.of(new MultiElements.Value(type, value))); ColumnCondition.Bound bound = condition.bind(QueryOptions.DEFAULT); return bound.appliesTo(newRow(definition, rowValue)); } diff --git a/test/unit/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictionsTest.java b/test/unit/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictionsTest.java index bf8e1e4955..815e5aca45 100644 --- a/test/unit/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictionsTest.java +++ b/test/unit/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictionsTest.java @@ -24,11 +24,15 @@ import com.google.common.collect.Iterables; import org.junit.BeforeClass; import org.junit.Test; +import org.apache.cassandra.cql3.terms.Constants; +import org.apache.cassandra.cql3.terms.MultiElements; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.Terms; +import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.*; -import org.apache.cassandra.cql3.Term.MultiItemTerminal; import org.apache.cassandra.cql3.statements.Bound; import org.apache.cassandra.db.*; @@ -1685,12 +1689,16 @@ public class ClusteringColumnRestrictionsTest */ private static Restriction newMultiEq(TableMetadata tableMetadata, int firstIndex, ByteBuffer... values) { - List columnMetadatas = new ArrayList<>(); + List columns = new ArrayList<>(); + List> types = new ArrayList<>(); for (int i = 0; i < values.length; i++) { - columnMetadatas.add(getClusteringColumnDefinition(tableMetadata, firstIndex + i)); + ColumnMetadata column = getClusteringColumnDefinition(tableMetadata, firstIndex + i); + columns.add(column); + types.add(column.type); } - return new MultiColumnRestriction.EQRestriction(columnMetadatas, toMultiItemTerminal(values)); + TupleType tupleType = new TupleType(types); + return new MultiColumnRestriction.EQRestriction(columns, new MultiElements.Value(tupleType, Arrays.asList(values))); } /** @@ -1704,14 +1712,24 @@ public class ClusteringColumnRestrictionsTest @SafeVarargs private static Restriction newMultiIN(TableMetadata tableMetadata, int firstIndex, List... values) { - List columnMetadatas = new ArrayList<>(); - List terms = new ArrayList<>(); + List columns = new ArrayList<>(); + List> types = new ArrayList<>(); + + for (int i = 0; i < values[0].size(); i++) + { + ColumnMetadata column = getClusteringColumnDefinition(tableMetadata, firstIndex + i); + columns.add(column); + types.add(column.type); + } + + TupleType tupleType = new TupleType(types); + + List terms = new ArrayList<>(values.length); for (int i = 0; i < values.length; i++) { - columnMetadatas.add(getClusteringColumnDefinition(tableMetadata, firstIndex + i)); - terms.add(toMultiItemTerminal(values[i].toArray(new ByteBuffer[0]))); + terms.add(new MultiElements.Value(tupleType, values[i])); } - return new MultiColumnRestriction.InRestrictionWithValues(columnMetadatas, terms); + return new MultiColumnRestriction.INRestriction(columns, Terms.of(terms)); } /** @@ -1725,7 +1743,7 @@ public class ClusteringColumnRestrictionsTest private static Restriction newSingleIN(TableMetadata tableMetadata, int index, ByteBuffer... values) { ColumnMetadata columnDef = getClusteringColumnDefinition(tableMetadata, index); - return new SingleColumnRestriction.InRestrictionWithValues(columnDef, toTerms(values)); + return new SingleColumnRestriction.INRestriction(columnDef, toTerms(values)); } /** @@ -1768,23 +1786,16 @@ public class ClusteringColumnRestrictionsTest */ private static Restriction newMultiSlice(TableMetadata tableMetadata, int firstIndex, Bound bound, boolean inclusive, ByteBuffer... values) { - List columnMetadatas = new ArrayList<>(); + List columnMetadatas = new ArrayList<>(values.length); + List> types = new ArrayList<>(values.length); for (int i = 0; i < values.length; i++) { - columnMetadatas.add(getClusteringColumnDefinition(tableMetadata, i + firstIndex)); + ColumnMetadata column = getClusteringColumnDefinition(tableMetadata, i + firstIndex); + columnMetadatas.add(column); + types.add(column.type); } - return new MultiColumnRestriction.SliceRestriction(columnMetadatas, bound, inclusive, toMultiItemTerminal(values)); - } - - /** - * Converts the specified values into a MultiItemTerminal. - * - * @param values the values to convert. - * @return the term corresponding to the specified values. - */ - private static MultiItemTerminal toMultiItemTerminal(ByteBuffer... values) - { - return new Tuples.Value(values); + TupleType type = new TupleType(types); + return new MultiColumnRestriction.SliceRestriction(columnMetadatas, bound, inclusive, new MultiElements.Value(type, Arrays.asList(values))); } /** @@ -1804,12 +1815,12 @@ public class ClusteringColumnRestrictionsTest * @param values the values to convert. * @return a List of terms corresponding to the specified values. */ - private static List toTerms(ByteBuffer... values) + private static Terms toTerms(ByteBuffer... values) { List terms = new ArrayList<>(); for (ByteBuffer value : values) terms.add(toTerm(value)); - return terms; + return Terms.of(terms); } private static T get(SortedSet set, int i) diff --git a/test/unit/org/apache/cassandra/cql3/selection/SelectorSerializationTest.java b/test/unit/org/apache/cassandra/cql3/selection/SelectorSerializationTest.java index 530cb67614..977a0af01d 100644 --- a/test/unit/org/apache/cassandra/cql3/selection/SelectorSerializationTest.java +++ b/test/unit/org/apache/cassandra/cql3/selection/SelectorSerializationTest.java @@ -26,7 +26,7 @@ import java.util.List; import org.junit.Test; import org.apache.cassandra.cql3.*; -import org.apache.cassandra.cql3.Constants.Literal; +import org.apache.cassandra.cql3.terms.Constants.Literal; import org.apache.cassandra.cql3.functions.AggregateFcts; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.TimeFcts; diff --git a/test/unit/org/apache/cassandra/cql3/ListsTest.java b/test/unit/org/apache/cassandra/cql3/terms/ListsTest.java similarity index 94% rename from test/unit/org/apache/cassandra/cql3/ListsTest.java rename to test/unit/org/apache/cassandra/cql3/terms/ListsTest.java index 27c36b8216..30921a8870 100644 --- a/test/unit/org/apache/cassandra/cql3/ListsTest.java +++ b/test/unit/org/apache/cassandra/cql3/terms/ListsTest.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package org.apache.cassandra.cql3; +package org.apache.cassandra.cql3.terms; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -28,9 +28,13 @@ import com.google.common.collect.Iterators; import org.junit.Assert; import org.junit.Test; -import org.apache.cassandra.cql3.Lists.PrecisionTime; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UpdateParameters; +import org.apache.cassandra.cql3.terms.Lists.PrecisionTime; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.marshal.MultiElementType; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.dht.Murmur3Partitioner; @@ -137,7 +141,7 @@ public class ListsTest extends CQLTester TableMetadata metaData = currentTableMetadata(); ColumnMetadata columnMetadata = metaData.getColumn(ByteBufferUtil.bytes("l")); - Term term = new Lists.Value(terms); + Term term = new MultiElements.Value((MultiElementType) columnMetadata.type, terms); Lists.Prepender prepender = new Lists.Prepender(columnMetadata, term); ByteBuffer keyBuf = ByteBufferUtil.bytes("key"); diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java index e6ef163af2..5af7255516 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/CollectionsTest.java @@ -964,7 +964,7 @@ public class CollectionsTest extends CQLTester createTable("CREATE TABLE %s(pk int PRIMARY KEY, s set)"); assertInvalidMessage("Not enough bytes to read a set", "INSERT INTO %s (pk, s) VALUES (?, ?)", 1, "test"); - assertInvalidMessage("Null value read when not allowed", + assertInvalidMessage("Not enough bytes to read a set", "INSERT INTO %s (pk, s) VALUES (?, ?)", 1, Long.MAX_VALUE); assertInvalidMessage("Not enough bytes to read a set", "INSERT INTO %s (pk, s) VALUES (?, ?)", 1, ""); @@ -976,9 +976,9 @@ public class CollectionsTest extends CQLTester public void testInvalidInputForMap() throws Throwable { createTable("CREATE TABLE %s(pk int PRIMARY KEY, m map)"); - assertInvalidMessage("Not enough bytes to read a map", + assertInvalidMessage("The data cannot be deserialized as a map", "INSERT INTO %s (pk, m) VALUES (?, ?)", 1, "test"); - assertInvalidMessage("Null value read when not allowed", + assertInvalidMessage("The data cannot be deserialized as a map", "INSERT INTO %s (pk, m) VALUES (?, ?)", 1, Long.MAX_VALUE); assertInvalidMessage("Not enough bytes to read a map", "INSERT INTO %s (pk, m) VALUES (?, ?)", 1, ""); diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java index 59f84de5f7..452bb0bc36 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java @@ -34,7 +34,6 @@ import org.junit.Test; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.DecimalType; import org.apache.cassandra.db.marshal.DurationType; import org.apache.cassandra.db.marshal.TupleType; @@ -150,7 +149,7 @@ public class TupleTypeTest extends CQLTester assertInvalidSyntax("INSERT INTO %s (k, t) VALUES (0, ())"); - assertInvalidMessage("Invalid tuple literal for t: too many elements. Type frozen> expects 3 but got 4", + assertInvalidMessage("Expected 3 elements in value for tuple t, but got 4: (2, 'foo', 3.1, 'bar')", "INSERT INTO %s (k, t) VALUES (0, (2, 'foo', 3.1, 'bar'))"); createTable("CREATE TABLE %s (k int PRIMARY KEY, t frozen>>)"); @@ -270,13 +269,12 @@ public class TupleTypeTest extends CQLTester for (ByteBuffer value : testcase.uniqueRows) { map.put(value, count); - ByteBuffer[] tupleBuffers = tupleType.split(ByteBufferAccessor.instance, value); + Object[] tupleBuffers = tupleType.unpack(value).toArray(); - // use cast to avoid warning - execute("INSERT INTO %s (id, value) VALUES (?, ?)", tuple((Object[]) tupleBuffers), count); + execute("INSERT INTO %s (id, value) VALUES (?, ?)", tuple(tupleBuffers), count); - assertRows(execute("SELECT * FROM %s WHERE id = ?", tuple((Object[]) tupleBuffers)), - row(tuple((Object[]) tupleBuffers), count)); + assertRows(execute("SELECT * FROM %s WHERE id = ?", tuple(tupleBuffers)), + row(tuple(tupleBuffers), count)); count++; } assertRows(execute("SELECT * FROM %s LIMIT 100"), @@ -308,13 +306,12 @@ public class TupleTypeTest extends CQLTester for (ByteBuffer value : testcase.uniqueRows) { map.put(value, count); - ByteBuffer[] tupleBuffers = tupleType.split(ByteBufferAccessor.instance, value); + Object[] tupleBuffers = tupleType.unpack(value).toArray(); - // use cast to avoid warning - execute("INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", 1, tuple((Object[]) tupleBuffers), count); + execute("INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", 1, tuple(tupleBuffers), count); - assertRows(execute("SELECT * FROM %s WHERE pk = ? AND ck = ?", 1, tuple((Object[]) tupleBuffers)), - row(1, tuple((Object[]) tupleBuffers), count)); + assertRows(execute("SELECT * FROM %s WHERE pk = ? AND ck = ?", 1, tuple(tupleBuffers)), + row(1, tuple(tupleBuffers), count)); count++; } UntypedResultSet results = execute("SELECT * FROM %s LIMIT 100"); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java index 3b361bd29c..093e70f969 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java @@ -63,6 +63,7 @@ public class CQLVectorTest extends CQLTester.InMemory assertRows(execute("SELECT * FROM %s WHERE pk IN ([1, 2])"), row); assertRows(execute("SELECT * FROM %s WHERE pk IN ([1, 2], [1, 2])"), row); assertRows(execute("SELECT * FROM %s WHERE pk IN (?)", vector), row); + assertRows(execute("SELECT * FROM %s WHERE pk IN ?", list(vector)), row); assertRows(execute("SELECT * FROM %s WHERE pk IN ([1, 1 + 1])"), row); assertRows(execute("SELECT * FROM %s WHERE pk IN ([1, ?])", 2), row); assertRows(execute("SELECT * FROM %s WHERE pk IN ([1, (int) ?])", 2), row); @@ -315,7 +316,7 @@ public class CQLVectorTest extends CQLTester.InMemory InvalidRequestException.class, "INSERT INTO %s (k, v) VALUES (0, 0x)"); - assertInvalidThrowMessage("Invalid empty vector value", + assertInvalidThrowMessage(format("Not enough bytes to read a vector<%s, 2>", type), InvalidRequestException.class, "INSERT INTO %s (k, v) VALUES (0, ?)", ByteBufferUtil.EMPTY_BYTE_BUFFER); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java index b69ee37ac8..01197d2c1a 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectMultiColumnRelationTest.java @@ -51,7 +51,7 @@ public class SelectMultiColumnRelationTest extends CQLTester createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))"); assertInvalidSyntax("SELECT * FROM %s WHERE a = 0 AND (b, c) > ()"); - assertInvalidMessage("Expected 2 elements in value tuple, but got 3: (?, ?, ?)", + assertInvalidMessage("Expected 2 elements in value for tuple (b,c), but got 3: (?, ?, ?)", "SELECT * FROM %s WHERE a = 0 AND (b, c) > (?, ?, ?)", 1, 2, 3); assertInvalidMessage("Invalid null value in condition for column c", "SELECT * FROM %s WHERE a = 0 AND (b, c) > (?, ?)", 1, null); @@ -63,9 +63,9 @@ public class SelectMultiColumnRelationTest extends CQLTester "SELECT * FROM %s WHERE a = 0 AND (d, c, b) > (?, ?, ?)", 0, 0, 0); // Wrong number of values - assertInvalidMessage("Expected 3 elements in value tuple, but got 2: (?, ?)", + assertInvalidMessage("Expected 3 elements in value for tuple (b,c,d), but got 2: (?, ?)", "SELECT * FROM %s WHERE a=0 AND (b, c, d) IN ((?, ?))", 0, 1); - assertInvalidMessage("Expected 3 elements in value tuple, but got 5: (?, ?, ?, ?, ?)", + assertInvalidMessage("Expected 3 elements in value for tuple (b,c,d), but got 5: (?, ?, ?, ?, ?)", "SELECT * FROM %s WHERE a=0 AND (b, c, d) IN ((?, ?, ?, ?, ?))", 0, 1, 2, 3, 4); // Missing first clustering column @@ -1017,7 +1017,7 @@ public class SelectMultiColumnRelationTest extends CQLTester "SELECT * from %s WHERE i = ? AND (j) > ? ALLOW FILTERING", 1, unset()); assertInvalidMessage("Invalid unset value for tuple (i,j)", "SELECT * from %s WHERE (i, j) IN (?, ?) ALLOW FILTERING", unset(), tuple(1, 1)); - assertInvalidMessage("Invalid unset value for in(i,j)", + assertInvalidMessage("Invalid unset value for in(i, j)", "SELECT * from %s WHERE (i, j) IN ? ALLOW FILTERING", unset()); } diff --git a/test/unit/org/apache/cassandra/db/CellTest.java b/test/unit/org/apache/cassandra/db/CellTest.java index 3a9e0d0dc0..9540c8de74 100644 --- a/test/unit/org/apache/cassandra/db/CellTest.java +++ b/test/unit/org/apache/cassandra/db/CellTest.java @@ -32,10 +32,12 @@ import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.FieldIdentifier; import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.db.marshal.IntegerType; import org.apache.cassandra.db.marshal.MapType; import org.apache.cassandra.db.marshal.ShortType; +import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.rows.BufferCell; @@ -370,7 +372,13 @@ public class CellTest private static ByteBuffer udt(ByteBuffer...buffers) { - return UserType.buildValue(buffers); + List> types = new ArrayList<>(buffers.length); + for (int i = 0, m = buffers.length; i < m; i++) + { + types.add(BytesType.instance); + } + TupleType tupleType = new TupleType(types); + return tupleType.pack(buffers); } private static FieldIdentifier field(String field) diff --git a/test/unit/org/apache/cassandra/db/aggregation/GroupMakerTest.java b/test/unit/org/apache/cassandra/db/aggregation/GroupMakerTest.java index 4363d81723..59f1808082 100644 --- a/test/unit/org/apache/cassandra/db/aggregation/GroupMakerTest.java +++ b/test/unit/org/apache/cassandra/db/aggregation/GroupMakerTest.java @@ -27,7 +27,7 @@ import org.junit.Test; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.Constants.Literal; +import org.apache.cassandra.cql3.terms.Constants.Literal; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.VariableSpecifications; import org.apache.cassandra.cql3.functions.ScalarFunction; diff --git a/test/unit/org/apache/cassandra/db/marshal/AbstractTypeTest.java b/test/unit/org/apache/cassandra/db/marshal/AbstractTypeTest.java index 86b3926a53..22925d1e1f 100644 --- a/test/unit/org/apache/cassandra/db/marshal/AbstractTypeTest.java +++ b/test/unit/org/apache/cassandra/db/marshal/AbstractTypeTest.java @@ -42,10 +42,10 @@ import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.cql3.AssignmentTestable; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.Json; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.VariableSpecifications; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputPlus; diff --git a/test/unit/org/apache/cassandra/db/marshal/CompositeAndTupleTypesTest.java b/test/unit/org/apache/cassandra/db/marshal/CompositeAndTupleTypesTest.java index 178c291019..0c86871c28 100644 --- a/test/unit/org/apache/cassandra/db/marshal/CompositeAndTupleTypesTest.java +++ b/test/unit/org/apache/cassandra/db/marshal/CompositeAndTupleTypesTest.java @@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Random; @@ -33,8 +34,8 @@ import static org.apache.cassandra.db.marshal.ValueAccessors.ACCESSORS; public class CompositeAndTupleTypesTest { - interface TypeFactory { T createType(List> types); } - interface ValueCombiner { V combine(ValueAccessor accessor, V[] values); } + interface TypeFactory> { T createType(List> types); } + interface ValueCombiner { V combine(AbstractType type, ValueAccessor accessor, V[] values); } public static Object[] createValues(ValueGenerator[] generators, Random random) { @@ -75,7 +76,7 @@ public class CompositeAndTupleTypesTest return composed; } - public void testSerializationDeserialization(TypeFactory typeFactory, ValueCombiner combiner) + public > void testSerializationDeserialization(TypeFactory typeFactory, ValueCombiner combiner) { for (int i=0; i<100; i++) { @@ -90,14 +91,14 @@ public class CompositeAndTupleTypesTest { ByteBuffer[] srcBuffers = decompose(generators, expected); Object[] srcValues = convert(srcBuffers, ByteBufferAccessor.instance, srcAccessor); - Object srcJoined = combiner.combine(srcAccessor, srcValues); + Object srcJoined = combiner.combine(type, srcAccessor, srcValues); String srcString = type.getString(srcJoined, srcAccessor); for (ValueAccessor dstAccessor : ACCESSORS) { // convert data types and deserialize with Object[] dstValues = convert(srcValues, srcAccessor, dstAccessor); - Object dstJoined = combiner.combine(dstAccessor, dstValues); + Object dstJoined = combiner.combine(type, dstAccessor, dstValues); String dstString = type.getString(dstJoined, dstAccessor); Object[] composed = compose(generators, dstValues, dstAccessor); @@ -113,7 +114,12 @@ public class CompositeAndTupleTypesTest @Test public void tuple() { - testSerializationDeserialization(TupleType::new, TupleType::buildValue); + testSerializationDeserialization(TupleType::new, getTupleValueCombiner()); + } + + private static ValueCombiner getTupleValueCombiner() + { + return (type, accessor, values) -> TupleType.pack(accessor, Arrays.asList(values)); } @Test @@ -127,12 +133,12 @@ public class CompositeAndTupleTypesTest } return new UserType("ks", ByteBufferUtil.bytes("user_type"), names, types, false); }; - testSerializationDeserialization(factory, TupleType::buildValue); + testSerializationDeserialization(factory, getTupleValueCombiner()); } @Test public void composite() { - testSerializationDeserialization(CompositeType::new, CompositeType::build); + testSerializationDeserialization(CompositeType::new, (type, accessor, values) -> CompositeType.build(accessor, values)); } } diff --git a/test/unit/org/apache/cassandra/db/marshal/TypeValidationTest.java b/test/unit/org/apache/cassandra/db/marshal/TypeValidationTest.java index 474b867007..6897661cf6 100644 --- a/test/unit/org/apache/cassandra/db/marshal/TypeValidationTest.java +++ b/test/unit/org/apache/cassandra/db/marshal/TypeValidationTest.java @@ -204,8 +204,8 @@ public class TypeValidationTest qt().forAll(tupleWithValueGen(baseGen)).checkAssert(pair -> { TupleType tuple = pair.left; ByteBuffer value = pair.right; - Assertions.assertThat(TupleType.buildValue(tuple.split(ByteBufferAccessor.instance, value))) - .as("TupleType.buildValue(split(value)) == value") + Assertions.assertThat(tuple.pack(tuple.unpack(value))) + .as("tuple.pack(tuple.unpack(value)) == value") .isEqualTo(value); }); } diff --git a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java index 4163e27069..5edfd2225f 100644 --- a/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/SASIIndexTest.java @@ -61,7 +61,7 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.Operator; import org.apache.cassandra.cql3.QueryProcessor; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.statements.schema.IndexTarget; import org.apache.cassandra.db.Clustering; diff --git a/test/unit/org/apache/cassandra/schema/TableMetadataTest.java b/test/unit/org/apache/cassandra/schema/TableMetadataTest.java index 1580f31636..b5fe72b957 100644 --- a/test/unit/org/apache/cassandra/schema/TableMetadataTest.java +++ b/test/unit/org/apache/cassandra/schema/TableMetadataTest.java @@ -53,14 +53,15 @@ public class TableMetadataTest metadata1.partitionKeyAsCQLLiteral(type1.decompose("test:", "composite!", "type)"))); // composite type with tuple - CompositeType type2 = CompositeType.getInstance(new TupleType(Arrays.asList(FloatType.instance, UTF8Type.instance)), + TupleType tupleType = new TupleType(Arrays.asList(FloatType.instance, UTF8Type.instance)); + CompositeType type2 = CompositeType.getInstance(tupleType, IntegerType.instance); TableMetadata metadata2 = TableMetadata.builder(keyspaceName, tableName) .addPartitionKeyColumn("key", type2) .offline() .build(); - ByteBuffer tupleValue = TupleType.buildValue(new ByteBuffer[]{ FloatType.instance.decompose(0.33f), - UTF8Type.instance.decompose("tuple test") }); + ByteBuffer tupleValue = tupleType.pack(FloatType.instance.decompose(0.33f), + UTF8Type.instance.decompose("tuple test")); assertEquals("((0.33, 'tuple test'), 10)", metadata2.partitionKeyAsCQLLiteral(type2.decompose(tupleValue, BigInteger.valueOf(10)))); diff --git a/test/unit/org/apache/cassandra/transport/SerDeserTest.java b/test/unit/org/apache/cassandra/transport/SerDeserTest.java index 11b7489266..b14e854cab 100644 --- a/test/unit/org/apache/cassandra/transport/SerDeserTest.java +++ b/test/unit/org/apache/cassandra/transport/SerDeserTest.java @@ -39,18 +39,17 @@ import org.apache.cassandra.Util; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.Constants; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.FieldIdentifier; -import org.apache.cassandra.cql3.Lists; -import org.apache.cassandra.cql3.Maps; +import org.apache.cassandra.cql3.terms.Lists; +import org.apache.cassandra.cql3.terms.Maps; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.ResultSet; -import org.apache.cassandra.cql3.Sets; -import org.apache.cassandra.cql3.Term; -import org.apache.cassandra.cql3.UserTypes; +import org.apache.cassandra.cql3.terms.Sets; +import org.apache.cassandra.cql3.terms.Term; +import org.apache.cassandra.cql3.terms.UserTypes; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.db.marshal.ListType; import org.apache.cassandra.db.marshal.LongType; @@ -58,7 +57,6 @@ import org.apache.cassandra.db.marshal.MapType; import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.UserType; -import org.apache.cassandra.serializers.CollectionSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; @@ -86,7 +84,7 @@ public class SerDeserTest } @Test - public void collectionSerDeserTest() throws Exception + public void collectionSerDeserTest() { // Lists ListType lt = ListType.getInstance(Int32Type.instance, true); @@ -96,7 +94,7 @@ public class SerDeserTest for (Integer i : l) lb.add(Int32Type.instance.decompose(i)); - assertEquals(l, lt.getSerializer().deserialize(CollectionSerializer.pack(lb, lb.size()))); + assertEquals(l, lt.compose(lt.pack(lb))); // Sets SetType st = SetType.getInstance(UTF8Type.instance, true); @@ -106,7 +104,7 @@ public class SerDeserTest for (String t : s) sb.add(UTF8Type.instance.decompose(t)); - assertEquals(s, st.getSerializer().deserialize(CollectionSerializer.pack(sb, sb.size()))); + assertEquals(s, st.compose(st.pack(sb))); // Maps MapType mt = MapType.getInstance(UTF8Type.instance, LongType.instance, true); @@ -122,7 +120,7 @@ public class SerDeserTest mb.add(LongType.instance.decompose(entry.getValue())); } - assertEquals(m, mt.getSerializer().deserialize(CollectionSerializer.pack(mb, m.size()))); + assertEquals(m, mt.compose(mt.pack(mb))); } @Test(expected = MarshalException.class) @@ -132,7 +130,7 @@ public class SerDeserTest List sb = new ArrayList<>(1); sb.add(null); - st.getSerializer().deserialize(CollectionSerializer.pack(sb, sb.size())); + st.compose(st.pack(sb)); } @Test(expected = MarshalException.class) @@ -143,7 +141,7 @@ public class SerDeserTest mb.add(null); mb.add(LongType.instance.decompose(999L)); - mt.getSerializer().deserialize(CollectionSerializer.pack(mb, mb.size())); + mt.compose(mt.pack(mb)); } @Test(expected = MarshalException.class) @@ -154,7 +152,7 @@ public class SerDeserTest mb.add(UTF8Type.instance.decompose("danger")); mb.add(null); - mt.getSerializer().deserialize(CollectionSerializer.pack(mb, mb.size())); + mt.compose(mt.pack(mb)); } @Test @@ -288,25 +286,25 @@ public class SerDeserTest ByteBuffer serialized = t.bindAndGet(options); - ByteBuffer[] fields = udt.split(ByteBufferAccessor.instance, serialized); + List fields = udt.unpack(serialized); - assertEquals(4, fields.length); + assertEquals(4, fields.size()); - assertEquals(bytes(42L), fields[0]); + assertEquals(bytes(42L), fields.get(0)); // Note that no matter what the protocol version has been used in bindAndGet above, the collections inside // a UDT should alway be serialized with version 3 of the protocol. Which is why we don't use 'version' // on purpose below. - assertEquals(Arrays.asList(3, 1), lt.getSerializer().deserialize(fields[1])); + assertEquals(Arrays.asList(3, 1), lt.getSerializer().deserialize(fields.get(1))); LinkedHashSet s = new LinkedHashSet<>(Arrays.asList("bar", "foo")); - assertEquals(s, st.getSerializer().deserialize(fields[2])); + assertEquals(s, st.getSerializer().deserialize(fields.get(2))); LinkedHashMap m = new LinkedHashMap<>(); m.put("bar", 12L); m.put("foo", 24L); - assertEquals(m, mt.getSerializer().deserialize(fields[3])); + assertEquals(m, mt.getSerializer().deserialize(fields.get(3))); } @Test diff --git a/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java b/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java index 42e1d0221a..e1185edec4 100644 --- a/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java +++ b/test/unit/org/apache/cassandra/utils/AbstractTypeGenerators.java @@ -860,11 +860,11 @@ public final class AbstractTypeGenerators List> columns = (List>) (List) tupleType.allTypes().stream().map(AbstractTypeGenerators::comparator).collect(Collectors.toList()); Comparator> listCompar = listComparator((i, a, b) -> columns.get(i).compare(a, b)); Comparator comparator = (ByteBuffer a, ByteBuffer b) -> { - ByteBuffer[] abb = tupleType.split(ByteBufferAccessor.instance, a); - List av = IntStream.range(0, abb.length).mapToObj(i -> tupleType.type(i).compose(abb[i])).collect(Collectors.toList()); + List abb = tupleType.unpack(a); + List av = IntStream.range(0, abb.size()).mapToObj(i -> tupleType.type(i).compose(abb.get(i))).collect(Collectors.toList()); - ByteBuffer[] bbb = tupleType.split(ByteBufferAccessor.instance, b); - List bv = IntStream.range(0, bbb.length).mapToObj(i -> tupleType.type(i).compose(bbb[i])).collect(Collectors.toList()); + List bbb = tupleType.unpack(b); + List bv = IntStream.range(0, bbb.size()).mapToObj(i -> tupleType.type(i).compose(bbb.get(i))).collect(Collectors.toList()); return listCompar.compare(av, bv); }; support = (TypeSupport) TypeSupport.of(tupleType, new TupleGen(tupleType, sizeGen, valueDomainGen), comparator); @@ -1201,22 +1201,25 @@ public final class AbstractTypeGenerators { private final List> elementsSupport; + private final TupleType type; + @SuppressWarnings("unchecked") private TupleGen(TupleType tupleType, Gen sizeGen, @Nullable Gen valueDomainGen) { this.elementsSupport = tupleType.allTypes().stream().map(t -> getTypeSupport((AbstractType) t, sizeGen, valueDomainGen)).collect(Collectors.toList()); + this.type = tupleType; } public ByteBuffer generate(RandomnessSource rnd) { List> eSupport = this.elementsSupport; - ByteBuffer[] elements = new ByteBuffer[eSupport.size()]; + List elements = new ArrayList<>(eSupport.size()); for (int i = 0; i < eSupport.size(); i++) { TypeSupport support = eSupport.get(i); - elements[i] = support.type.decompose(support.valueGen.generate(rnd)); + elements.add(support.type.decompose(support.valueGen.generate(rnd))); } - return TupleType.buildValue(elements); + return type.pack(elements); } } diff --git a/test/unit/org/apache/cassandra/utils/bytecomparable/AbstractTypeByteSourceTest.java b/test/unit/org/apache/cassandra/utils/bytecomparable/AbstractTypeByteSourceTest.java index 6fb70a3409..2f9ec74ff1 100644 --- a/test/unit/org/apache/cassandra/utils/bytecomparable/AbstractTypeByteSourceTest.java +++ b/test/unit/org/apache/cassandra/utils/bytecomparable/AbstractTypeByteSourceTest.java @@ -41,7 +41,7 @@ import java.util.stream.Stream; import com.google.common.collect.ImmutableList; -import org.apache.cassandra.cql3.Term; +import org.apache.cassandra.cql3.terms.Term; import org.apache.cassandra.db.marshal.*; import org.junit.Assert; import org.junit.Test; @@ -909,11 +909,11 @@ public class AbstractTypeByteSourceTest { for (byte[] bytes : bytesValues) { - ByteBuffer tupleData = TupleType.buildValue(UTF8Type.instance.decompose(utf8), - decimal != null ? DecimalType.instance.decompose(decimal) : null, - varint != null ? IntegerType.instance.decompose(varint) : null, - // We could also use the wrapped bytes directly - BytesType.instance.decompose(ByteBuffer.wrap(bytes))); + ByteBuffer tupleData = tt.pack(UTF8Type.instance.decompose(utf8), + decimal != null ? DecimalType.instance.decompose(decimal) : null, + varint != null ? IntegerType.instance.decompose(varint) : null, + // We could also use the wrapped bytes directly + BytesType.instance.decompose(ByteBuffer.wrap(bytes))); tuplesData.add(tupleData); } } diff --git a/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceComparisonTest.java b/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceComparisonTest.java index 56309e82c1..d49a483bfa 100644 --- a/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceComparisonTest.java +++ b/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceComparisonTest.java @@ -491,20 +491,13 @@ public class ByteSourceComparisonTest extends ByteSourceTestBase TupleType tt = new TupleType(ImmutableList.of(UTF8Type.instance, Int32Type.instance)); List tests = ImmutableList.of ( - TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, ""), - decomposeAndRandomPad(Int32Type.instance, 0)), + tt.pack(decomposeAndRandomPad(UTF8Type.instance, ""), decomposeAndRandomPad(Int32Type.instance, 0)), // Note: a decomposed null (e.g. decomposeAndRandomPad(Int32Type.instance, null)) should not reach a tuple - TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, ""), - null), - TupleType.buildValue(ByteBufferAccessor.instance, - null, - decomposeAndRandomPad(Int32Type.instance, 0)), - TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, "")), - TupleType.buildValue(ByteBufferAccessor.instance, (ByteBuffer) null), - TupleType.buildValue(ByteBufferAccessor.instance) + tt.pack(decomposeAndRandomPad(UTF8Type.instance, ""), null), + tt.pack(null, decomposeAndRandomPad(Int32Type.instance, 0)), + tt.pack(decomposeAndRandomPad(UTF8Type.instance, "")), + tt.pack((ByteBuffer) null), + tt.pack() ); testBuffers(tt, tests); } @@ -515,11 +508,8 @@ public class ByteSourceComparisonTest extends ByteSourceTestBase TupleType t1 = new TupleType(ImmutableList.of(UTF8Type.instance)); TupleType t2 = new TupleType(ImmutableList.of(UTF8Type.instance, Int32Type.instance)); - ByteBuffer vOne = TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, "str")); - ByteBuffer vOneAndNull = TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, "str"), - null); + ByteBuffer vOne = t1.pack(decomposeAndRandomPad(UTF8Type.instance, "str")); + ByteBuffer vOneAndNull = t2.pack(decomposeAndRandomPad(UTF8Type.instance, "str"), null); ByteComparable bOne1 = typeToComparable(t1, vOne); ByteComparable bOne2 = typeToComparable(t2, vOne); @@ -537,12 +527,8 @@ public class ByteSourceComparisonTest extends ByteSourceTestBase void assertTupleComparesSame(AbstractType t1, AbstractType t2, Object o1, Object o2, Object o3, Object o4) { TupleType tt = new TupleType(ImmutableList.of(t1, t2)); - ByteBuffer b1 = TupleType.buildValue(ByteBufferAccessor.instance, - decomposeForTuple(t1, o1), - decomposeForTuple(t2, o2)); - ByteBuffer b2 = TupleType.buildValue(ByteBufferAccessor.instance, - decomposeForTuple(t1, o3), - decomposeForTuple(t2, o4)); + ByteBuffer b1 = tt.pack(decomposeForTuple(t1, o1), decomposeForTuple(t2, o2)); + ByteBuffer b2 = tt.pack(decomposeForTuple(t1, o3), decomposeForTuple(t2, o4)); assertComparesSameBuffers(tt, b1, b2); } diff --git a/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceConversionTest.java b/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceConversionTest.java index 95ea614186..7ab30adfa7 100644 --- a/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceConversionTest.java +++ b/test/unit/org/apache/cassandra/utils/bytecomparable/ByteSourceConversionTest.java @@ -439,19 +439,13 @@ public class ByteSourceConversionTest extends ByteSourceTestBase TupleType tt = new TupleType(ImmutableList.of(UTF8Type.instance, Int32Type.instance)); List tests = ImmutableList.of ( - TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, ""), - decomposeAndRandomPad(Int32Type.instance, 0)), + tt.pack(decomposeAndRandomPad(UTF8Type.instance, ""), decomposeAndRandomPad(Int32Type.instance, 0)), // Note: a decomposed null (e.g. decomposeAndRandomPad(Int32Type.instance, null)) should not reach a tuple - TupleType.buildValue(ByteBufferAccessor.instance, - decomposeAndRandomPad(UTF8Type.instance, ""), - null), - TupleType.buildValue(ByteBufferAccessor.instance, - null, - decomposeAndRandomPad(Int32Type.instance, 0)), - TupleType.buildValue(ByteBufferAccessor.instance, decomposeAndRandomPad(UTF8Type.instance, "")), - TupleType.buildValue(ByteBufferAccessor.instance, (ByteBuffer) null), - TupleType.buildValue(ByteBufferAccessor.instance) + tt.pack(decomposeAndRandomPad(UTF8Type.instance, ""), null), + tt.pack(null, decomposeAndRandomPad(Int32Type.instance, 0)), + tt.pack(decomposeAndRandomPad(UTF8Type.instance, "")), + tt.pack((ByteBuffer) null), + tt.pack() ); testBuffers(tt, tests); } @@ -459,9 +453,7 @@ public class ByteSourceConversionTest extends ByteSourceTestBase void assertTupleConvertsSame(AbstractType t1, AbstractType t2, Object o1, Object o2) { TupleType tt = new TupleType(ImmutableList.of(t1, t2)); - ByteBuffer b1 = TupleType.buildValue(ByteBufferAccessor.instance, - decomposeForTuple(t1, o1), - decomposeForTuple(t2, o2)); + ByteBuffer b1 = tt.pack(decomposeForTuple(t1, o1), decomposeForTuple(t2, o2)); assertConvertsSameBuffers(tt, b1); }