Simplify the bind marker and Term logic

patch by Benjamin Lerer; reviewed by Andrés de la Peña, Ekaterina Dimitrova and Maxwell Guo for CASSANDRA-18813

The patch refactor the Term and Terms interfaces to simplify the logic.
* It removes the MultiItemTerminal and MultiColumnRaw interfaces
* Represents IN bind marker as Terms instead of having 2 different representations (a list of terms and a single MultiItemTerminal).
* Replaces the AbstractMarker hierachy by the Marker class
* It introduces a new MultiElementType that becomes a super class of all the CollectionTypes, TupleType, UserType and VectorType (standardizing the pack and unpack method name and their modifiers)
* It replaces the Value and DelayedValue implementations for the Lists, Sets, Maps, Tuples, UserTypes and Vectors classes by the MultiElements Value and DelayedValue classes.
This commit is contained in:
Benjamin Lerer 2023-08-17 10:03:00 +02:00
parent 1e44a0850b
commit 34fa4e279a
124 changed files with 2721 additions and 2843 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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<Tuples.Literal>())); }
| 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<ColumnIdentifier> ids]
: '(' n1=cident { $ids.add(n1); } (',' ni=cident { $ids.add(ni); })* ')'
;
singleColumnInValues returns [List<Term.Raw> terms]
@init { $terms = new ArrayList<Term.Raw>(); }
: '(' ( t1 = term { $terms.add(t1); } (',' ti=term { $terms.add(ti); })* )? ')'
singleColumnInValues returns [Terms.Raw terms]
@init { List<Term.Raw> list = new ArrayList<>(); }
@after { $terms = Terms.Raw.of(list); }
: '(' ( t1 = term { list.add(t1); } (',' ti=term { list.add(ti); })* )? ')'
;
tupleOfTupleLiterals returns [List<Tuples.Literal> literals]
@init { $literals = new ArrayList<>(); }
: '(' t1=tupleLiteral { $literals.add(t1); } (',' ti=tupleLiteral { $literals.add(ti); })* ')'
tupleOfTupleLiterals returns [Terms.Raw literals]
@init { List<Term.Raw> 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<Tuples.Raw> markers]
@init { $markers = new ArrayList<Tuples.Raw>(); }
: '(' m1=markerForTuple { $markers.add(m1); } (',' mi=markerForTuple { $markers.add(mi); })* ')'
;
inMarkerForTuple returns [Tuples.INRaw marker]
: QMARK { $marker = newTupleINBindVariables(null); }
| ':' name=noncol_ident { $marker = newTupleINBindVariables(name); }
tupleOfMarkersForTuples returns [Terms.Raw markers]
@init { List<Term.Raw> list = new ArrayList<>(); }
@after { $markers = Terms.Raw.of(list); }
: '(' m1=markerForTuple { list.add(m1); } (',' mi=markerForTuple { list.add(mi); })* ')'
;
comparatorType returns [CQL3Type.Raw t]

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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));
}

View File

@ -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<Function> 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));
}
}
}

View File

@ -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;

View File

@ -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<ByteBuffer> values = getType().split(buffer);
List<ByteBuffer> 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;

View File

@ -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;

View File

@ -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<ColumnIdentifier> 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<? extends Term.MultiColumnRaw> 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<ColumnIdentifier> entities, Operator relationType, Term.MultiColumnRaw valuesOrMarker, List<? extends Term.MultiColumnRaw> inValues, Tuples.INRaw inMarker)
private MultiColumnRelation(List<ColumnIdentifier> 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 <code>MultiColumnRelation</code> instance
*/
public static MultiColumnRelation createNonInRelation(List<ColumnIdentifier> entities, Operator relationType, Term.MultiColumnRaw valuesOrMarker)
public static MultiColumnRelation createNonInRelation(List<ColumnIdentifier> 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 <code>MultiColumnRelation</code> instance
*/
public static MultiColumnRelation createInRelation(List<ColumnIdentifier> entities, List<? extends Term.MultiColumnRaw> inValues)
public static MultiColumnRelation createInRelation(List<ColumnIdentifier> 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 <code>MultiColumnRelation</code> instance
*/
public static MultiColumnRelation createSingleMarkerInRelation(List<ColumnIdentifier> entities, Tuples.INRaw inMarker)
{
return new MultiColumnRelation(entities, Operator.IN, null, null, inMarker);
return new MultiColumnRelation(entities, Operator.IN, null, inValues);
}
public List<ColumnIdentifier> 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<? extends Term.Raw> 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<ColumnMetadata> 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<ColumnMetadata> receivers = receivers(table);
List<Term> 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<ColumnMetadata> 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<? extends ColumnSpecification> receivers,
Raw raw,
String keyspace,
VariableSpecifications boundNames) throws InvalidRequestException
{
Term term = ((MultiColumnRaw) raw).prepare(keyspace, receivers);
term.collectMarkerSpecification(boundNames);
return term;
}
protected List<ColumnMetadata> receivers(TableMetadata table) throws InvalidRequestException
{
List<ColumnMetadata> names = new ArrayList<>(getEntities().size());
@ -223,7 +187,7 @@ public class MultiColumnRelation extends Relation
return this;
List<ColumnIdentifier> 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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<? extends Term.Raw> getInValues();
/**
* Checks if this relation apply to multiple columns.
*
* @return <code>true</code> if this relation apply to multiple columns, <code>false</code> otherwise.
*/
public boolean isMultiColumn()
{
return false;
}
public abstract Terms.Raw getInValues();
/**
* Checks if this relation is a token relation (e.g. <pre>token(a) = token(1)</pre>).
@ -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 <code>Raw</code> into a <code>Term</code>.
* @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 <code>Term</code> corresponding to the specified <code>Raw</code>
* @throws InvalidRequestException if the <code>Raw</code> term is not valid
*/
protected abstract Term toTerm(List<? extends ColumnSpecification> 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 <code>Raw</code> terms into a <code>Term</code>s.
* @param receivers the columns to which the values must be associated at
* Converts the specified <code>Raw</code> terms into a <code>Terms</code>.
* @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 <code>Term</code>s corresponding to the specified <code>Raw</code> terms
* @throws InvalidRequestException if the <code>Raw</code> terms are not valid
*/
protected final List<Term> toTerms(List<? extends ColumnSpecification> receivers,
List<? extends Term.Raw> raws,
String keyspace,
VariableSpecifications boundNames)
protected final Terms toTerms(ColumnSpecification receiver,
Terms.Raw raws,
String keyspace,
VariableSpecifications boundNames)
{
if (raws == null)
return null;
List<Term> 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;
}

View File

@ -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 <key> > "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<Term.Raw> inValues;
private final Terms.Raw inValues;
private SingleColumnRelation(ColumnIdentifier entity, Term.Raw mapKey, Operator type, Term.Raw value, List<Term.Raw> 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<? extends Term.Raw> getInValues()
public Terms.Raw getInValues()
{
return inValues;
}
public static SingleColumnRelation createInRelation(ColumnIdentifier entity, List<Term.Raw> 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<? extends ColumnSpecification> 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<? extends ColumnSpecification> 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<? extends ColumnSpecification> receivers = toReceivers(columnDef);
List<Term> 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<? extends ColumnSpecification> 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<ColumnSpecification> 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);
}
}

View File

@ -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<Function> 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<Terminal> bind(QueryOptions options);
public List<ByteBuffer> 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<Function> functions)
{
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
{
marker.collectMarkerSpecification(boundNames);
}
@Override
public List<ByteBuffer> 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<Terminal> 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<ByteBuffer, Term.Terminal> deserializer = deserializer(options.getProtocolVersion());
List<ByteBuffer> boundValues = ((MultiItemTerminal) terminal).getElements();
List<Term.Terminal> 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<ByteBuffer, Term.Terminal> 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<Function> functions)
{
term.addFunctionsTo(functions);
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
{
term.collectMarkerSpecification(boundNames);
}
@Override
public List<ByteBuffer> bindAndGet(QueryOptions options)
{
return Collections.singletonList(term.bindAndGet(options));
}
@Override
public List<Terminal> 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<Term> terms)
{
return new Terms()
{
@Override
public void addFunctionsTo(List<Function> 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 List<Terminal> bind(QueryOptions options)
{
int size = terms.size();
List<Terminal> 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<ByteBuffer> bindAndGet(QueryOptions options)
{
int size = terms.size();
List<ByteBuffer> 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<Term> terms, List<Function> 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);
}
}

View File

@ -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<ColumnIdentifier> 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<? extends Term.Raw> 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<? extends ColumnSpecification> 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<? extends ColumnSpecification> toReceivers(TableMetadata table,
List<ColumnMetadata> columnDefs)
throws InvalidRequestException
private static ColumnSpecification toReceivers(TableMetadata table,
List<ColumnMetadata> 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());
}
}

View File

@ -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<Term.Raw> elements;
public Literal(List<Term.Raw> 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<Term> 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<? extends ColumnSpecification> receivers) throws InvalidRequestException
{
if (elements.size() != receivers.size())
throw new InvalidRequestException(String.format("Expected %d elements in value tuple, but got %d: %s", receivers.size(), elements.size(), this));
List<Term> values = new ArrayList<>(elements.size());
List<AbstractType<?>> 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<AbstractType<?>> 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<ByteBuffer> getElements()
{
return Arrays.asList(elements);
}
}
/**
* Similar to Value, but contains at least one NonTerminal, such as a non-pure functions or bind marker.
*/
public static class DelayedValue extends Term.NonTerminal
{
public final TupleType type;
public final List<Term> elements;
public DelayedValue(TupleType type, List<Term> 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<Function> 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<List<ByteBuffer>> elements;
public InValue(List<List<ByteBuffer>> items)
{
this.elements = items;
}
public static <T> InValue fromSerialized(ByteBuffer value, ListType<T> 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<T> l = type.getSerializer().deserialize(value, ByteBufferAccessor.instance);
assert type.getElementsType() instanceof TupleType;
TupleType tupleType = Tuples.getTupleType(type.getElementsType());
// type.split(bytes)
List<List<ByteBuffer>> 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<List<ByteBuffer>> getSplitValues()
{
return elements;
}
}
/**
* A raw placeholder for a tuple of values for different multiple columns, each of which may have a different type.
* {@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<? extends ColumnSpecification> receivers)
{
List<AbstractType<?>> types = new ArrayList<>(receivers.size());
StringBuilder inName = new StringBuilder("(");
for (int i = 0; i < receivers.size(); i++)
{
ColumnSpecification receiver = receivers.get(i);
inName.append(receiver.name);
if (i < receivers.size() - 1)
inName.append(",");
types.add(receiver.type);
}
inName.append(')');
ColumnIdentifier identifier = new ColumnIdentifier(inName.toString(), true);
TupleType type = new TupleType(types);
return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, type);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return null;
}
public AbstractMarker prepare(String keyspace, List<? extends ColumnSpecification> 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<? extends ColumnSpecification> receivers) throws InvalidRequestException
{
List<AbstractType<?>> types = new ArrayList<>(receivers.size());
StringBuilder inName = new StringBuilder("in(");
for (int i = 0; i < receivers.size(); i++)
{
ColumnSpecification receiver = receivers.get(i);
inName.append(receiver.name);
if (i < receivers.size() - 1)
inName.append(",");
if (receiver.type.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<? extends ColumnSpecification> 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 <code>String</code> representation of the tuple containing the specified elements.
*
* @param elements the tuple elements
* @return a <code>String</code> representation of the tuple
*/
public static String tupleToString(List<?> elements)
{
return tupleToString(elements, Object::toString);
}
/**
* Create a <code>String</code> 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 <code>String</code> representation of the tuple elements
* @return a <code>String</code> representation of the tuple
*/
public static <T> String tupleToString(Iterable<T> items, java.util.function.Function<T, String> 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 <code>null</code>
*/
public static <T> TupleType getExactTupleTypeIfKnown(List<T> items,
java.util.function.Function<T, AbstractType<?>> mapper)
{
List<AbstractType<?>> 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<? extends AssignmentTestable> 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<? extends AssignmentTestable> 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);
}
}

View File

@ -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;

View File

@ -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<ByteBuffer> bindAndGetTerms(QueryOptions options)
{
return filterUnsetValuesIfNeeded(checkValues(terms.bindAndGet(options)));
List<ByteBuffer> 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<Terminal> 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 <T> List<T> checkValues(List<T> 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 <T> List<T> filterUnsetValuesIfNeeded(List<T> values)
private <T> List<T> filterUnsetValuesIfNeeded(List<T> 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<Cell<?>> getCells(Row row, ColumnMetadata column)
protected static Iterator<Cell<?>> 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.<Cell<?>>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<Term.Terminal> values;
private final Terms.Terminals values;
public MultiCellCollectionBound(ColumnMetadata column, Operator operator, List<Term.Terminal> 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<Cell<?>> 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<ByteBuffer> valueList = ((Lists.Value) value).elements;
return listAppliesTo((ListType<?>)type, iter, valueList, operator);
return listAppliesTo((ListType<?>)type, iter, value.getElements(), operator);
case SET:
Set<ByteBuffer> valueSet = ((Sets.Value) value).elements;
return setAppliesTo((SetType<?>)type, iter, valueSet, operator);
return setAppliesTo((SetType<?>)type, iter, value.getElements(), operator);
case MAP:
Map<ByteBuffer, ByteBuffer> 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<Cell<?>> iter, Set<ByteBuffer> elements, Operator operator)
private static boolean setAppliesTo(SetType<?> type, Iterator<Cell<?>> iter, List<ByteBuffer> elements, Operator operator)
{
ArrayList<ByteBuffer> 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<Cell<?>> iter, Map<ByteBuffer, ByteBuffer> elements, Operator operator)
private static boolean mapAppliesTo(MapType<?, ?> type, Iterator<Cell<?>> iter, List<ByteBuffer> elements, Operator operator)
{
Iterator<Map.Entry<ByteBuffer, ByteBuffer>> conditionIter = elements.entrySet().iterator();
Iterator<ByteBuffer> conditionIter = elements.iterator();
while(iter.hasNext())
{
if (!conditionIter.hasNext())
return (operator == Operator.GT) || (operator == Operator.GTE) || (operator == Operator.NEQ);
Map.Entry<ByteBuffer, ByteBuffer> 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<Term.Raw> 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<Term.Raw> 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<Term.Raw> 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<Term.Raw> 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<Term.Raw> 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<Term> prepareTerms(String keyspace, ColumnSpecification receiver, List<Term.Raw> raws)
{
List<Term> 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())

View File

@ -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);
}

View File

@ -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<? extends AssignmentTestable> args)
{
return args.stream().anyMatch(AbstractMarker.Raw.class::isInstance);
return args.stream().anyMatch(Marker.Raw.class::isInstance);
}
/**

View File

@ -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;

View File

@ -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;

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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<ColumnMetadata> columnDefs)
private final Terms values;
public INRestriction(List<ColumnMetadata> columnDefs, Terms values)
{
super(columnDefs);
this.values = values;
}
@Override
public void addFunctionsTo(List<Function> functions)
{
values.addFunctionsTo(functions);
}
/**
@ -271,8 +278,14 @@ public abstract class MultiColumnRestriction implements SingleRestriction
@Override
public MultiCBuilder appendTo(MultiCBuilder builder, QueryOptions options)
{
List<List<ByteBuffer>> splitInValues = splitValues(options);
builder.addAllElementsToAll(splitInValues);
List<List<ByteBuffer>> 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<List<ByteBuffer>> splitValues = splitValues(options);
List<List<ByteBuffer>> splitValues = values.bindAndGetElements(options);
List<ByteBuffer> values = new ArrayList<>(splitValues.size());
for (List<ByteBuffer> 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<List<ByteBuffer>> 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<Term> values;
public InRestrictionWithValues(List<ColumnMetadata> columnDefs, List<Term> values)
{
super(columnDefs);
this.values = values;
}
@Override
public void addFunctionsTo(List<Function> functions)
{
Terms.addFunctions(values, functions);
}
@Override
public String toString()
{
return String.format("IN(%s)", values);
}
@Override
protected List<List<ByteBuffer>> splitValues(QueryOptions options)
{
List<List<ByteBuffer>> buffers = new ArrayList<>(values.size());
for (Term value : values)
{
Term.MultiItemTerminal term = (Term.MultiItemTerminal) value.bind(options);
buffers.add(term.getElements());
}
return buffers;
}
}
/**
* An IN restriction that uses a single marker for a set of IN values that are tuples.
* For example: "SELECT ... WHERE (a, b, c) IN ?"
*/
public static class InRestrictionWithMarker extends INRestriction
{
protected final AbstractMarker marker;
public InRestrictionWithMarker(List<ColumnMetadata> columnDefs, AbstractMarker marker)
{
super(columnDefs);
this.marker = marker;
}
@Override
public void addFunctionsTo(List<Function> functions)
{
}
@Override
public String toString()
{
return "IN ?";
}
@Override
protected List<List<ByteBuffer>> 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<Bound, List<ByteBuffer>> componentBounds)

View File

@ -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<Function> 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<ByteBuffer> 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<ByteBuffer> values = getValues(options);
ByteBuffer buffer = ListSerializer.pack(values, values.size());
List<ByteBuffer> 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<ByteBuffer> getValues(QueryOptions options);
}
public static class InRestrictionWithValues extends INRestriction
{
protected final List<Term> values;
public InRestrictionWithValues(ColumnMetadata columnDef, List<Term> values)
{
super(columnDef);
this.values = values;
}
@Override
MultiColumnRestriction toMultiColumnRestriction()
{
return new MultiColumnRestriction.InRestrictionWithValues(Collections.singletonList(columnDef), values);
}
@Override
public void addFunctionsTo(List<Function> functions)
{
Terms.addFunctions(values, functions);
}
@Override
protected List<ByteBuffer> getValues(QueryOptions options)
{
List<ByteBuffer> 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<Function> functions)
{
}
@Override
MultiColumnRestriction toMultiColumnRestriction()
{
return new MultiColumnRestriction.InRestrictionWithMarker(Collections.singletonList(columnDef), marker);
}
@Override
protected List<ByteBuffer> 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()
{

View File

@ -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;

View File

@ -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;

View File

@ -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> LONG_LIST_TYPE = ListType.getInstance(LongType.instance, false);
private final List<Long> timestamps;
public MultipleTimestamps(TimestampsType type, int initialCapacity)
@ -382,7 +384,7 @@ abstract class ColumnTimestamps
List<ByteBuffer> 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

View File

@ -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));
}
};

View File

@ -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<ByteBuffer> buffers = type.unpack(value);
return field < buffers.size() ? buffers.get(field) : null;
}
@Override

View File

@ -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<Selector> elements)
{
super(Kind.LIST_SELECTOR);
this.type = type;
this.type = (ListType<?>) type;
this.elements = elements;
}

View File

@ -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()

View File

@ -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;

View File

@ -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()

View File

@ -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;

View File

@ -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<ByteBuffer> 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<Selector> elements)
{
super(Kind.TUPLE_SELECTOR);
this.type = type;
this.type = (TupleType) type;
this.elements = elements;
}

View File

@ -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<ByteBuffer> 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<FieldIdentifier, Selector> fields)
{
super(Kind.USER_TYPE_SELECTOR);
this.type = type;
this.type = (UserType) type;
this.fields = fields;
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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))

View File

@ -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);
}

View File

@ -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)

View File

@ -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<Function> 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 <T> Terminals toTerminals(ByteBuffer value,
ListType<T> type,
java.util.function.Function<ByteBuffer, Term.Terminal> terminalConverter)
{
List<T> elements = type.getSerializer().deserialize(value, ByteBufferAccessor.instance);
List<Term.Terminal> 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<ByteBuffer, Term.Terminal> terminalConverter(AbstractType<?> type)
{
if (type instanceof MultiElementType<?>)
return e -> MultiElements.Value.fromSerialized(e, (MultiElementType<?>) type);
return Constants.Value::new;
}
@Override
public List<ByteBuffer> bindAndGet(QueryOptions options)
{
Terminals terminals = bind(options);
return terminals == null ? null : terminals.get();
}
@Override
public List<List<ByteBuffer>> 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 ?}.
* <p>
* 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));
}
}
}

View File

@ -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<ByteBuffer> elements;
public Value(List<ByteBuffer> elements)
{
this.elements = elements;
}
public static <T> Value fromSerialized(ByteBuffer value, ListType<T> 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<T> l = type.getSerializer().deserialize(value, ByteBufferAccessor.instance);
List<ByteBuffer> 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<ByteBuffer> getElements()
{
return elements;
}
}
/**
* Basically similar to a Value, but with some non-pure function (that need
* to be evaluated at execution time) in it.
*
* 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<Term> elements;
public DelayedValue(List<Term> 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<ByteBuffer> buffers = new ArrayList<ByteBuffer>(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<Function> 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<ByteBuffer> elements = ((Value) value).elements;
List<ByteBuffer> 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<ByteBuffer> toAdd = ((Value) value).elements;
List<ByteBuffer> 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<ByteBuffer> toDiscard = ((Value)value).elements;
List<ByteBuffer> 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)

View File

@ -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<Term, Term> 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<Term> values = new ArrayList<>(entries.size() << 1);
boolean allTerminal = true;
for (Pair<Term.Raw, Term.Raw> 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<ByteBuffer, ByteBuffer> map;
public Value(SortedMap<ByteBuffer, ByteBuffer> map)
{
this.map = map;
}
public static <K, V> Value fromSerialized(ByteBuffer value, MapType<K, V> 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<K, V> 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<ByteBuffer, ByteBuffer> map = new TreeMap<>(type.getKeysType());
for (Map.Entry<K, V> 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<ByteBuffer> buffers = new ArrayList<>(2 * map.size());
for (Map.Entry<ByteBuffer, ByteBuffer> 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<Map.Entry<ByteBuffer, ByteBuffer>> thisIter = map.entrySet().iterator();
Iterator<Map.Entry<ByteBuffer, ByteBuffer>> thatIter = v.map.entrySet().iterator();
while (thisIter.hasNext())
{
Map.Entry<ByteBuffer, ByteBuffer> thisEntry = thisIter.next();
Map.Entry<ByteBuffer, ByteBuffer> 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<ByteBuffer> comparator;
private final Map<Term, Term> elements;
public DelayedValue(Comparator<ByteBuffer> comparator, Map<Term, Term> 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<ByteBuffer, ByteBuffer> buffers = new TreeMap<>(comparator);
for (Map.Entry<Term, Term> 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<Function> 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<ByteBuffer, ByteBuffer> elements = ((Value) value).map;
List<ByteBuffer> 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<ByteBuffer, ByteBuffer> entry : elements.entrySet())
Iterator<ByteBuffer> 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()));
}
}
}

View File

@ -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 :<name>} 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.
* <p>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)}</p>
*/
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<Function> 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 "?";
}
}
}

View File

@ -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<ByteBuffer> 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<ByteBuffer> elements)
{
this.type = type;
this.elements = elements;
}
@Override
public ByteBuffer get()
{
return type.pack(elements);
}
@Override
public List<ByteBuffer> 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<Term> elements;
public DelayedValue(MultiElementType<?> type, List<Term> 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<ByteBuffer> 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<Function> functions)
{
Terms.addFunctions(elements, functions);
}
}
}

View File

@ -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<Term> values = new HashSet<>(elements.size());
List<Term> 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<ByteBuffer> elements;
public Value(SortedSet<ByteBuffer> elements)
{
this.elements = elements;
}
public static <T> Value fromSerialized(ByteBuffer value, SetType<T> 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<T> s = type.getSerializer().deserialize(value, ByteBufferAccessor.instance);
SortedSet<ByteBuffer> 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<ByteBuffer> thisIter = elements.iterator();
Iterator<ByteBuffer> 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<ByteBuffer> comparator;
private final Set<Term> elements;
public DelayedValue(Comparator<ByteBuffer> comparator, Set<Term> 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<ByteBuffer> 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<Function> 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<ByteBuffer> elements = ((Value) value).elements;
List<ByteBuffer> 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<ByteBuffer> toDiscard = value instanceof Sets.Value
? ((Sets.Value)value).elements
: Collections.singleton(value.get(params.options.getProtocolVersion()));
List<ByteBuffer> 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()));
}
}
}

View File

@ -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
* <p>
* 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<ByteBuffer> bindAndGetElements(QueryOptions options);
/**
* Whether that term contains at least one bind marker.
* <p>
* 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<Function> functions);
/**
* Adds the functions used by this {@link Term} to the list of functions.
* <p>
* 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<Function> 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.
*
* <p>
* This can be one of:
* - a constant
* - a collection literal
* - a function call
* - a marker
* <ul>
* <li>a constant</li>
* <li>a multi-element type literal</li>
* <li>a function call</li>
* <li>a marker</li>
* </ul>
*/
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<? extends ColumnSpecification> receiver) throws InvalidRequestException;
}
/**
* A terminal term, one that can be reduced to a byte buffer directly.
*
* <p>
* 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).
*
* <p>
* This can be only one of:
* - a constant value
* - a collection value
*
* <ul>
* <li>a constant value</li>
* <li>a multi-element value</li>
* </ul>
* <p>
* 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<Function> 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<ByteBuffer> 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<ByteBuffer> getElements();
@Override
public ByteBuffer bindAndGet(QueryOptions options)
{
return get();
}
@Override
public List<ByteBuffer> 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.
*
* <p>
* 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)
* <ul>
* <li>marker for a constant value</li>
* <li>marker for a multi-element data type value (list, set, map, tuple, udt, vector)</li>
* <li>a function having bind markers</li>
* <li>a non-pure function (even if it doesn't have bind markers - see #5616)</li>
* </ul>
*/
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<ByteBuffer> bindAndGetElements(QueryOptions options)
{
Terminal t = bind(options);
return t == null ? Collections.emptyList() : t.getElements();
}
}
}

View File

@ -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<ByteBuffer> get()
{
return (List<ByteBuffer>) UNSET_LIST;
}
@Override
@SuppressWarnings("unchecked")
public List<List<ByteBuffer>> getElements()
{
return (List<List<ByteBuffer>>) UNSET_LIST;
}
@Override
public List<Terminal> asList()
{
throw new UnsupportedOperationException();
}
@Override
public void addFunctionsTo(List<Function> 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<Function> 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<ByteBuffer> 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<List<ByteBuffer>> 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<Term> terms)
{
boolean allTerminals = terms.stream().allMatch(Term::isTerminal);
if (allTerminals)
{
int size = terms.size();
List<Terminal> 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}.
* <p>
* 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<? extends Term> terms, List<Function> 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<? extends Term.Raw> raws)
{
return new Raw()
{
@Override
public Terms prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
List<Term> 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<ByteBuffer> bindAndGet(QueryOptions options)
{
return get();
}
@Override
public List<List<ByteBuffer>> bindAndGetElements(QueryOptions options)
{
return getElements();
}
/**
* @return the serialized values of this {@code Terminals}.
*/
public abstract List<ByteBuffer> 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<List<ByteBuffer>> getElements();
/**
* Converts these {@code Terminals} into a {@code List} of {@code Term.Terminal}.
* @return a {@code List} of {@code Term.Terminal}.
*/
public abstract List<Term.Terminal> 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<ByteBuffer> get()
{
return Collections.singletonList(terminal == null ? null : terminal.get());
}
@Override
public List<List<ByteBuffer>> getElements()
{
return Collections.singletonList(terminal == null ? null : terminal.getElements());
}
@Override
public List<Terminal> asList()
{
return Collections.singletonList(terminal);
}
@Override
public void addFunctionsTo(List<Function> functions)
{
if (terminal != null)
terminal.addFunctionsTo(functions);
}
@Override
public boolean containsSingleTerm()
{
return true;
}
@Override
public Term asSingleTerm()
{
return terminal;
}
};
}
public static Terminals of(List<Terminal> terminals)
{
return new Terminals()
{
@Override
public List<Terminal> asList()
{
return terminals;
}
@Override
public List<ByteBuffer> get()
{
int size = terminals.size();
List<ByteBuffer> 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<List<ByteBuffer>> getElements()
{
int size = terminals.size();
List<List<ByteBuffer>> 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<Function> 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<Function> 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<ByteBuffer> bindAndGet(QueryOptions options)
{
return Collections.singletonList(term.bindAndGet(options));
}
@Override
public List<List<ByteBuffer>> 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<Term> terms)
{
return new NonTerminals()
{
@Override
public void addFunctionsTo(List<Function> 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<Terminal> 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<ByteBuffer> bindAndGet(QueryOptions options)
{
int size = terms.size();
List<ByteBuffer> 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<List<ByteBuffer>> bindAndGetElements(QueryOptions options)
{
int size = terms.size();
List<List<ByteBuffer>> 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);
}
};
}
}
}

View File

@ -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<? extends ColumnSpecification> receivers)
{
List<AbstractType<?>> types = new ArrayList<>(receivers.size());
StringBuilder inName = new StringBuilder("(");
for (int i = 0; i < receivers.size(); i++)
{
ColumnSpecification receiver = receivers.get(i);
inName.append(receiver.name);
if (i < receivers.size() - 1)
inName.append(',');
types.add(receiver.type);
}
inName.append(')');
ColumnIdentifier identifier = new ColumnIdentifier(inName.toString(), true);
TupleType type = new TupleType(types);
return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, type);
}
/**
* 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<Term.Raw> elements;
public Literal(List<Term.Raw> 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<Term> 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<AbstractType<?>> 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 <code>String</code> representation of the tuple containing the specified elements.
*
* @param elements the tuple elements
* @return a <code>String</code> representation of the tuple
*/
public static String tupleToString(List<?> elements)
{
return tupleToString(elements, Object::toString);
}
/**
* Create a <code>String</code> 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 <code>String</code> representation of the tuple elements
* @return a <code>String</code> representation of the tuple
*/
public static <T> String tupleToString(Iterable<T> items, java.util.function.Function<T, String> 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 <code>null</code>
*/
public static <T> TupleType getExactTupleTypeIfKnown(List<T> items,
java.util.function.Function<T, AbstractType<?>> mapper)
{
List<AbstractType<?>> 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<? extends AssignmentTestable> 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<? extends AssignmentTestable> 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);
}
}

View File

@ -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<ByteBuffer> getElements()
{
return Arrays.asList(elements);
}
}
public static class DelayedValue extends Term.NonTerminal
{
private final UserType type;
private final List<Term> values;
public DelayedValue(UserType type, List<Term> values)
{
this.type = type;
this.values = values;
}
public void addFunctionsTo(List<Function> 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<FieldIdentifier> fieldNameIter = userTypeValue.type.fieldNames().iterator();
for (ByteBuffer buffer : userTypeValue.elements)
Iterator<FieldIdentifier> 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());
}
}

View File

@ -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<T> extends Term.MultiItemTerminal
{
public final VectorType<T> type;
public final List<ByteBuffer> elements;
public Value(VectorType<T> type, List<ByteBuffer> elements)
{
this.type = type;
this.elements = elements;
}
public ByteBuffer get(ProtocolVersion version)
{
return type.decomposeRaw(elements);
}
public List<ByteBuffer> getElements()
{
return elements;
}
}
/**
* Basically similar to a Value, but with some non-pure function (that need
* to be evaluated at execution time) in it.
*/
public static class DelayedValue<T> extends Term.NonTerminal
{
private final VectorType<T> type;
private final List<Term> elements;
public DelayedValue(VectorType<T> type, List<Term> 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<ByteBuffer> 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<Function> functions)
{
Terms.addFunctions(elements, functions);
}
}
}

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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);

View File

@ -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;

View File

@ -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;

View File

@ -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<T> implements Comparator<ByteBuffer>, Assignm
public AbstractType<T> unwrap()
{
return isReversed() ? ((ReversedType<T>) this).baseType.unwrap() : this;
return this;
}
public static AbstractType<?> parseDefaultParameters(AbstractType<?> baseType, TypeParser parser) throws SyntaxException

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<T> extends AbstractType<T>
public abstract class CollectionType<T> extends MultiElementType<T>
{
public static CellPath.Serializer cellPathSerializer = new CollectionPathSerializer();
@ -101,6 +102,12 @@ public abstract class CollectionType<T> extends AbstractType<T>
return kind.makeCollectionReceiver(collection, isKey);
}
@Override
public ByteBuffer decompose(T value)
{
return super.decompose(value);
}
public <V> String getString(V value, ValueAccessor<V> accessor)
{
return BytesType.instance.getString(value, accessor);
@ -155,18 +162,11 @@ public abstract class CollectionType<T> extends AbstractType<T>
return true;
}
// Overrided by maps
protected int collectionSize(List<ByteBuffer> values)
{
return values.size();
}
public ByteBuffer serializeForNativeProtocol(Iterator<Cell<?>> cells)
{
assert isMultiCell();
List<ByteBuffer> 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<T> extends AbstractType<T>
return Integer.compare(sizeL, sizeR);
}
static <V> ByteSource asComparableBytesListOrSet(AbstractType<?> elementsComparator,
ValueAccessor<V> accessor,
V data,
ByteComparable.Version version)
<V> ByteSource asComparableBytesListOrSet(AbstractType<?> elementsComparator,
ValueAccessor<V> accessor,
V data,
ByteComparable.Version version)
{
if (accessor.isEmpty(data))
return null;
@ -312,10 +312,10 @@ public abstract class CollectionType<T> extends AbstractType<T>
return ByteSource.withTerminatorMaybeLegacy(version, 0x00, srcs);
}
static <V> V fromComparableBytesListOrSet(ValueAccessor<V> accessor,
ByteSource.Peekable comparableBytes,
ByteComparable.Version version,
AbstractType<?> elementType)
<V> V fromComparableBytesListOrSet(ValueAccessor<V> accessor,
ByteSource.Peekable comparableBytes,
ByteComparable.Version version,
AbstractType<?> elementType)
{
if (comparableBytes == null)
return accessor.empty();
@ -331,13 +331,50 @@ public abstract class CollectionType<T> extends AbstractType<T>
buffers.add(null);
separator = comparableBytes.next();
}
return CollectionSerializer.pack(buffers, accessor, buffers.size());
return getSerializer().pack(buffers, accessor);
}
@Override
public ByteBuffer pack(List<ByteBuffer> elements)
{
return getSerializer().pack(elements);
}
@Override
public List<ByteBuffer> 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<ByteBuffer> elements)
{
return getSerializer().collectionSize(elements);
}
/**
* Checks if this type of collection support bind markers
* <p>
* 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<T> extends AbstractType<T>
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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<T> extends CollectionType<List<T>>
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<T> extends CollectionType<List<T>>
{
return decompose(Collections.emptyList());
}
@Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
for (ByteBuffer buffer: buffers)
{
if (buffer == null)
throw new MarshalException("null is not supported inside collections");
elements.validate(buffer);
}
return buffers;
}
}

View File

@ -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;

View File

@ -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<K, V> extends CollectionType<Map<K, V>>
@Override
public <T> ByteSource asComparableBytes(ValueAccessor<T> accessor, T data, Version version)
{
return asComparableBytesMap(getKeysType(), getValuesType(), accessor, data, version);
}
@Override
public <T> T fromComparableBytes(ValueAccessor<T> accessor, ByteSource.Peekable comparableBytes, Version version)
{
return fromComparableBytesMap(accessor, comparableBytes, version, getKeysType(), getValuesType());
}
static <V> ByteSource asComparableBytesMap(AbstractType<?> keysComparator,
AbstractType<?> valuesComparator,
ValueAccessor<V> accessor,
V data,
Version version)
{
if (accessor.isEmpty(data))
return null;
@ -259,40 +244,37 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
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> V fromComparableBytesMap(ValueAccessor<V> accessor,
ByteSource.Peekable comparableBytes,
Version version,
AbstractType<?> keysComparator,
AbstractType<?> valuesComparator)
@Override
public <T> T fromComparableBytes(ValueAccessor<T> 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<V> buffers = new ArrayList<>();
List<T> 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<K, V> extends CollectionType<Map<K, V>>
return serializer;
}
@Override
protected int collectionSize(List<ByteBuffer> 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<K, V> extends CollectionType<Map<K, V>>
"Expected a map, but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
Map<?, ?> map = (Map<?, ?>) parsed;
Map<Term, Term> terms = new HashMap<>(map.size());
List<Term> terms = new ArrayList<>(map.size() << 1);
for (Map.Entry<?, ?> entry : map.entrySet())
{
if (entry.getKey() == null)
@ -353,9 +329,10 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
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<K, V> extends CollectionType<Map<K, V>>
{
return decompose(Collections.emptyMap());
}
@Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
// We depend on Maps to be properly sorted by their keys, so use a sorted map implementation here.
SortedMap<ByteBuffer, ByteBuffer> map = new TreeMap<>(getKeysType());
Iterator<ByteBuffer> 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<ByteBuffer> sortedBuffers = new ArrayList<>(map.size() << 1);
for (Map.Entry<ByteBuffer, ByteBuffer> entry : map.entrySet())
{
sortedBuffers.add(entry.getKey());
sortedBuffers.add(entry.getValue());
}
return sortedBuffers;
}
}

View File

@ -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<T> extends AbstractType<T>
{
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<ByteBuffer> 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<ByteBuffer> 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.
* <p>
* 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<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers);
}

View File

@ -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;

View File

@ -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<T> extends AbstractType<T>
{
return baseType.getMaskedValue();
}
@Override
public AbstractType<T> unwrap()
{
return baseType.unwrap();
}
}

View File

@ -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<T> extends CollectionType<Set<T>>
"Expected a list (representing a set), but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
List<?> list = (List<?>) parsed;
Set<Term> terms = new HashSet<>(list.size());
List<Term> terms = new ArrayList<>(list.size());
for (Object element : list)
{
if (element == null)
@ -223,7 +223,7 @@ public class SetType<T> extends CollectionType<Set<T>>
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<T> extends CollectionType<Set<T>>
{
return decompose(Collections.emptySet());
}
@Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
SortedSet<ByteBuffer> 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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<ByteBuffer>
public class TupleType extends MultiElementType<ByteBuffer>
{
private static final String COLON = ":";
private static final Pattern COLON_PAT = Pattern.compile(COLON);
@ -227,10 +233,10 @@ public class TupleType extends AbstractType<ByteBuffer>
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<V> 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<ByteBuffer>
if (accessor.isEmpty(data))
return null;
V[] bufs = split(accessor, data);
List<V> 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<ByteBuffer>
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> V[] split(ValueAccessor<V> accessor, V value)
@Override
public List<ByteBuffer> 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> V[] split(ValueAccessor<V> accessor, V value, int numberOfElements, TupleType type)
public <V> List<V> unpack(V value, ValueAccessor<V> accessor)
{
V[] components = accessor.createArray(numberOfElements);
int numberOfElements = size();
List<V> 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<ByteBuffer>
// 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> V buildValue(ValueAccessor<V> 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> V pack(ValueAccessor<V> accessor, Collection<V> components)
{
int totalLength = 0;
for (V component : components)
@ -361,9 +372,35 @@ public class TupleType extends AbstractType<ByteBuffer>
return result;
}
public static ByteBuffer buildValue(ByteBuffer... components)
@Override
public ByteBuffer pack(List<ByteBuffer> components)
{
return buildValue(ByteBufferAccessor.instance, components);
return pack(ByteBufferAccessor.instance, components);
}
public ByteBuffer pack(ByteBuffer... components)
{
return pack(Arrays.asList(components));
}
@Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> 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<ByteBuffer>
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<ByteBuffer> 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<ByteBuffer>
}
}
return new Tuples.DelayedValue(this, terms);
return new MultiElements.DelayedValue(this, terms);
}
@Override
@ -560,13 +601,10 @@ public class TupleType extends AbstractType<ByteBuffer>
@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<ByteBuffer> buffers = new ArrayList<>(types.size());
for (AbstractType<?> type : types)
buffers.add(type.getMaskedValue());
return serializer.serialize(buildValue(buffers));
return serializer.serialize(pack(buffers));
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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<ByteBuffer> 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 <V> void validateCell(Cell<V> 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<ByteBuffer> 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<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> 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 {

View File

@ -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<T> extends AbstractType<List<T>>
public final class VectorType<T> extends MultiElementType<List<T>>
{
private static class Key
{
@ -136,14 +136,15 @@ public final class VectorType<T> extends AbstractType<List<T>>
return serializer;
}
public List<ByteBuffer> split(ByteBuffer buffer)
@Override
public List<ByteBuffer> unpack(ByteBuffer buffer)
{
return split(buffer, ByteBufferAccessor.instance);
return unpack(buffer, ByteBufferAccessor.instance);
}
public <V> List<V> split(V buffer, ValueAccessor<V> accessor)
public <V> List<V> unpack(V buffer, ValueAccessor<V> accessor)
{
return getSerializer().split(buffer, accessor);
return getSerializer().unpack(buffer, accessor);
}
public float[] composeAsFloat(ByteBuffer input)
@ -191,14 +192,34 @@ public final class VectorType<T> extends AbstractType<List<T>>
return buffer;
}
public ByteBuffer decomposeRaw(List<ByteBuffer> elements)
public ByteBuffer pack(List<ByteBuffer> elements)
{
return decomposeRaw(elements, ByteBufferAccessor.instance);
return pack(elements, ByteBufferAccessor.instance);
}
public <V> V decomposeRaw(List<V> elements, ValueAccessor<V> accessor)
public <V> V pack(List<V> elements, ValueAccessor<V> accessor)
{
return getSerializer().serializeRaw(elements, accessor);
return getSerializer().pack(elements, accessor);
}
@Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> 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<T> extends AbstractType<List<T>>
if (isNull(value, accessor))
return null;
ByteSource[] srcs = new ByteSource[dimension];
List<V> split = split(value, accessor);
List<V> 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<T> extends AbstractType<List<T>>
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<T> extends AbstractType<List<T>>
{
StringBuilder sb = new StringBuilder();
sb.append('[');
List<V> split = split(value, accessor);
List<V> split = unpack(value, accessor);
for (int i = 0; i < dimension; i++)
{
if (i > 0)
@ -311,7 +332,7 @@ public final class VectorType<T> extends AbstractType<List<T>>
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<T> extends AbstractType<List<T>>
public ByteBuffer getMaskedValue()
{
List<ByteBuffer> values = Collections.nCopies(dimension, elementType.getMaskedValue());
return serializer.serializeRaw(values, ByteBufferAccessor.instance);
return serializer.pack(values, ByteBufferAccessor.instance);
}
public abstract class VectorSerializer extends TypeSerializer<List<T>>
{
public abstract <VL, VR> int compareCustom(VL left, ValueAccessor<VL> accessorL, VR right, ValueAccessor<VR> accessorR);
public abstract <V> List<V> split(V buffer, ValueAccessor<V> accessor);
public abstract <V> V serializeRaw(List<V> elements, ValueAccessor<V> accessor);
public abstract <V> List<V> unpack(V buffer, ValueAccessor<V> accessor);
public abstract <V> V pack(List<V> elements, ValueAccessor<V> accessor);
@Override
public String toString(List<T> value)
@ -442,13 +463,18 @@ public final class VectorType<T> extends AbstractType<List<T>>
}
@Override
public <V> List<V> split(V buffer, ValueAccessor<V> accessor)
public <V> List<V> unpack(V buffer, ValueAccessor<V> accessor)
{
if (isNull(buffer, accessor))
return null;
List<V> 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<T> extends AbstractType<List<T>>
}
@Override
public <V> V serializeRaw(List<V> value, ValueAccessor<V> accessor)
public <V> V pack(List<V> value, ValueAccessor<V> accessor)
{
if (value == null)
rejectNullOrEmptyValue();
@ -564,7 +590,7 @@ public final class VectorType<T> extends AbstractType<List<T>>
{
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<T> extends AbstractType<List<T>>
}
@Override
public <V> List<V> split(V buffer, ValueAccessor<V> accessor)
public <V> List<V> unpack(V buffer, ValueAccessor<V> accessor)
{
if (isNull(buffer, accessor))
return null;
List<V> result = new ArrayList<>(dimension);
int offset = 0;
for (int i = 0; i < dimension; i++)
@ -601,7 +629,7 @@ public final class VectorType<T> extends AbstractType<List<T>>
}
@Override
public <V> V serializeRaw(List<V> value, ValueAccessor<V> accessor)
public <V> V pack(List<V> value, ValueAccessor<V> accessor)
{
if (value == null)
rejectNullOrEmptyValue();
@ -626,7 +654,7 @@ public final class VectorType<T> extends AbstractType<List<T>>
List<ByteBuffer> 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

View File

@ -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.
*

View File

@ -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);
}

View File

@ -53,7 +53,8 @@ public abstract class AbstractTextSerializer extends TypeSerializer<String>
}
public String toString(String value)
@Override
public String toString(String value)
{
return value;
}

View File

@ -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<T> extends TypeSerializer<T>
{
protected abstract List<ByteBuffer> serializeValues(T value);
protected abstract int getElementCount(T value);
@Override
public ByteBuffer serialize(T input)
{
List<ByteBuffer> values = serializeValues(input);
return pack(values, ByteBufferAccessor.instance, getElementCount(input));
return pack(values, ByteBufferAccessor.instance);
}
public static ByteBuffer pack(Collection<ByteBuffer> values, int elements)
public ByteBuffer pack(Collection<ByteBuffer> values)
{
return pack(values, ByteBufferAccessor.instance, elements);
return pack(values, ByteBufferAccessor.instance);
}
public static <V> V pack(Collection<V> values, ValueAccessor<V> accessor, int elements)
public <V> V pack(Collection<V> values, ValueAccessor<V> 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<T> extends TypeSerializer<T>
return accessor.valueOf((ByteBuffer) result.flip());
}
public List<ByteBuffer> unpack(ByteBuffer input)
{
return unpack(input, ByteBufferAccessor.instance);
}
public <V> List<V> unpack(V input, ValueAccessor<V> 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<V> 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 <E> the value type, ByteBuffer or byte[]
* @param elements the serialized elements
* @return the size of the collections from the number of serialized elements.
*/
public <E> int collectionSize(Collection<E> 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<T> extends TypeSerializer<T>
}
catch (BufferUnderflowException | IndexOutOfBoundsException e)
{
throw new MarshalException("Not enough bytes to read a set");
throw new MarshalException("Not enough bytes to read a " + getCollectionName());
}
}
}

View File

@ -63,12 +63,6 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
return output;
}
@Override
public int getElementCount(List<T> value)
{
return value.size();
}
@Override
public <V> void validate(V input, ValueAccessor<V> accessor)
{

View File

@ -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<K, V> extends AbstractMapSerializer<Map<K, V>>
return buffers;
}
@Override
public int getElementCount(Map<K, V> value)
public <E> int collectionSize(Collection<E> 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<K, V> extends AbstractMapSerializer<Map<K, V>>
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++)

View File

@ -67,12 +67,6 @@ public class SetSerializer<T> extends AbstractMapSerializer<Set<T>>
return buffers;
}
@Override
public int getElementCount(Set<T> value)
{
return value.size();
}
@Override
public <V> void validate(V input, ValueAccessor<V> accessor)
{
@ -80,9 +74,6 @@ public class SetSerializer<T> extends AbstractMapSerializer<Set<T>>
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++)

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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);

Some files were not shown because too many files have changed in this diff Show More