mirror of https://github.com/apache/cassandra
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:
parent
1e44a0850b
commit
34fa4e279a
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 "?";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ public abstract class AbstractTextSerializer extends TypeSerializer<String>
|
|||
}
|
||||
|
||||
|
||||
public String toString(String value)
|
||||
@Override
|
||||
public String toString(String value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue