Support for frozen collections

Patch by Tyler Hobbs; reviewed by Sylvain Lebresne for CASSANDRA-7859
This commit is contained in:
Tyler Hobbs 2014-11-11 12:40:48 -06:00
parent 0337620994
commit ee55f361b7
61 changed files with 2142 additions and 594 deletions

View File

@ -1,4 +1,5 @@
2.1.3
* Support for frozen collections (CASSANDRA-7859)
* Fix overflow on histogram computation (CASSANDRA-8028)
* Have paxos reuse the timestamp generation of normal queries (CASSANDRA-7801)
Merged from 2.0:

View File

@ -486,6 +486,24 @@ def auto_format_udts():
cassandra.cqltypes.UserType.make_udt_class = classmethod(new_make_udt_class)
class FrozenType(cassandra.cqltypes._ParameterizedType):
"""
Needed until the bundled python driver adds FrozenType.
"""
typename = "frozen"
num_subtypes = 1
@classmethod
def deserialize_safe(cls, byts, protocol_version):
subtype, = cls.subtypes
return subtype.from_binary(byts)
@classmethod
def serialize_safe(cls, val, protocol_version):
subtype, = cls.subtypes
return subtype.to_binary(val, protocol_version)
class Shell(cmd.Cmd):
custom_prompt = os.getenv('CQLSH_PROMPT', '')
if custom_prompt is not '':

View File

@ -255,13 +255,20 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
<userType> ::= utname=<cfOrKsName> ;
<storageType> ::= <simpleStorageType> | <collectionType> | <userType> ;
<storageType> ::= <simpleStorageType> | <collectionType> | <frozenCollectionType> | <userType> ;
# Note: autocomplete for frozen collection types does not handle nesting past depth 1 properly,
# but that's a lot of work to fix for little benefit.
<collectionType> ::= "map" "<" <simpleStorageType> "," ( <simpleStorageType> | <userType> ) ">"
| "list" "<" ( <simpleStorageType> | <userType> ) ">"
| "set" "<" ( <simpleStorageType> | <userType> ) ">"
;
<frozenCollectionType> ::= "frozen" "<" "map" "<" <storageType> "," <storageType> ">" ">"
| "frozen" "<" "list" "<" <storageType> ">" ">"
| "frozen" "<" "set" "<" <storageType> ">" ">"
;
<columnFamilyName> ::= ( ksname=<cfOrKsName> dot="." )? cfname=<cfOrKsName> ;
<userTypeName> ::= ( ksname=<cfOrKsName> dot="." )? utname=<cfOrKsName> ;
@ -906,11 +913,11 @@ syntax_rules += r'''
<cfamOrdering> ::= [ordercol]=<cident> ( "ASC" | "DESC" )
;
<singleKeyCfSpec> ::= [newcolname]=<cident> <simpleStorageType> "PRIMARY" "KEY"
<singleKeyCfSpec> ::= [newcolname]=<cident> <storageType> "PRIMARY" "KEY"
( "," [newcolname]=<cident> <storageType> )*
;
<compositeKeyCfSpec> ::= [newcolname]=<cident> <simpleStorageType>
<compositeKeyCfSpec> ::= [newcolname]=<cident> <storageType>
"," [newcolname]=<cident> <storageType> ( "static" )?
( "," [newcolname]=<cident> <storageType> ( "static" )? )*
"," "PRIMARY" k="KEY" p="(" ( partkey=<pkDef> | [pkey]=<cident> )
@ -986,7 +993,11 @@ def create_cf_composite_primary_key_comma_completer(ctxt, cass):
syntax_rules += r'''
<createIndexStatement> ::= "CREATE" "CUSTOM"? "INDEX" ("IF" "NOT" "EXISTS")? indexname=<identifier>? "ON"
cf=<columnFamilyName> ( "(" col=<cident> ")" | "(" "KEYS" "(" col=<cident> ")" ")")
cf=<columnFamilyName> "(" (
col=<cident> |
"keys(" col=<cident> ")" |
"fullCollection(" col=<cident> ")"
) ")"
( "USING" <stringLiteral> ( "WITH" "OPTIONS" "=" <mapLiteral> )? )?
;

View File

@ -99,7 +99,7 @@ public abstract class AbstractMarker extends Term.NonTerminal
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));
return new ColumnSpecification(receiver.ksName, receiver.cfName, inName, ListType.getInstance(receiver.type, false));
}
@Override

View File

@ -27,7 +27,7 @@ public interface CQL3Row
{
public ByteBuffer getClusteringColumn(int i);
public Cell getColumn(ColumnIdentifier name);
public List<Cell> getCollection(ColumnIdentifier name);
public List<Cell> getMultiCellColumn(ColumnIdentifier name);
public interface Builder
{

View File

@ -26,9 +26,13 @@ import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public interface CQL3Type
{
static final Logger logger = LoggerFactory.getLogger(CQL3Type.class);
public boolean isCollection();
public AbstractType<?> getType();
@ -160,17 +164,30 @@ public interface CQL3Type
@Override
public String toString()
{
boolean isFrozen = !this.type.isMultiCell();
StringBuilder sb = new StringBuilder(isFrozen ? "frozen<" : "");
switch (type.kind)
{
case LIST:
return "list<" + ((ListType)type).elements.asCQL3Type() + ">";
AbstractType<?> listType = ((ListType)type).getElementsType();
sb.append("list<").append(listType.asCQL3Type());
break;
case SET:
return "set<" + ((SetType)type).elements.asCQL3Type() + ">";
AbstractType<?> setType = ((SetType)type).getElementsType();
sb.append("set<").append(setType.asCQL3Type());
break;
case MAP:
MapType mt = (MapType)type;
return "map<" + mt.keys.asCQL3Type() + ", " + mt.values.asCQL3Type() + ">";
AbstractType<?> keysType = ((MapType)type).getKeysType();
AbstractType<?> valuesType = ((MapType)type).getValuesType();
sb.append("map<").append(keysType.asCQL3Type()).append(", ").append(valuesType.asCQL3Type());
break;
default:
throw new AssertionError();
}
throw new AssertionError();
sb.append(">");
if (isFrozen)
sb.append(">");
return sb.toString();
}
}
@ -284,7 +301,9 @@ public interface CQL3Type
// actual type used, so Raw is a "not yet prepared" CQL3Type.
public abstract class Raw
{
protected boolean frozen;
protected boolean frozen = false;
protected abstract boolean supportsFreezing();
public boolean isCollection()
{
@ -296,10 +315,10 @@ public interface CQL3Type
return false;
}
public Raw freeze()
public void freeze() throws InvalidRequestException
{
frozen = true;
return this;
String message = String.format("frozen<> is only allowed on collections, tuples, and user-defined types (got %s)", this);
throw new InvalidRequestException(message);
}
public abstract CQL3Type prepare(String keyspace) throws InvalidRequestException;
@ -314,53 +333,30 @@ public interface CQL3Type
return new RawUT(name);
}
public static Raw map(CQL3Type.Raw t1, CQL3Type.Raw t2) throws InvalidRequestException
public static Raw map(CQL3Type.Raw t1, CQL3Type.Raw t2)
{
if (t1.isCollection() || t2.isCollection())
throw new InvalidRequestException("map type cannot contain another collection");
if (t1.isCounter() || t2.isCounter())
throw new InvalidRequestException("counters are not allowed inside a collection");
return new RawCollection(CollectionType.Kind.MAP, t1, t2);
}
public static Raw list(CQL3Type.Raw t) throws InvalidRequestException
public static Raw list(CQL3Type.Raw t)
{
if (t.isCollection())
throw new InvalidRequestException("list type cannot contain another collection");
if (t.isCounter())
throw new InvalidRequestException("counters are not allowed inside a collection");
return new RawCollection(CollectionType.Kind.LIST, null, t);
}
public static Raw set(CQL3Type.Raw t) throws InvalidRequestException
public static Raw set(CQL3Type.Raw t)
{
if (t.isCollection())
throw new InvalidRequestException("set type cannot contain another collection");
if (t.isCounter())
throw new InvalidRequestException("counters are not allowed inside a collection");
return new RawCollection(CollectionType.Kind.SET, null, t);
}
public static Raw tuple(List<CQL3Type.Raw> ts) throws InvalidRequestException
public static Raw tuple(List<CQL3Type.Raw> ts)
{
for (int i = 0; i < ts.size(); i++)
if (ts.get(i) != null && ts.get(i).isCounter())
throw new InvalidRequestException("counters are not allowed inside tuples");
return new RawTuple(ts);
}
public static Raw frozen(CQL3Type.Raw t) throws InvalidRequestException
{
if (t instanceof RawUT)
return ((RawUT)t).freeze();
if (t instanceof RawTuple)
return ((RawTuple)t).freeze();
throw new InvalidRequestException("frozen<> is only currently only allowed on User-Defined and tuple types");
t.freeze();
return t;
}
private static class RawType extends Raw
@ -377,6 +373,11 @@ public interface CQL3Type
return type;
}
protected boolean supportsFreezing()
{
return false;
}
public boolean isCounter()
{
return type == Native.COUNTER;
@ -402,12 +403,18 @@ public interface CQL3Type
this.values = values;
}
public Raw freeze()
public void freeze() throws InvalidRequestException
{
if (keys != null)
if (keys != null && keys.supportsFreezing())
keys.freeze();
values.freeze();
return super.freeze();
if (values != null && values.supportsFreezing())
values.freeze();
frozen = true;
}
protected boolean supportsFreezing()
{
return true;
}
public boolean isCollection()
@ -417,11 +424,28 @@ public interface CQL3Type
public CQL3Type prepare(String keyspace) throws InvalidRequestException
{
assert values != null : "Got null values type for a collection";
if (!frozen && values.supportsFreezing() && !values.frozen)
throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
if (values.isCounter())
throw new InvalidRequestException("Counters are not allowed inside collections: " + this);
if (keys != null)
{
if (!frozen && keys.supportsFreezing() && !keys.frozen)
throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
}
switch (kind)
{
case LIST: return new Collection(ListType.getInstance(values.prepare(keyspace).getType()));
case SET: return new Collection(SetType.getInstance(values.prepare(keyspace).getType()));
case MAP: return new Collection(MapType.getInstance(keys.prepare(keyspace).getType(), values.prepare(keyspace).getType()));
case LIST:
return new Collection(ListType.getInstance(values.prepare(keyspace).getType(), !frozen));
case SET:
return new Collection(SetType.getInstance(values.prepare(keyspace).getType(), !frozen));
case MAP:
assert keys != null : "Got null keys type for a collection";
return new Collection(MapType.getInstance(keys.prepare(keyspace).getType(), values.prepare(keyspace).getType(), !frozen));
}
throw new AssertionError();
}
@ -429,11 +453,13 @@ public interface CQL3Type
@Override
public String toString()
{
String start = frozen? "frozen<" : "";
String end = frozen ? ">" : "";
switch (kind)
{
case LIST: return "list<" + values + ">";
case SET: return "set<" + values + ">";
case MAP: return "map<" + keys + ", " + values + ">";
case LIST: return start + "list<" + values + ">" + end;
case SET: return start + "set<" + values + ">" + end;
case MAP: return start + "map<" + keys + ", " + values + ">" + end;
}
throw new AssertionError();
}
@ -448,6 +474,11 @@ public interface CQL3Type
this.name = name;
}
public void freeze()
{
frozen = true;
}
public CQL3Type prepare(String keyspace) throws InvalidRequestException
{
if (name.hasKeyspace())
@ -477,7 +508,7 @@ public interface CQL3Type
return new UserDefined(name.toString(), type);
}
public boolean isUDT()
protected boolean supportsFreezing()
{
return true;
}
@ -498,12 +529,9 @@ public interface CQL3Type
this.types = types;
}
public Raw freeze()
protected boolean supportsFreezing()
{
for (CQL3Type.Raw t : types)
if (t != null)
t.freeze();
return super.freeze();
return true;
}
public boolean isCollection()
@ -511,15 +539,29 @@ public interface CQL3Type
return false;
}
public void freeze() throws InvalidRequestException
{
for (CQL3Type.Raw t : types)
{
if (t.supportsFreezing())
t.freeze();
}
frozen = true;
}
public CQL3Type prepare(String keyspace) throws InvalidRequestException
{
if (!frozen)
freeze();
List<AbstractType<?>> ts = new ArrayList<>(types.size());
for (CQL3Type.Raw t : types)
{
if (t.isCounter())
throw new InvalidRequestException("Counters are not allowed inside tuples");
ts.add(t.prepare(keyspace).getType());
if (!frozen)
throw new InvalidRequestException("Non-frozen tuples are not supported, please use frozen<>");
}
return new Tuple(new TupleType(ts));
}

View File

@ -20,7 +20,6 @@ package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import static com.google.common.collect.Lists.newArrayList;
@ -33,12 +32,13 @@ import org.apache.cassandra.db.composites.Composite;
import org.apache.cassandra.db.filter.ColumnSlice;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A CQL3 condition.
* A CQL3 condition on the value of a column or collection element. For example, "UPDATE .. IF a = 0".
*/
public class ColumnCondition
{
@ -235,12 +235,6 @@ public class ColumnCondition
CellName name = current.metadata().comparator.create(rowPrefix, column);
return isSatisfiedByValue(value, current.getColumn(name), column.type, operator, now);
}
@Override
public int hashCode()
{
return Objects.hashCode(column, value, operator);
}
}
/**
@ -275,12 +269,6 @@ public class ColumnCondition
}
return false;
}
@Override
public int hashCode()
{
return Objects.hashCode(column, inValues, operator);
}
}
/** A condition on an element of a collection column. IN operators are not supported here, see ElementAccessInBound. */
@ -305,16 +293,37 @@ public class ColumnCondition
if (column.type instanceof MapType)
{
Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement));
return isSatisfiedByValue(value, cell, ((MapType) column.type).values, operator, now);
MapType mapType = (MapType) column.type;
if (column.type.isMultiCell())
{
Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column, collectionElement));
return isSatisfiedByValue(value, cell, mapType.getValuesType(), operator, now);
}
else
{
Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column));
ByteBuffer mapElementValue = cell.isLive(now) ? mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType())
: null;
return compareWithOperator(operator, mapType.getValuesType(), value, mapElementValue);
}
}
// sets don't have element access, so it's a list
assert column.type instanceof ListType;
ByteBuffer columnValue = getListItem(
collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now),
getListIndex(collectionElement));
return compareWithOperator(operator, ((ListType)column.type).elements, value, columnValue);
ListType listType = (ListType) column.type;
if (column.type.isMultiCell())
{
ByteBuffer columnValue = getListItem(
collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now),
getListIndex(collectionElement));
return compareWithOperator(operator, listType.getElementsType(), value, columnValue);
}
else
{
Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column));
ByteBuffer listElementValue = cell.isLive(now) ? listType.getSerializer().getElement(cell.value(), getListIndex(collectionElement))
: null;
return compareWithOperator(operator, listType.getElementsType(), value, listElementValue);
}
}
static int getListIndex(ByteBuffer collectionElement) throws InvalidRequestException
@ -338,12 +347,6 @@ public class ColumnCondition
{
return collectionElement;
}
@Override
public int hashCode()
{
return Objects.hashCode(column, collectionElement, value, operator);
}
}
static class ElementAccessInBound extends Bound
@ -375,47 +378,86 @@ public class ColumnCondition
CellNameType nameType = current.metadata().comparator;
if (column.type instanceof MapType)
{
CellName name = nameType.create(rowPrefix, column, collectionElement);
Cell item = current.getColumn(name);
AbstractType<?> valueType = ((MapType) column.type).values;
MapType mapType = (MapType) column.type;
AbstractType<?> valueType = mapType.getValuesType();
if (column.type.isMultiCell())
{
CellName name = nameType.create(rowPrefix, column, collectionElement);
Cell item = current.getColumn(name);
for (ByteBuffer value : inValues)
{
if (isSatisfiedByValue(value, item, valueType, Operator.EQ, now))
return true;
}
return false;
}
else
{
Cell cell = current.getColumn(nameType.create(rowPrefix, column));
ByteBuffer mapElementValue = null;
if (cell != null && cell.isLive(now))
mapElementValue = mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType());
for (ByteBuffer value : inValues)
{
if (value == null)
{
if (mapElementValue == null)
return true;
continue;
}
if (valueType.compare(value, mapElementValue) == 0)
return true;
}
return false;
}
}
ListType listType = (ListType) column.type;
AbstractType<?> elementsType = listType.getElementsType();
if (column.type.isMultiCell())
{
ByteBuffer columnValue = ElementAccessBound.getListItem(
collectionColumns(nameType.create(rowPrefix, column), current, now),
ElementAccessBound.getListIndex(collectionElement));
for (ByteBuffer value : inValues)
{
if (isSatisfiedByValue(value, item, valueType, Operator.EQ, now))
if (compareWithOperator(Operator.EQ, elementsType, value, columnValue))
return true;
}
return false;
}
assert column.type instanceof ListType;
ByteBuffer columnValue = ElementAccessBound.getListItem(
collectionColumns(nameType.create(rowPrefix, column), current, now),
ElementAccessBound.getListIndex(collectionElement));
AbstractType<?> valueType = ((ListType) column.type).elements;
for (ByteBuffer value : inValues)
else
{
if (compareWithOperator(Operator.EQ, valueType, value, columnValue))
return true;
Cell cell = current.getColumn(nameType.create(rowPrefix, column));
ByteBuffer listElementValue = null;
if (cell != null && cell.isLive(now))
listElementValue = listType.getSerializer().getElement(cell.value(), ElementAccessBound.getListIndex(collectionElement));
for (ByteBuffer value : inValues)
{
if (value == null)
{
if (listElementValue == null)
return true;
continue;
}
if (elementsType.compare(value, listElementValue) == 0)
return true;
}
}
return false;
}
@Override
public int hashCode()
{
return Objects.hashCode(column, collectionElement, inValues, operator);
}
}
/** A condition on an entire collection column. IN operators are not supported here, see CollectionInBound. */
static class CollectionBound extends Bound
{
public final Term.Terminal value;
private final Term.Terminal value;
private CollectionBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert column.type instanceof CollectionType && condition.collectionElement == null;
assert column.type.isCollection() && condition.collectionElement == null;
assert !condition.operator.equals(Operator.IN);
this.value = condition.value.bind(options);
}
@ -424,18 +466,44 @@ public class ColumnCondition
{
CollectionType type = (CollectionType)column.type;
Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now);
if (type.isMultiCell())
{
Iterator<Cell> iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now);
if (value == null)
{
if (operator.equals(Operator.EQ))
return !iter.hasNext();
else if (operator.equals(Operator.NEQ))
return iter.hasNext();
else
throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator));
}
return valueAppliesTo(type, iter, value, operator);
}
// frozen collections
Cell cell = current.getColumn(current.metadata().comparator.create(rowPrefix, column));
if (value == null)
{
if (operator.equals(Operator.EQ))
return !iter.hasNext();
else if (operator.equals(Operator.NEQ))
return iter.hasNext();
if (operator == Operator.EQ)
return cell == null || !cell.isLive(now);
else if (operator == Operator.NEQ)
return cell != null && cell.isLive(now);
else
throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator));
}
return valueAppliesTo(type, iter, value, operator);
// make sure we use v3 serialization format for comparison
ByteBuffer conditionValue;
if (type.kind == CollectionType.Kind.LIST)
conditionValue = ((Lists.Value) value).getWithProtocolVersion(Server.VERSION_3);
else if (type.kind == CollectionType.Kind.SET)
conditionValue = ((Sets.Value) value).getWithProtocolVersion(Server.VERSION_3);
else
conditionValue = ((Maps.Value) value).getWithProtocolVersion(Server.VERSION_3);
return compareWithOperator(operator, type, conditionValue, cell.value());
}
static boolean valueAppliesTo(CollectionType type, Iterator<Cell> iter, Term.Terminal value, Operator operator)
@ -495,15 +563,15 @@ public class ColumnCondition
static boolean listAppliesTo(ListType type, Iterator<Cell> iter, List<ByteBuffer> elements, Operator operator)
{
return setOrListAppliesTo(type.elements, iter, elements.iterator(), operator, false);
return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false);
}
static boolean setAppliesTo(SetType type, Iterator<Cell> iter, Set<ByteBuffer> elements, Operator operator)
{
ArrayList<ByteBuffer> sortedElements = new ArrayList<>(elements.size());
sortedElements.addAll(elements);
Collections.sort(sortedElements, type.elements);
return setOrListAppliesTo(type.elements, iter, sortedElements.iterator(), operator, true);
Collections.sort(sortedElements, type.getElementsType());
return setOrListAppliesTo(type.getElementsType(), iter, sortedElements.iterator(), operator, true);
}
static boolean mapAppliesTo(MapType type, Iterator<Cell> iter, Map<ByteBuffer, ByteBuffer> elements, Operator operator)
@ -518,12 +586,12 @@ public class ColumnCondition
Cell c = iter.next();
// compare the keys
int comparison = type.keys.compare(c.name().collectionElement(), conditionEntry.getKey());
int comparison = type.getKeysType().compare(c.name().collectionElement(), conditionEntry.getKey());
if (comparison != 0)
return evaluateComparisonWithOperator(comparison, operator);
// compare the values
comparison = type.values.compare(c.value(), conditionEntry.getValue());
comparison = type.getValuesType().compare(c.value(), conditionEntry.getValue());
if (comparison != 0)
return evaluateComparisonWithOperator(comparison, operator);
}
@ -534,33 +602,11 @@ public class ColumnCondition
// they're equal
return operator == Operator.EQ || operator == Operator.LTE || operator == Operator.GTE;
}
@Override
public int hashCode()
{
Object val = null;
if (value != null)
{
switch (((CollectionType)column.type).kind)
{
case LIST:
val = ((Lists.Value)value).elements.hashCode();
break;
case SET:
val = ((Sets.Value)value).elements.hashCode();
break;
case MAP:
val = ((Maps.Value)value).map.hashCode();
break;
}
}
return Objects.hashCode(column, val);
}
}
public static class CollectionInBound extends Bound
{
public final List<Term.Terminal> inValues;
private final List<Term.Terminal> inValues;
private CollectionInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
@ -575,7 +621,7 @@ public class ColumnCondition
Lists.Marker inValuesMarker = (Lists.Marker) condition.value;
if (column.type instanceof ListType)
{
ListType deserializer = ListType.getInstance(collectionType.valueComparator());
ListType deserializer = ListType.getInstance(collectionType.valueComparator(), false);
for (ByteBuffer buffer : inValuesMarker.bind(options).elements)
{
if (buffer == null)
@ -586,7 +632,7 @@ public class ColumnCondition
}
else if (column.type instanceof MapType)
{
MapType deserializer = MapType.getInstance(collectionType.nameComparator(), collectionType.valueComparator());
MapType deserializer = MapType.getInstance(collectionType.nameComparator(), collectionType.valueComparator(), false);
for (ByteBuffer buffer : inValuesMarker.bind(options).elements)
{
if (buffer == null)
@ -597,7 +643,7 @@ public class ColumnCondition
}
else if (column.type instanceof SetType)
{
SetType deserializer = SetType.getInstance(collectionType.valueComparator());
SetType deserializer = SetType.getInstance(collectionType.valueComparator(), false);
for (ByteBuffer buffer : inValuesMarker.bind(options).elements)
{
if (buffer == null)
@ -618,45 +664,34 @@ public class ColumnCondition
{
CollectionType type = (CollectionType)column.type;
CellName name = current.metadata().comparator.create(rowPrefix, column);
// copy iterator contents so that we can properly reuse them for each comparison with an IN value
List<Cell> cells = newArrayList(collectionColumns(name, current, now));
for (Term.Terminal value : inValues)
if (type.isMultiCell())
{
if (CollectionBound.valueAppliesTo(type, cells.iterator(), value, Operator.EQ))
return true;
// copy iterator contents so that we can properly reuse them for each comparison with an IN value
List<Cell> cells = newArrayList(collectionColumns(name, current, now));
for (Term.Terminal value : inValues)
{
if (CollectionBound.valueAppliesTo(type, cells.iterator(), value, Operator.EQ))
return true;
}
return false;
}
return false;
}
@Override
public int hashCode()
{
List<Collection<ByteBuffer>> inValueBuffers = new ArrayList<>(inValues.size());
switch (((CollectionType)column.type).kind)
else
{
case LIST:
for (Term.Terminal term : inValues)
inValueBuffers.add(term == null ? null : ((Lists.Value)term).elements);
break;
case SET:
for (Term.Terminal term : inValues)
inValueBuffers.add(term == null ? null : ((Sets.Value)term).elements);
break;
case MAP:
for (Term.Terminal term : inValues)
Cell cell = current.getColumn(name);
for (Term.Terminal value : inValues)
{
if (value == null)
{
if (term != null)
{
inValueBuffers.add(((Maps.Value)term).map.keySet());
inValueBuffers.add(((Maps.Value)term).map.values());
}
else
inValueBuffers.add(null);
if (cell == null || !cell.isLive(now))
return true;
}
break;
else if (type.compare(((Term.CollectionTerminal)value).getWithProtocolVersion(Server.VERSION_3), cell.value()) == 0)
{
return true;
}
}
return false;
}
return Objects.hashCode(column, inValueBuffers, operator);
}
}

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -368,7 +367,7 @@ public abstract class Constants
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
CellName cname = cf.getComparator().create(prefix, column);
if (column.type.isCollection())
if (column.type.isMultiCell())
cf.addAtom(params.makeRangeTombstone(cname.slice()));
else
cf.addColumn(params.makeTombstone(cname));

View File

@ -575,8 +575,9 @@ createIndexStatement returns [CreateIndexStatement expr]
;
indexIdent returns [IndexTarget.Raw id]
: c=cident { $id = IndexTarget.Raw.of(c); }
: c=cident { $id = IndexTarget.Raw.valuesOf(c); }
| K_KEYS '(' c=cident ')' { $id = IndexTarget.Raw.keysOf(c); }
| K_FULL '(' c=cident ')' { $id = IndexTarget.Raw.fullCollection(c); }
;
@ -1148,21 +1149,21 @@ native_type returns [CQL3Type t]
collection_type returns [CQL3Type.Raw pt]
: K_MAP '<' t1=comparatorType ',' t2=comparatorType '>'
{ try {
{
// if we can't parse either t1 or t2, antlr will "recover" and we may have t1 or t2 null.
if (t1 != null && t2 != null)
$pt = CQL3Type.Raw.map(t1, t2);
} catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } }
}
| K_LIST '<' t=comparatorType '>'
{ try { if (t != null) $pt = CQL3Type.Raw.list(t); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } }
{ if (t != null) $pt = CQL3Type.Raw.list(t); }
| K_SET '<' t=comparatorType '>'
{ try { if (t != null) $pt = CQL3Type.Raw.set(t); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } }
{ if (t != null) $pt = CQL3Type.Raw.set(t); }
;
tuple_type returns [CQL3Type.Raw t]
: K_TUPLE '<' { List<CQL3Type.Raw> types = new ArrayList<>(); }
t1=comparatorType { types.add(t1); } (',' tn=comparatorType { types.add(tn); })*
'>' { try { $t = CQL3Type.Raw.tuple(types); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); }}
'>' { $t = CQL3Type.Raw.tuple(types); }
;
username
@ -1227,6 +1228,7 @@ K_WHERE: W H E R E;
K_AND: A N D;
K_KEY: K E Y;
K_KEYS: K E Y S;
K_FULL: F U L L;
K_INSERT: I N S E R T;
K_UPDATE: U P D A T E;
K_WITH: W I T H;

View File

@ -19,8 +19,6 @@ package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@ -34,6 +32,7 @@ import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.UUIDGen;
@ -56,7 +55,7 @@ public abstract class Lists
public static ColumnSpecification valueSpecOf(ColumnSpecification column)
{
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).elements);
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).getElementsType());
}
public static class Literal implements Term.Raw
@ -124,7 +123,7 @@ public abstract class Lists
}
}
public static class Value extends Term.MultiItemTerminal
public static class Value extends Term.MultiItemTerminal implements Term.CollectionTerminal
{
public final List<ByteBuffer> elements;
@ -143,7 +142,7 @@ public abstract class Lists
List<ByteBuffer> elements = new ArrayList<ByteBuffer>(l.size());
for (Object element : l)
// elements can be null in lists that represent a set of IN values
elements.add(element == null ? null : type.elements.decompose(element));
elements.add(element == null ? null : type.getElementsType().decompose(element));
return new Value(elements);
}
catch (MarshalException e)
@ -154,7 +153,12 @@ public abstract class Lists
public ByteBuffer get(QueryOptions options)
{
return CollectionSerializer.pack(elements, elements.size(), options.getProtocolVersion());
return getWithProtocolVersion(options.getProtocolVersion());
}
public ByteBuffer getWithProtocolVersion(int protocolVersion)
{
return CollectionSerializer.pack(elements, elements.size(), protocolVersion);
}
public boolean equals(ListType lt, Value v)
@ -163,7 +167,7 @@ public abstract class Lists
return false;
for (int i = 0; i < elements.size(); i++)
if (lt.elements.compare(elements.get(i), v.elements.get(i)) != 0)
if (lt.getElementsType().compare(elements.get(i), v.elements.get(i)) != 0)
return false;
return true;
@ -292,9 +296,12 @@ public abstract class Lists
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
// delete + append
CellName name = cf.getComparator().create(prefix, column);
cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
if (column.type.isMultiCell())
{
// delete + append
CellName name = cf.getComparator().create(prefix, column);
cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
}
Appender.doAppend(t, cf, prefix, column, params);
}
}
@ -324,6 +331,9 @@ public abstract class Lists
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
// we should not get here for frozen lists
assert column.type.isMultiCell() : "Attempted to set an individual element on a frozen list";
ByteBuffer index = idx.bindAndGet(params.options);
ByteBuffer value = t.bindAndGet(params.options);
@ -362,23 +372,36 @@ public abstract class Lists
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to append to a frozen list";
doAppend(t, cf, prefix, column, params);
}
static void doAppend(Term t, ColumnFamily cf, Composite prefix, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
// If we append null, do nothing. Note that for Setter, we've
// already removed the previous value so we're good here too
if (value == null)
return;
assert value instanceof Lists.Value;
List<ByteBuffer> toAdd = ((Lists.Value)value).elements;
for (int i = 0; i < toAdd.size(); i++)
Lists.Value listValue = (Lists.Value)value;
if (column.type.isMultiCell())
{
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
cf.addColumn(params.makeColumn(cf.getComparator().create(prefix, column, uuid), toAdd.get(i)));
// If we append null, do nothing. Note that for Setter, we've
// already removed the previous value so we're good here too
if (value == null)
return;
List<ByteBuffer> toAdd = listValue.elements;
for (int i = 0; i < toAdd.size(); i++)
{
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
cf.addColumn(params.makeColumn(cf.getComparator().create(prefix, column, uuid), toAdd.get(i)));
}
}
else
{
// for frozen lists, we're overwriting the whole cell value
CellName name = cf.getComparator().create(prefix, column);
if (value == null)
cf.addAtom(params.makeTombstone(name));
else
cf.addColumn(params.makeColumn(name, listValue.getWithProtocolVersion(Server.CURRENT_VERSION)));
}
}
}
@ -392,6 +415,7 @@ public abstract class Lists
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to prepend to a frozen list";
Term.Terminal value = t.bind(params.options);
if (value == null)
return;
@ -424,6 +448,7 @@ public abstract class Lists
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete from a frozen list";
List<Cell> existingList = params.getPrefetchedList(rowKey, column.name);
// We want to call bind before possibly returning to reject queries where the value provided is not a list.
Term.Terminal value = t.bind(params.options);
@ -464,6 +489,7 @@ public abstract class Lists
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete an item by index from a frozen list";
Term.Terminal index = t.bind(params.options);
if (index == null)
throw new InvalidRequestException("Invalid null value for list index");

View File

@ -19,7 +19,6 @@ package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@ -36,6 +35,7 @@ import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
@ -48,12 +48,12 @@ public abstract class Maps
public static ColumnSpecification keySpecOf(ColumnSpecification column)
{
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType)column.type).keys);
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType)column.type).getKeysType());
}
public static ColumnSpecification valueSpecOf(ColumnSpecification column)
{
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).values);
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).getValuesType());
}
public static class Literal implements Term.Raw
@ -86,7 +86,7 @@ public abstract class Maps
values.put(k, v);
}
DelayedValue value = new DelayedValue(((MapType)receiver.type).keys, values);
DelayedValue value = new DelayedValue(((MapType)receiver.type).getKeysType(), values);
return allTerminal ? value.bind(QueryOptions.DEFAULT) : value;
}
@ -134,7 +134,7 @@ public abstract class Maps
}
}
public static class Value extends Term.Terminal
public static class Value extends Term.Terminal implements Term.CollectionTerminal
{
public final Map<ByteBuffer, ByteBuffer> map;
@ -152,7 +152,7 @@ public abstract class Maps
Map<?, ?> m = (Map<?, ?>)type.getSerializer().deserializeForNativeProtocol(value, version);
Map<ByteBuffer, ByteBuffer> map = new LinkedHashMap<ByteBuffer, ByteBuffer>(m.size());
for (Map.Entry<?, ?> entry : m.entrySet())
map.put(type.keys.decompose(entry.getKey()), type.values.decompose(entry.getValue()));
map.put(type.getKeysType().decompose(entry.getKey()), type.getValuesType().decompose(entry.getValue()));
return new Value(map);
}
catch (MarshalException e)
@ -163,13 +163,18 @@ public abstract class Maps
public ByteBuffer get(QueryOptions options)
{
List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(2 * map.size());
return getWithProtocolVersion(options.getProtocolVersion());
}
public ByteBuffer getWithProtocolVersion(int protocolVersion)
{
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(), options.getProtocolVersion());
return CollectionSerializer.pack(buffers, map.size(), protocolVersion);
}
public boolean equals(MapType mt, Value v)
@ -184,7 +189,7 @@ public abstract class Maps
{
Map.Entry<ByteBuffer, ByteBuffer> thisEntry = thisIter.next();
Map.Entry<ByteBuffer, ByteBuffer> thatEntry = thatIter.next();
if (mt.keys.compare(thisEntry.getKey(), thatEntry.getKey()) != 0 || mt.values.compare(thisEntry.getValue(), thatEntry.getValue()) != 0)
if (mt.getKeysType().compare(thisEntry.getKey(), thatEntry.getKey()) != 0 || mt.getValuesType().compare(thisEntry.getValue(), thatEntry.getValue()) != 0)
return false;
}
@ -266,9 +271,12 @@ public abstract class Maps
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
// delete + put
CellName name = cf.getComparator().create(prefix, column);
cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
if (column.type.isMultiCell())
{
// delete + put
CellName name = cf.getComparator().create(prefix, column);
cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
}
Putter.doPut(t, cf, prefix, column, params);
}
}
@ -292,6 +300,7 @@ public abstract class Maps
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to set a value for a single key on a frozen map";
ByteBuffer key = k.bindAndGet(params.options);
ByteBuffer value = t.bindAndGet(params.options);
if (key == null)
@ -325,21 +334,33 @@ public abstract class Maps
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to add items to a frozen map";
doPut(t, cf, prefix, column, params);
}
static void doPut(Term t, ColumnFamily cf, Composite prefix, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
if (value == null)
return;
assert value instanceof Maps.Value;
Map<ByteBuffer, ByteBuffer> toAdd = ((Maps.Value)value).map;
for (Map.Entry<ByteBuffer, ByteBuffer> entry : toAdd.entrySet())
Maps.Value mapValue = (Maps.Value) value;
if (column.type.isMultiCell())
{
CellName cellName = cf.getComparator().create(prefix, column, entry.getKey());
cf.addColumn(params.makeColumn(cellName, entry.getValue()));
if (value == null)
return;
for (Map.Entry<ByteBuffer, ByteBuffer> entry : mapValue.map.entrySet())
{
CellName cellName = cf.getComparator().create(prefix, column, entry.getKey());
cf.addColumn(params.makeColumn(cellName, entry.getValue()));
}
}
else
{
// for frozen maps, we're overwriting the whole cell
CellName cellName = cf.getComparator().create(prefix, column);
if (value == null)
cf.addAtom(params.makeTombstone(cellName));
else
cf.addColumn(params.makeColumn(cellName, mapValue.getWithProtocolVersion(Server.CURRENT_VERSION)));
}
}
}
@ -353,6 +374,7 @@ public abstract class Maps
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete a single key in a frozen map";
Term.Terminal key = t.bind(params.options);
if (key == null)
throw new InvalidRequestException("Invalid null map key");

View File

@ -163,7 +163,7 @@ public abstract class Operation
if (receiver.type instanceof CounterColumnType)
throw new InvalidRequestException(String.format("Cannot set the value of counter column %s (counters can only be incremented/decremented, not set)", receiver.name));
if (!(receiver.type instanceof CollectionType))
if (!(receiver.type.isCollection()))
return new Constants.Setter(receiver, v);
switch (((CollectionType)receiver.type).kind)
@ -206,6 +206,8 @@ public abstract class Operation
{
if (!(receiver.type instanceof CollectionType))
throw new InvalidRequestException(String.format("Invalid operation (%s) for non collection column %s", toString(receiver), receiver.name));
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
switch (((CollectionType)receiver.type).kind)
{
@ -255,6 +257,8 @@ public abstract class Operation
throw new InvalidRequestException(String.format("Invalid operation (%s) for non counter column %s", toString(receiver), receiver.name));
return new Constants.Adder(receiver, v);
}
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
switch (((CollectionType)receiver.type).kind)
{
@ -296,6 +300,8 @@ public abstract class Operation
throw new InvalidRequestException(String.format("Invalid operation (%s) for non counter column %s", toString(receiver), receiver.name));
return new Constants.Substracter(receiver, value.prepare(keyspace, receiver));
}
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
switch (((CollectionType)receiver.type).kind)
{
@ -308,7 +314,7 @@ public abstract class Operation
ColumnSpecification vr = new ColumnSpecification(receiver.ksName,
receiver.cfName,
receiver.name,
SetType.getInstance(((MapType)receiver.type).keys));
SetType.getInstance(((MapType)receiver.type).getKeysType(), false));
return new Sets.Discarder(receiver, value.prepare(keyspace, vr));
}
throw new AssertionError();
@ -340,6 +346,8 @@ public abstract class Operation
if (!(receiver.type instanceof ListType))
throw new InvalidRequestException(String.format("Invalid operation (%s) for non list column %s", toString(receiver), receiver.name));
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen list column %s", toString(receiver), receiver.name));
return new Lists.Prepender(receiver, v);
}
@ -394,8 +402,10 @@ public abstract class Operation
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
{
if (!(receiver.type instanceof CollectionType))
if (!(receiver.type.isCollection()))
throw new InvalidRequestException(String.format("Invalid deletion operation for non collection column %s", receiver.name));
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid deletion operation for frozen collection column %s", receiver.name));
switch (((CollectionType)receiver.type).kind)
{

View File

@ -18,15 +18,7 @@
package org.apache.cassandra.cql3;
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.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;
import com.google.common.base.Joiner;
@ -34,11 +26,13 @@ import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.ColumnFamily;
import org.apache.cassandra.db.composites.CellName;
import org.apache.cassandra.db.composites.Composite;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
@ -51,7 +45,7 @@ public abstract class Sets
public static ColumnSpecification valueSpecOf(ColumnSpecification column)
{
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).elements);
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).getElementsType());
}
public static class Literal implements Term.Raw
@ -87,7 +81,7 @@ public abstract class Sets
values.add(t);
}
DelayedValue value = new DelayedValue(((SetType)receiver.type).elements, values);
DelayedValue value = new DelayedValue(((SetType)receiver.type).getElementsType(), values);
return allTerminal ? value.bind(QueryOptions.DEFAULT) : value;
}
@ -97,7 +91,7 @@ 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())
if ((receiver.type instanceof MapType) && elements.isEmpty())
return;
throw new InvalidRequestException(String.format("Invalid set literal for %s of type %s", receiver.name, receiver.type.asCQL3Type()));
@ -131,11 +125,11 @@ public abstract class Sets
}
}
public static class Value extends Term.Terminal
public static class Value extends Term.Terminal implements Term.CollectionTerminal
{
public final Set<ByteBuffer> elements;
public final SortedSet<ByteBuffer> elements;
public Value(Set<ByteBuffer> elements)
public Value(SortedSet<ByteBuffer> elements)
{
this.elements = elements;
}
@ -147,9 +141,9 @@ public abstract class Sets
// Collections have this small hack that validate cannot be called on a serialized object,
// but compose does the validation (so we're fine).
Set<?> s = (Set<?>)type.getSerializer().deserializeForNativeProtocol(value, version);
Set<ByteBuffer> elements = new LinkedHashSet<ByteBuffer>(s.size());
SortedSet<ByteBuffer> elements = new TreeSet<ByteBuffer>(type.getElementsType());
for (Object element : s)
elements.add(type.elements.decompose(element));
elements.add(type.getElementsType().decompose(element));
return new Value(elements);
}
catch (MarshalException e)
@ -160,7 +154,12 @@ public abstract class Sets
public ByteBuffer get(QueryOptions options)
{
return CollectionSerializer.pack(new ArrayList<ByteBuffer>(elements), elements.size(), options.getProtocolVersion());
return getWithProtocolVersion(options.getProtocolVersion());
}
public ByteBuffer getWithProtocolVersion(int protocolVersion)
{
return CollectionSerializer.pack(new ArrayList<>(elements), elements.size(), protocolVersion);
}
public boolean equals(SetType st, Value v)
@ -170,8 +169,9 @@ public abstract class Sets
Iterator<ByteBuffer> thisIter = elements.iterator();
Iterator<ByteBuffer> thatIter = v.elements.iterator();
AbstractType elementsType = st.getElementsType();
while (thisIter.hasNext())
if (st.elements.compare(thisIter.next(), thatIter.next()) != 0)
if (elementsType.compare(thisIter.next(), thatIter.next()) != 0)
return false;
return true;
@ -202,7 +202,7 @@ public abstract class Sets
public Value bind(QueryOptions options) throws InvalidRequestException
{
Set<ByteBuffer> buffers = new TreeSet<ByteBuffer>(comparator);
SortedSet<ByteBuffer> buffers = new TreeSet<>(comparator);
for (Term t : elements)
{
ByteBuffer bytes = t.bindAndGet(options);
@ -246,9 +246,12 @@ public abstract class Sets
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
// delete + add
CellName name = cf.getComparator().create(prefix, column);
cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
if (column.type.isMultiCell())
{
// delete + add
CellName name = cf.getComparator().create(prefix, column);
cf.addAtom(params.makeTombstoneForOverwrite(name.slice()));
}
Adder.doAdd(t, cf, prefix, column, params);
}
}
@ -262,22 +265,35 @@ public abstract class Sets
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to add items to a frozen set";
doAdd(t, cf, prefix, column, params);
}
static void doAdd(Term t, ColumnFamily cf, Composite prefix, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
if (value == null)
return;
assert value instanceof Sets.Value : value;
Set<ByteBuffer> toAdd = ((Sets.Value)value).elements;
for (ByteBuffer bb : toAdd)
Sets.Value setValue = (Sets.Value)value;
if (column.type.isMultiCell())
{
CellName cellName = cf.getComparator().create(prefix, column, bb);
cf.addColumn(params.makeColumn(cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER));
if (value == null)
return;
Set<ByteBuffer> toAdd = setValue.elements;
for (ByteBuffer bb : toAdd)
{
CellName cellName = cf.getComparator().create(prefix, column, bb);
cf.addColumn(params.makeColumn(cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER));
}
}
else
{
// for frozen sets, we're overwriting the whole cell
CellName cellName = cf.getComparator().create(prefix, column);
if (value == null)
cf.addAtom(params.makeTombstone(cellName));
else
cf.addColumn(params.makeColumn(cellName, ((Value) value).getWithProtocolVersion(Server.CURRENT_VERSION)));
}
}
}
@ -292,6 +308,8 @@ public abstract class Sets
public void execute(ByteBuffer rowKey, ColumnFamily cf, Composite prefix, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to remove items from a frozen set";
Term.Terminal value = t.bind(params.options);
if (value == null)
return;

View File

@ -138,6 +138,12 @@ public interface Term
public abstract List<ByteBuffer> getElements();
}
public interface CollectionTerminal
{
/** Gets the value of the collection when serialized with the given protocol version format */
public ByteBuffer getWithProtocolVersion(int protocolVersion);
}
/**
* A non terminal term, i.e. a term that can only be reduce to a byte buffer
* at execution time.

View File

@ -248,13 +248,13 @@ public class Tuples
// but the deserialization does the validation (so we're fine).
List<?> l = (List<?>)type.getSerializer().deserializeForNativeProtocol(value, options.getProtocolVersion());
assert type.elements instanceof TupleType;
TupleType tupleType = (TupleType) type.elements;
assert type.getElementsType() instanceof TupleType;
TupleType tupleType = (TupleType) type.getElementsType();
// type.split(bytes)
List<List<ByteBuffer>> elements = new ArrayList<>(l.size());
for (Object element : l)
elements.add(Arrays.asList(tupleType.split(type.elements.decompose(element))));
elements.add(Arrays.asList(tupleType.split(type.getElementsType().decompose(element))));
return new InValue(elements);
}
catch (MarshalException e)
@ -337,15 +337,16 @@ public class Tuples
if (i < receivers.size() - 1)
inName.append(",");
if (receiver.type instanceof CollectionType)
throw new InvalidRequestException("Collection columns do not support IN relations");
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));
return new ColumnSpecification(receivers.get(0).ksName, receivers.get(0).cfName, identifier, ListType.getInstance(type, false));
}
public AbstractMarker prepare(String keyspace, List<? extends ColumnSpecification> receivers) throws InvalidRequestException

View File

@ -267,19 +267,19 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row>
public <T> Set<T> getSet(String column, AbstractType<T> type)
{
ByteBuffer raw = data.get(column);
return raw == null ? null : SetType.getInstance(type).compose(raw);
return raw == null ? null : SetType.getInstance(type, true).compose(raw);
}
public <T> List<T> getList(String column, AbstractType<T> type)
{
ByteBuffer raw = data.get(column);
return raw == null ? null : ListType.getInstance(type).compose(raw);
return raw == null ? null : ListType.getInstance(type, true).compose(raw);
}
public <K, V> Map<K, V> getMap(String column, AbstractType<K> keyType, AbstractType<V> valueType)
{
ByteBuffer raw = data.get(column);
return raw == null ? null : MapType.getInstance(keyType, valueType).compose(raw);
return raw == null ? null : MapType.getInstance(keyType, valueType, true).compose(raw);
}
public List<ColumnSpecification> getColumns()

View File

@ -97,6 +97,6 @@ public class UpdateParameters
return Collections.emptyList();
CQL3Row row = prefetchedLists.get(rowKey);
return row == null ? Collections.<Cell>emptyList() : row.getCollection(cql3ColumnName);
return row == null ? Collections.<Cell>emptyList() : row.getMultiCellColumn(cql3ColumnName);
}
}

View File

@ -24,6 +24,7 @@ import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.transport.Server;
/**
* Static helper methods and classes for user types.
@ -171,7 +172,7 @@ public abstract class UserTypes
buffers[i] = values.get(i).bindAndGet(options);
// Inside UDT values, we must force the serialization of collections to v3 whatever protocol
// version is in use since we're going to store directly that serialized value.
if (version < 3 && type.fieldType(i).isCollection() && buffers[i] != null)
if (version < Server.VERSION_3 && type.fieldType(i).isCollection() && buffers[i] != null)
buffers[i] = ((CollectionType)type.fieldType(i)).getSerializer().reserializeToV3(buffers[i]);
}
return buffers;

View File

@ -120,13 +120,12 @@ public class AlterTableStatement extends SchemaAlteringStatement
throw new InvalidRequestException(String.format("Cannot re-add previously dropped counter column %s", columnName));
AbstractType<?> type = validator.getType();
if (type instanceof CollectionType)
if (type.isCollection() && type.isMultiCell())
{
if (!cfm.comparator.supportCollections())
throw new InvalidRequestException("Cannot use collection types with non-composite PRIMARY KEY");
throw new InvalidRequestException("Cannot use non-frozen collections with a non-composite PRIMARY KEY");
if (cfm.isSuper())
throw new InvalidRequestException("Cannot use collection types with Super column family");
throw new InvalidRequestException("Cannot use non-frozen collections with super column families");
// If there used to be a collection column with the same name (that has been dropped), it will
// still be appear in the ColumnToCollectionType because or reasons explained on #6276. The same
@ -151,35 +150,35 @@ public class AlterTableStatement extends SchemaAlteringStatement
case ALTER:
assert columnName != null;
if (def == null)
throw new InvalidRequestException(String.format("Cell %s was not found in table %s", columnName, columnFamily()));
throw new InvalidRequestException(String.format("Column %s was not found in table %s", columnName, columnFamily()));
AbstractType<?> validatorType = validator.getType();
switch (def.kind)
{
case PARTITION_KEY:
AbstractType<?> newType = validator.getType();
if (newType instanceof CounterColumnType)
if (validatorType instanceof CounterColumnType)
throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", columnName));
if (cfm.getKeyValidator() instanceof CompositeType)
{
List<AbstractType<?>> oldTypes = ((CompositeType) cfm.getKeyValidator()).types;
if (!newType.isValueCompatibleWith(oldTypes.get(def.position())))
if (!validatorType.isValueCompatibleWith(oldTypes.get(def.position())))
throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
columnName,
oldTypes.get(def.position()).asCQL3Type(),
validator));
List<AbstractType<?>> newTypes = new ArrayList<AbstractType<?>>(oldTypes);
newTypes.set(def.position(), newType);
newTypes.set(def.position(), validatorType);
cfm.keyValidator(CompositeType.getInstance(newTypes));
}
else
{
if (!newType.isValueCompatibleWith(cfm.getKeyValidator()))
if (!validatorType.isValueCompatibleWith(cfm.getKeyValidator()))
throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
columnName,
cfm.getKeyValidator().asCQL3Type(),
validator));
cfm.keyValidator(newType);
cfm.keyValidator(validatorType);
}
break;
case CLUSTERING_COLUMN:
@ -187,22 +186,22 @@ public class AlterTableStatement extends SchemaAlteringStatement
// Note that CFMetaData.validateCompatibility already validate the change we're about to do. However, the error message it
// sends is a bit cryptic for a CQL3 user, so validating here for a sake of returning a better error message
// Do note that we need isCompatibleWith here, not just isValueCompatibleWith.
if (!validator.getType().isCompatibleWith(oldType))
if (!validatorType.isCompatibleWith(oldType))
throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are not order-compatible.",
columnName,
oldType.asCQL3Type(),
validator));
cfm.comparator = cfm.comparator.setSubtype(def.position(), validator.getType());
cfm.comparator = cfm.comparator.setSubtype(def.position(), validatorType);
break;
case COMPACT_VALUE:
// See below
if (!validator.getType().isValueCompatibleWith(cfm.getDefaultValidator()))
if (!validatorType.isValueCompatibleWith(cfm.getDefaultValidator()))
throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
columnName,
cfm.getDefaultValidator().asCQL3Type(),
validator));
cfm.defaultValidator(validator.getType());
cfm.defaultValidator(validatorType);
break;
case REGULAR:
case STATIC:
@ -211,23 +210,23 @@ public class AlterTableStatement extends SchemaAlteringStatement
// allow it for CQL3 (see #5882) so validating it explicitly here. We only care about value compatibility
// though since we won't compare values (except when there is an index, but that is validated by
// ColumnDefinition already).
if (!validator.getType().isValueCompatibleWith(def.type))
if (!validatorType.isValueCompatibleWith(def.type))
throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.",
columnName,
def.type.asCQL3Type(),
validator));
// For collections, if we alter the type, we need to update the comparator too since it includes
// the type too (note that isValueCompatibleWith above has validated that the need type don't really
// the type too (note that isValueCompatibleWith above has validated that the new type doesn't
// change the underlying sorting order, but we still don't want to have a discrepancy between the type
// in the comparator and the one in the ColumnDefinition as that would be dodgy).
if (validator.getType() instanceof CollectionType)
cfm.comparator = cfm.comparator.addOrUpdateCollection(def.name, (CollectionType)validator.getType());
if (validatorType.isCollection() && validatorType.isMultiCell())
cfm.comparator = cfm.comparator.addOrUpdateCollection(def.name, (CollectionType)validatorType);
break;
}
// In any case, we update the column definition
cfm.addOrReplaceColumnDefinition(def.withNewType(validator.getType()));
cfm.addOrReplaceColumnDefinition(def.withNewType(validatorType));
break;
case DROP:

View File

@ -214,23 +214,27 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
{
if (type instanceof ListType)
{
AbstractType<?> t = updateWith(((ListType)type).elements, keyspace, toReplace, updated);
return t == null ? null : ListType.getInstance(t);
AbstractType<?> t = updateWith(((ListType)type).getElementsType(), keyspace, toReplace, updated);
if (t == null)
return null;
return ListType.getInstance(t, type.isMultiCell());
}
else if (type instanceof SetType)
{
AbstractType<?> t = updateWith(((SetType)type).elements, keyspace, toReplace, updated);
return t == null ? null : SetType.getInstance(t);
AbstractType<?> t = updateWith(((SetType)type).getElementsType(), keyspace, toReplace, updated);
if (t == null)
return null;
return SetType.getInstance(t, type.isMultiCell());
}
else
{
assert type instanceof MapType;
MapType mt = (MapType)type;
AbstractType<?> k = updateWith(mt.keys, keyspace, toReplace, updated);
AbstractType<?> v = updateWith(mt.values, keyspace, toReplace, updated);
AbstractType<?> k = updateWith(mt.getKeysType(), keyspace, toReplace, updated);
AbstractType<?> v = updateWith(mt.getValuesType(), keyspace, toReplace, updated);
if (k == null && v == null)
return null;
return MapType.getInstance(k == null ? mt.keys : k, v == null ? mt.values : v);
return MapType.getInstance(k == null ? mt.getKeysType() : k, v == null ? mt.getValuesType() : v, type.isMultiCell());
}
}
else

View File

@ -79,8 +79,24 @@ public class CreateIndexStatement extends SchemaAlteringStatement
throw new InvalidRequestException("No column definition found for column " + target.column);
boolean isMap = cd.type instanceof MapType;
if (target.isCollectionKeys && !isMap)
throw new InvalidRequestException("Cannot create index on keys of column " + target + " with non map type");
boolean isFrozenCollection = cd.type.isCollection() && !cd.type.isMultiCell();
if (target.isCollectionKeys)
{
if (!isMap)
throw new InvalidRequestException("Cannot create index on keys of column " + target + " with non-map type");
if (!cd.type.isMultiCell())
throw new InvalidRequestException("Cannot create index on keys of frozen<map> column " + target);
}
else if (target.isFullCollection)
{
if (!isFrozenCollection)
throw new InvalidRequestException("full() indexes can only be created on frozen collections");
}
else if (isFrozenCollection)
{
throw new InvalidRequestException("Frozen collections currently only support full-collection indexes. " +
"For example, 'CREATE INDEX ON <table>(full(<columnName>))'.");
}
if (cd.getIndexType() != null)
{
@ -116,7 +132,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
throw new InvalidRequestException("Secondary indexes are not allowed on static columns");
if (cd.kind == ColumnDefinition.Kind.PARTITION_KEY && cd.isOnAllComponents())
throw new InvalidRequestException(String.format("Cannot add secondary index to already primarily indexed column %s", target.column));
throw new InvalidRequestException(String.format("Cannot create secondary index on partition key column %s", target.column));
}
public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException
@ -139,7 +155,7 @@ public class CreateIndexStatement extends SchemaAlteringStatement
// For now, we only allow indexing values for collections, but we could later allow
// to also index map keys, so we record that this is the values we index to make our
// lives easier then.
if (cd.type.isCollection())
if (cd.type.isCollection() && cd.type.isMultiCell())
options = ImmutableMap.of(target.isCollectionKeys ? SecondaryIndex.INDEX_KEYS_OPTION_NAME
: SecondaryIndex.INDEX_VALUES_OPTION_NAME, "");
cd.setIndexType(IndexType.COMPOSITES, options);

View File

@ -199,16 +199,16 @@ public class CreateTableStatement extends SchemaAlteringStatement
CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns);
Map<ByteBuffer, CollectionType> definedCollections = null;
Map<ByteBuffer, CollectionType> definedMultiCellCollections = null;
for (Map.Entry<ColumnIdentifier, CQL3Type.Raw> entry : definitions.entrySet())
{
ColumnIdentifier id = entry.getKey();
CQL3Type pt = entry.getValue().prepare(keyspace());
if (pt.isCollection())
if (pt.isCollection() && ((CollectionType) pt.getType()).isMultiCell())
{
if (definedCollections == null)
definedCollections = new HashMap<ByteBuffer, CollectionType>();
definedCollections.put(id.bytes, (CollectionType)pt.getType());
if (definedMultiCellCollections == null)
definedMultiCellCollections = new HashMap<>();
definedMultiCellCollections.put(id.bytes, (CollectionType) pt.getType());
}
stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below
}
@ -246,16 +246,16 @@ public class CreateTableStatement extends SchemaAlteringStatement
if (stmt.columns.isEmpty())
throw new InvalidRequestException("No definition found that is not part of the PRIMARY KEY");
if (definedCollections != null)
throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE");
if (definedMultiCellCollections != null)
throw new InvalidRequestException("Non-frozen collection types are not supported with COMPACT STORAGE");
stmt.comparator = new SimpleSparseCellNameType(UTF8Type.instance);
}
else
{
stmt.comparator = definedCollections == null
stmt.comparator = definedMultiCellCollections == null
? new CompoundSparseCellNameType(Collections.<AbstractType<?>>emptyList())
: new CompoundSparseCellNameType.WithCollection(Collections.<AbstractType<?>>emptyList(), ColumnToCollectionType.getInstance(definedCollections));
: new CompoundSparseCellNameType.WithCollection(Collections.<AbstractType<?>>emptyList(), ColumnToCollectionType.getInstance(definedMultiCellCollections));
}
}
else
@ -264,7 +264,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
// standard "dynamic" CF, otherwise it's a composite
if (useCompactStorage && columnAliases.size() == 1)
{
if (definedCollections != null)
if (definedMultiCellCollections != null)
throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE");
ColumnIdentifier alias = columnAliases.get(0);
@ -294,16 +294,16 @@ public class CreateTableStatement extends SchemaAlteringStatement
if (useCompactStorage)
{
if (definedCollections != null)
if (definedMultiCellCollections != null)
throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE");
stmt.comparator = new CompoundDenseCellNameType(types);
}
else
{
stmt.comparator = definedCollections == null
stmt.comparator = definedMultiCellCollections == null
? new CompoundSparseCellNameType(types)
: new CompoundSparseCellNameType.WithCollection(types, ColumnToCollectionType.getInstance(definedCollections));
: new CompoundSparseCellNameType.WithCollection(types, ColumnToCollectionType.getInstance(definedMultiCellCollections));
}
}
}
@ -385,7 +385,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
AbstractType type = columns.get(t);
if (type == null)
throw new InvalidRequestException(String.format("Unknown definition %s referenced in PRIMARY KEY", t));
if (type instanceof CollectionType)
if (type.isCollection() && type.isMultiCell())
throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t));
columns.remove(t);

View File

@ -56,7 +56,7 @@ public class DeleteStatement extends ModificationStatement
// However, if we delete only static colums, it's fine since we won't really use the prefix anyway.
for (Operation deletion : deletions)
if (!deletion.column.isStatic())
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s since %s specified", getFirstEmptyKey(), deletion.column.name));
throw new InvalidRequestException(String.format("Primary key column '%s' must be specified in order to delete column '%s'", getFirstEmptyKey().name, deletion.column.name));
}
if (deletions.isEmpty())

View File

@ -117,11 +117,11 @@ public class DropTypeStatement extends SchemaAlteringStatement
else if (toCheck instanceof CollectionType)
{
if (toCheck instanceof ListType)
return isUsedBy(((ListType)toCheck).elements);
return isUsedBy(((ListType)toCheck).getElementsType());
else if (toCheck instanceof SetType)
return isUsedBy(((SetType)toCheck).elements);
return isUsedBy(((SetType)toCheck).getElementsType());
else
return isUsedBy(((MapType)toCheck).keys) || isUsedBy(((MapType)toCheck).keys);
return isUsedBy(((MapType)toCheck).getKeysType()) || isUsedBy(((MapType)toCheck).getKeysType());
}
return false;
}

View File

@ -24,37 +24,46 @@ public class IndexTarget
{
public final ColumnIdentifier column;
public final boolean isCollectionKeys;
public final boolean isFullCollection;
private IndexTarget(ColumnIdentifier column, boolean isCollectionKeys)
private IndexTarget(ColumnIdentifier column, boolean isCollectionKeys, boolean isFullCollection)
{
this.column = column;
this.isCollectionKeys = isCollectionKeys;
this.isFullCollection = isFullCollection;
}
public static class Raw
{
private final ColumnIdentifier.Raw column;
public final boolean isCollectionKeys;
private final boolean isCollectionKeys;
private final boolean isFullCollection;
private Raw(ColumnIdentifier.Raw column, boolean isCollectionKeys)
private Raw(ColumnIdentifier.Raw column, boolean isCollectionKeys, boolean isFullCollection)
{
this.column = column;
this.isCollectionKeys = isCollectionKeys;
this.isFullCollection = isFullCollection;
}
public static Raw of(ColumnIdentifier.Raw c)
public static Raw valuesOf(ColumnIdentifier.Raw c)
{
return new Raw(c, false);
return new Raw(c, false, false);
}
public static Raw keysOf(ColumnIdentifier.Raw c)
{
return new Raw(c, true);
return new Raw(c, true, false);
}
public static Raw fullCollection(ColumnIdentifier.Raw c)
{
return new Raw(c, false, true);
}
public IndexTarget prepare(CFMetaData cfm)
{
return new IndexTarget(column.prepare(cfm), isCollectionKeys);
return new IndexTarget(column.prepare(cfm), isCollectionKeys, isFullCollection);
}
}
}

View File

@ -40,6 +40,12 @@ public interface Restriction
public boolean isContains();
public boolean isMultiColumn();
/**
* Returns true if, when applied to a clustering column, this restriction can be handled through one or more slices
* alone without filtering. For example, EQ restrictions can be represented as a slice, but CONTAINS cannot.
*/
public boolean canEvaluateWithSlices();
// Not supported by Slice, but it's convenient to have here
public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException;

View File

@ -707,33 +707,52 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
ColumnDefinition def = idIter.next();
assert r != null && !r.isSlice();
List<ByteBuffer> values = r.values(options);
if (values.size() == 1)
if (r.isEQ())
{
ByteBuffer val = values.get(0);
ByteBuffer val = r.values(options).get(0);
if (val == null)
throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
builder.add(val);
}
else
{
// We have a IN, which we only support for the last column.
// If compact, just add all values and we're done. Otherwise,
// for each value of the IN, creates all the columns corresponding to the selection.
if (values.isEmpty())
return null;
SortedSet<CellName> columns = new TreeSet<CellName>(cfm.comparator);
Iterator<ByteBuffer> iter = values.iterator();
while (iter.hasNext())
if (!r.isMultiColumn())
{
ByteBuffer val = iter.next();
if (val == null)
throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
List<ByteBuffer> values = r.values(options);
// We have a IN, which we only support for the last column.
// If compact, just add all values and we're done. Otherwise,
// for each value of the IN, creates all the columns corresponding to the selection.
if (values.isEmpty())
return null;
SortedSet<CellName> columns = new TreeSet<CellName>(cfm.comparator);
Iterator<ByteBuffer> iter = values.iterator();
while (iter.hasNext())
{
ByteBuffer val = iter.next();
if (val == null)
throw new InvalidRequestException(String.format("Invalid null value for clustering key part %s", def.name));
Composite prefix = builder.buildWith(val);
columns.addAll(addSelectedColumns(prefix));
Composite prefix = builder.buildWith(val);
columns.addAll(addSelectedColumns(prefix));
}
return columns;
}
else
{
// we have a multi-column IN restriction
List<List<ByteBuffer>> values = ((MultiColumnRestriction.IN) r).splitValues(options);
TreeSet<CellName> inValues = new TreeSet<>(cfm.comparator);
for (List<ByteBuffer> components : values)
{
for (int i = 0; i < components.size(); i++)
if (components.get(i) == null)
throw new InvalidRequestException("Invalid null value in condition for column " + cfm.clusteringColumns().get(i + def.position()));
Composite prefix = builder.buildWith(components);
inValues.addAll(addSelectedColumns(prefix));
}
return inValues;
}
return columns;
}
}
@ -778,6 +797,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
}
}
/** Returns true if a non-frozen collection is selected, false otherwise. */
private boolean selectACollection()
{
if (!cfm.comparator.hasCollections())
@ -785,7 +805,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
for (ColumnDefinition def : selection.getColumns())
{
if (def.type instanceof CollectionType)
if (def.type.isCollection() && def.type.isMultiCell())
return true;
}
@ -830,7 +850,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
// But if the actual comparator itself is reversed, we must inversed the bounds too.
Bound b = isReversed == isReversedType(def) ? bound : Bound.reverse(bound);
Restriction r = restrictions[def.position()];
if (isNullRestriction(r, b))
if (isNullRestriction(r, b) || !r.canEvaluateWithSlices())
{
// There wasn't any non EQ relation on that key, we select all records having the preceding component as prefix.
// For composites, if there was preceding component and we're computing the end, we must change the last component
@ -846,6 +866,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
}
else
{
// IN or EQ
List<ByteBuffer> values = r.values(options);
if (values.size() != 1)
{
@ -1088,7 +1109,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
expressions.add(new IndexExpression(def.name.bytes, Operator.EQ, value));
}
}
if (usesSecondaryIndexing)
{
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily());
@ -1258,13 +1279,13 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
return;
}
if (def.type.isCollection())
if (def.type.isMultiCell())
{
List<Cell> collection = row.getCollection(def.name);
ByteBuffer value = collection == null
List<Cell> cells = row.getMultiCellColumn(def.name);
ByteBuffer buffer = cells == null
? null
: ((CollectionType)def.type).serializeForNativeProtocol(collection, options.getProtocolVersion());
result.add(value);
: ((CollectionType)def.type).serializeForNativeProtocol(cells, options.getProtocolVersion());
result.add(buffer);
return;
}
@ -1470,16 +1491,26 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
stmt.usesSecondaryIndexing = true;
if (!stmt.usesSecondaryIndexing)
stmt.restrictedColumns.removeAll(cfm.clusteringColumns());
{
for (ColumnDefinition def : cfm.clusteringColumns())
{
// Remove clustering column restrictions that can be handled by slices; the remainder will be
// handled by filters (which may require a secondary index).
Restriction restriction = stmt.columnRestrictions[def.position()];
if (restriction != null)
{
if (restriction.canEvaluateWithSlices())
stmt.restrictedColumns.remove(def);
else
stmt.usesSecondaryIndexing = true;
}
}
}
// Even if usesSecondaryIndexing is false at this point, we'll still have to use one if
// there is restrictions not covered by the PK.
// there are restrictions not covered by the PK.
if (!stmt.metadataRestrictions.isEmpty())
{
if (!hasQueriableIndex)
throw new InvalidRequestException("No indexed columns present in by-columns clause with Equal operator");
stmt.usesSecondaryIndexing = true;
}
if (stmt.usesSecondaryIndexing)
validateSecondaryIndexSelections(stmt);
@ -1510,6 +1541,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
SecondaryIndex index = indexManager.getIndexForColumn(def.name.bytes);
if (index != null && index.supportsOperator(relation.operator()))
return new boolean[]{true, def.kind == ColumnDefinition.Kind.CLUSTERING_COLUMN};
return new boolean[]{false, false};
}
@ -1692,8 +1724,8 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
StorageService.getPartitioner().getTokenValidator());
}
// We don't support relations against entire collections, like "numbers = {1, 2, 3}"
if (receiver.type.isCollection() && !(newRel.operator().equals(Operator.CONTAINS_KEY) || newRel.operator() == Operator.CONTAINS))
// We don't support relations against entire collections (unless they're frozen), like "numbers = {1, 2, 3}"
if (receiver.type.isCollection() && receiver.type.isMultiCell() && !(newRel.operator() == Operator.CONTAINS_KEY || newRel.operator() == Operator.CONTAINS))
{
throw new InvalidRequestException(String.format("Collection column '%s' (%s) cannot be restricted by a '%s' relation",
def.name, receiver.type.asCQL3Type(), newRel.operator()));
@ -1760,7 +1792,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
break;
case CONTAINS_KEY:
if (!(receiver.type instanceof MapType))
throw new InvalidRequestException(String.format("Cannot use CONTAINS_KEY on non-map column %s", def.name));
throw new InvalidRequestException(String.format("Cannot use CONTAINS KEY on non-map column %s", def.name));
// Fallthrough on purpose
case CONTAINS:
{
@ -1771,6 +1803,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
existingRestriction = new SingleColumnRestriction.Contains();
else if (!existingRestriction.isContains())
throw new InvalidRequestException(String.format("Collection column %s can only be restricted by CONTAINS or CONTAINS KEY", def.name));
boolean isKey = newRel.operator() == Operator.CONTAINS_KEY;
receiver = makeCollectionReceiver(receiver, isKey);
Term t = newRel.getValue().prepare(keyspace(), receiver);
@ -1937,6 +1970,12 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache
else if (stmt.selectACollection())
throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by IN relation as a collection is selected by the query", cdef.name));
}
/*
else if (restriction.isContains() && !hasQueriableIndex)
{
throw new InvalidRequestException(String.format("Cannot restrict column \"%s\" by a CONTAINS relation without a secondary index", cdef.name));
}
*/
previous = cdef;
}

View File

@ -73,6 +73,11 @@ public abstract class SingleColumnRestriction implements Restriction
return onToken;
}
public boolean canEvaluateWithSlices()
{
return true;
}
@Override
public String toString()
{
@ -127,6 +132,11 @@ public abstract class SingleColumnRestriction implements Restriction
return false;
}
public boolean canEvaluateWithSlices()
{
return true;
}
@Override
public String toString()
{
@ -181,6 +191,11 @@ public abstract class SingleColumnRestriction implements Restriction
return false;
}
public boolean canEvaluateWithSlices()
{
return true;
}
@Override
public String toString()
{
@ -231,6 +246,11 @@ public abstract class SingleColumnRestriction implements Restriction
return onToken;
}
public boolean canEvaluateWithSlices()
{
return true;
}
/** Returns true if the start or end bound (depending on the argument) is set, false otherwise */
public boolean hasBound(Bound b)
{
@ -412,6 +432,10 @@ public abstract class SingleColumnRestriction implements Restriction
return false;
}
public boolean canEvaluateWithSlices()
{
return false;
}
@Override
public String toString()

View File

@ -64,7 +64,7 @@ public class CFRowAdder
public CFRowAdder resetCollection(String cql3ColumnName)
{
ColumnDefinition def = getDefinition(cql3ColumnName);
assert def.type.isCollection();
assert def.type.isCollection() && def.type.isMultiCell();
Composite name = cf.getComparator().create(prefix, def);
cf.addAtom(new RangeTombstone(name.start(), name.end(), timestamp - 1, ldt));
return this;
@ -75,7 +75,7 @@ public class CFRowAdder
ColumnDefinition def = getDefinition(cql3ColumnName);
assert def.type instanceof MapType;
MapType mt = (MapType)def.type;
CellName name = cf.getComparator().create(prefix, def, mt.keys.decompose(key));
CellName name = cf.getComparator().create(prefix, def, mt.getKeysType().decompose(key));
return add(name, def, value);
}

View File

@ -304,7 +304,7 @@ public abstract class AbstractCellNameType extends AbstractCType implements Cell
return cell;
}
public List<Cell> getCollection(ColumnIdentifier name)
public List<Cell> getMultiCellColumn(ColumnIdentifier name)
{
return null;
}
@ -446,7 +446,7 @@ public abstract class AbstractCellNameType extends AbstractCType implements Cell
return columns == null ? null : columns.get(name);
}
public List<Cell> getCollection(ColumnIdentifier name)
public List<Cell> getMultiCellColumn(ColumnIdentifier name)
{
return collections == null ? null : collections.get(name);
}

View File

@ -95,7 +95,7 @@ public interface CellNameType extends CType
public boolean supportCollections();
/**
* The type of the collections (or null if the type has not collections).
* The type of the collections (or null if the type does not have any non-frozen collections).
*/
public ColumnToCollectionType collectionType();

View File

@ -24,10 +24,7 @@ import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.CQL3Row;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.ColumnToCollectionType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.memory.AbstractAllocator;

View File

@ -40,9 +40,7 @@ import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IndexExpression;
import org.apache.cassandra.db.composites.CellName;
import org.apache.cassandra.db.composites.Composite;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.*;
/**
* Extends a column filter (IFilter) to include a number of IndexExpression.
@ -324,7 +322,7 @@ public abstract class ExtendedFilter
}
else
{
if (def.type.isCollection())
if (def.type.isCollection() && def.type.isMultiCell())
{
if (!collectionSatisfies(def, data, prefix, expression, collectionElement))
return false;
@ -338,16 +336,49 @@ public abstract class ExtendedFilter
if (dataValue == null)
return false;
int v = validator.compare(dataValue, expression.value);
if (!satisfies(v, expression.operator))
return false;
if (expression.operator == Operator.CONTAINS)
{
assert def != null && def.type.isCollection() && !def.type.isMultiCell();
CollectionType type = (CollectionType)def.type;
switch (type.kind)
{
case LIST:
ListType<?> listType = (ListType)def.type;
if (!listType.getSerializer().deserialize(dataValue).contains(listType.getElementsType().getSerializer().deserialize(expression.value)))
return false;
break;
case SET:
SetType<?> setType = (SetType)def.type;
if (!setType.getSerializer().deserialize(dataValue).contains(setType.getElementsType().getSerializer().deserialize(expression.value)))
return false;
break;
case MAP:
MapType<?,?> mapType = (MapType)def.type;
if (!mapType.getSerializer().deserialize(dataValue).containsValue(mapType.getValuesType().getSerializer().deserialize(expression.value)))
return false;
break;
}
}
else if (expression.operator == Operator.CONTAINS_KEY)
{
assert def != null && def.type.isCollection() && !def.type.isMultiCell() && def.type instanceof MapType;
MapType<?,?> mapType = (MapType)def.type;
if (mapType.getSerializer().getSerializedValue(dataValue, expression.value, mapType.getKeysType()) == null)
return false;
}
else
{
int v = validator.compare(dataValue, expression.value);
if (!satisfies(v, expression.operator))
return false;
}
}
return true;
}
private static boolean collectionSatisfies(ColumnDefinition def, ColumnFamily data, Composite prefix, IndexExpression expr, ByteBuffer collectionElement)
{
assert def.type.isCollection();
assert def.type.isCollection() && def.type.isMultiCell();
CollectionType type = (CollectionType)def.type;
if (expr.isContains())

View File

@ -35,11 +35,7 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.BufferDecoratedKey;
import org.apache.cassandra.db.Cell;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.composites.CellName;
import org.apache.cassandra.db.composites.CellNameType;

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.db.index;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -33,6 +34,7 @@ import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.Future;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -585,6 +587,7 @@ public class SecondaryIndexManager
}
// Validate
boolean haveSupportedIndexLookup = false;
for (Map.Entry<String, Set<IndexExpression>> expressions : expressionsByIndexType.entrySet())
{
Set<ByteBuffer> columns = columnsByIndexType.get(expressions.getKey());
@ -593,8 +596,37 @@ public class SecondaryIndexManager
for (IndexExpression expression : expressions.getValue())
{
searcher.validate(expression);
haveSupportedIndexLookup |= secondaryIndex.supportsOperator(expression.operator);
}
}
if (!haveSupportedIndexLookup)
{
// build the error message
int i = 0;
StringBuilder sb = new StringBuilder("No secondary indexes on the restricted columns support the provided operators: ");
for (Map.Entry<String, Set<IndexExpression>> expressions : expressionsByIndexType.entrySet())
{
for (IndexExpression expression : expressions.getValue())
{
if (i++ > 0)
sb.append(", ");
sb.append("'");
String columnName;
try
{
columnName = ByteBufferUtil.string(expression.column);
}
catch (CharacterCodingException ex)
{
columnName = "<unprintable>";
}
sb.append(columnName).append(" ").append(expression.operator).append(" <value>").append("'");
}
}
throw new InvalidRequestException(sb.toString());
}
}
/**

View File

@ -18,12 +18,14 @@
package org.apache.cassandra.db.index;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.util.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ExtendedFilter;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
public abstract class SecondaryIndexSearcher

View File

@ -58,7 +58,7 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
public static CompositesIndex create(ColumnDefinition cfDef)
{
if (cfDef.type.isCollection())
if (cfDef.type.isCollection() && cfDef.type.isMultiCell())
{
switch (((CollectionType)cfDef.type).kind)
{
@ -90,7 +90,7 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
// Check SecondaryIndex.getIndexComparator if you want to know why this is static
public static CellNameType getIndexComparator(CFMetaData baseMetadata, ColumnDefinition cfDef)
{
if (cfDef.type.isCollection())
if (cfDef.type.isCollection() && cfDef.type.isMultiCell())
{
switch (((CollectionType)cfDef.type).kind)
{

View File

@ -98,7 +98,7 @@ public class CompositesIndexOnCollectionValue extends CompositesIndex
@Override
public boolean supportsOperator(Operator operator)
{
return operator == Operator.CONTAINS;
return operator == Operator.CONTAINS && !(columnDef.type instanceof SetType);
}
@Override

View File

@ -223,6 +223,24 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
return false;
}
public boolean isMultiCell()
{
return false;
}
public AbstractType<?> freeze()
{
return this;
}
/**
* @param ignoreFreezing if true, the type string will not be wrapped with FrozenType(...), even if this type is frozen.
*/
public String toString(boolean ignoreFreezing)
{
return this.toString();
}
/**
* The number of subcomponents this type has.
* This is always 1, i.e. the type has only itself as "subcomponents", except for CompositeType.

View File

@ -20,20 +20,20 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.cassandra.db.Cell;
import org.apache.cassandra.transport.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.db.Cell;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* The abstract validator that is the base for maps, sets and lists.
* The abstract validator that is the base for maps, sets and lists (both frozen and non-frozen).
*
* Please note that this comparator shouldn't be used "manually" (through thrift for instance).
*
*/
public abstract class CollectionType<T> extends AbstractType<T>
{
@ -56,27 +56,9 @@ public abstract class CollectionType<T> extends AbstractType<T>
public abstract AbstractType<?> nameComparator();
public abstract AbstractType<?> valueComparator();
protected abstract void appendToStringBuilder(StringBuilder sb);
public abstract List<ByteBuffer> serializedValues(List<Cell> cells);
@Override
public abstract CollectionSerializer<T> getSerializer();
@Override
public void validateCellValue(ByteBuffer cellValue) throws MarshalException
{
valueComparator().validate(cellValue);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
appendToStringBuilder(sb);
return sb.toString();
}
public String getString(ByteBuffer bytes)
{
return BytesType.instance.getString(bytes);
@ -94,27 +76,20 @@ public abstract class CollectionType<T> extends AbstractType<T>
}
}
@Override
public boolean isCompatibleWith(AbstractType<?> previous)
{
if (this == previous)
return true;
if (!getClass().equals(previous.getClass()))
return false;
CollectionType tprev = (CollectionType) previous;
// The name is part of the Cell name, so we need sorting compatibility, i.e. isCompatibleWith().
// But value is the Cell value, so isValueCompatibleWith() is enough
return this.nameComparator().isCompatibleWith(tprev.nameComparator())
&& this.valueComparator().isValueCompatibleWith(tprev.valueComparator());
}
public boolean isCollection()
{
return true;
}
@Override
public void validateCellValue(ByteBuffer cellValue) throws MarshalException
{
if (isMultiCell())
valueComparator().validate(cellValue);
else
super.validateCellValue(cellValue);
}
/**
* Checks if this collection is Map.
* @return <code>true</code> if this collection is a Map, <code>false</code> otherwise.
@ -124,9 +99,11 @@ public abstract class CollectionType<T> extends AbstractType<T>
return kind == Kind.MAP;
}
protected List<Cell> enforceLimit(List<Cell> cells, int version)
public List<Cell> enforceLimit(List<Cell> cells, int version)
{
if (version >= 3 || cells.size() <= MAX_ELEMENTS)
assert isMultiCell();
if (version >= Server.VERSION_3 || cells.size() <= MAX_ELEMENTS)
return cells;
logger.error("Detected collection with {} elements, more than the {} limit. Only the first {} elements will be returned to the client. "
@ -134,15 +111,75 @@ public abstract class CollectionType<T> extends AbstractType<T>
return cells.subList(0, MAX_ELEMENTS);
}
public abstract List<ByteBuffer> serializedValues(List<Cell> cells);
public ByteBuffer serializeForNativeProtocol(List<Cell> cells, int version)
{
assert isMultiCell();
cells = enforceLimit(cells, version);
List<ByteBuffer> values = serializedValues(cells);
return CollectionSerializer.pack(values, cells.size(), version);
}
@Override
public boolean isCompatibleWith(AbstractType<?> previous)
{
if (this == previous)
return true;
if (!getClass().equals(previous.getClass()))
return false;
CollectionType tprev = (CollectionType) previous;
if (this.isMultiCell() != tprev.isMultiCell())
return false;
// subclasses should handle compatibility checks for frozen collections
if (!this.isMultiCell())
return isCompatibleWithFrozen(tprev);
if (!this.nameComparator().isCompatibleWith(tprev.nameComparator()))
return false;
// the value comparator is only used for Cell values, so sorting doesn't matter
return this.valueComparator().isValueCompatibleWith(tprev.valueComparator());
}
@Override
public boolean isValueCompatibleWithInternal(AbstractType<?> previous)
{
// for multi-cell collections, compatibility and value-compatibility are the same
if (this.isMultiCell())
return isCompatibleWith(previous);
if (this == previous)
return true;
if (!getClass().equals(previous.getClass()))
return false;
CollectionType tprev = (CollectionType) previous;
if (this.isMultiCell() != tprev.isMultiCell())
return false;
// subclasses should handle compatibility checks for frozen collections
return isValueCompatibleWithFrozen(tprev);
}
/** A version of isCompatibleWith() to deal with non-multicell (frozen) collections */
protected abstract boolean isCompatibleWithFrozen(CollectionType<?> previous);
/** A version of isValueCompatibleWith() to deal with non-multicell (frozen) collections */
protected abstract boolean isValueCompatibleWithFrozen(CollectionType<?> previous);
public CQL3Type asCQL3Type()
{
return new CQL3Type.Collection(this);
}
@Override
public String toString()
{
return this.toString(false);
}
}

View File

@ -33,7 +33,7 @@ import org.apache.cassandra.utils.ByteBufferUtil;
public class ColumnToCollectionType extends AbstractType<ByteBuffer>
{
// interning instances
private static final Map<Map<ByteBuffer, CollectionType>, ColumnToCollectionType> instances = new HashMap<Map<ByteBuffer, CollectionType>, ColumnToCollectionType>();
private static final Map<Map<ByteBuffer, CollectionType>, ColumnToCollectionType> instances = new HashMap<>();
public final Map<ByteBuffer, CollectionType> defined;

View File

@ -0,0 +1,62 @@
/*
* 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;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.MarshalException;
/**
* A fake type that is only used for parsing type strings that include frozen types.
*/
public class FrozenType extends AbstractType<Void>
{
public static AbstractType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
{
List<AbstractType<?>> innerTypes = parser.getTypeParameters();
if (innerTypes.size() != 1)
throw new SyntaxException("FrozenType() only accepts one parameter");
AbstractType<?> innerType = innerTypes.get(0);
return innerType.freeze();
}
public int compare(ByteBuffer o1, ByteBuffer o2)
{
throw new UnsupportedOperationException();
}
public String getString(ByteBuffer bytes)
{
throw new UnsupportedOperationException();
}
public ByteBuffer fromString(String source) throws MarshalException
{
throw new UnsupportedOperationException();
}
public TypeSerializer<Void> getSerializer()
{
throw new UnsupportedOperationException();
}
}

View File

@ -24,17 +24,21 @@ import org.apache.cassandra.db.Cell;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.ListSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ListType<T> extends CollectionType<List<T>>
{
// interning instances
private static final Map<AbstractType<?>, ListType> instances = new HashMap<AbstractType<?>, ListType>();
private static final Logger logger = LoggerFactory.getLogger(ListType.class);
public final AbstractType<T> elements;
// interning instances
private static final Map<AbstractType<?>, ListType> instances = new HashMap<>();
private static final Map<AbstractType<?>, ListType> frozenInstances = new HashMap<>();
private final AbstractType<T> elements;
public final ListSerializer<T> serializer;
private final boolean isMultiCell;
public static ListType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
{
@ -42,25 +46,32 @@ public class ListType<T> extends CollectionType<List<T>>
if (l.size() != 1)
throw new ConfigurationException("ListType takes exactly 1 type parameter");
return getInstance(l.get(0));
return getInstance(l.get(0), true);
}
public static synchronized <T> ListType<T> getInstance(AbstractType<T> elements)
public static synchronized <T> ListType<T> getInstance(AbstractType<T> elements, boolean isMultiCell)
{
ListType<T> t = instances.get(elements);
Map<AbstractType<?>, ListType> internMap = isMultiCell ? instances : frozenInstances;
ListType<T> t = internMap.get(elements);
if (t == null)
{
t = new ListType<T>(elements);
instances.put(elements, t);
t = new ListType<T>(elements, isMultiCell);
internMap.put(elements, t);
}
return t;
}
private ListType(AbstractType<T> elements)
private ListType(AbstractType<T> elements, boolean isMultiCell)
{
super(Kind.LIST);
this.elements = elements;
this.serializer = ListSerializer.getInstance(elements.getSerializer());
this.isMultiCell = isMultiCell;
}
public AbstractType<T> getElementsType()
{
return elements;
}
public AbstractType<UUID> nameComparator()
@ -78,6 +89,35 @@ public class ListType<T> extends CollectionType<List<T>>
return serializer;
}
@Override
public AbstractType<?> freeze()
{
if (isMultiCell)
return getInstance(this.elements, false);
else
return this;
}
@Override
public boolean isMultiCell()
{
return isMultiCell;
}
@Override
public boolean isCompatibleWithFrozen(CollectionType<?> previous)
{
assert !isMultiCell;
return this.elements.isCompatibleWith(((ListType) previous).elements);
}
@Override
public boolean isValueCompatibleWithFrozen(CollectionType<?> previous)
{
assert !isMultiCell;
return this.elements.isValueCompatibleWithInternal(((ListType) previous).elements);
}
@Override
public int compare(ByteBuffer o1, ByteBuffer o2)
{
@ -86,7 +126,7 @@ public class ListType<T> extends CollectionType<List<T>>
static int compareListOrSet(AbstractType<?> elementsComparator, ByteBuffer o1, ByteBuffer o2)
{
// Note that this is only used if the collection is inside an UDT
// Note that this is only used if the collection is frozen
if (!o1.hasRemaining() || !o2.hasRemaining())
return o1.hasRemaining() ? 1 : o2.hasRemaining() ? -1 : 0;
@ -108,13 +148,24 @@ public class ListType<T> extends CollectionType<List<T>>
return size1 == size2 ? 0 : (size1 < size2 ? -1 : 1);
}
protected void appendToStringBuilder(StringBuilder sb)
@Override
public String toString(boolean ignoreFreezing)
{
sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements)));
boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
StringBuilder sb = new StringBuilder();
if (includeFrozenType)
sb.append(FrozenType.class.getName()).append("(");
sb.append(getClass().getName());
sb.append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements), ignoreFreezing || !isMultiCell));
if (includeFrozenType)
sb.append(")");
return sb.toString();
}
public List<ByteBuffer> serializedValues(List<Cell> cells)
{
assert isMultiCell;
List<ByteBuffer> bbs = new ArrayList<ByteBuffer>(cells.size());
for (Cell c : cells)
bbs.add(c.value());

View File

@ -24,19 +24,20 @@ import org.apache.cassandra.db.Cell;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.MapSerializer;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.ByteBufferUtil;
public class MapType<K, V> extends CollectionType<Map<K, V>>
{
// interning instances
private static final Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> instances = new HashMap<Pair<AbstractType<?>, AbstractType<?>>, MapType>();
private static final Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> instances = new HashMap<>();
private static final Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> frozenInstances = new HashMap<>();
public final AbstractType<K> keys;
public final AbstractType<V> values;
private final AbstractType<K> keys;
private final AbstractType<V> values;
private final MapSerializer<K, V> serializer;
private final boolean isMultiCell;
public static MapType<?, ?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
{
@ -44,27 +45,39 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
if (l.size() != 2)
throw new ConfigurationException("MapType takes exactly 2 type parameters");
return getInstance(l.get(0), l.get(1));
return getInstance(l.get(0), l.get(1), true);
}
public static synchronized <K, V> MapType<K, V> getInstance(AbstractType<K> keys, AbstractType<V> values)
public static synchronized <K, V> MapType<K, V> getInstance(AbstractType<K> keys, AbstractType<V> values, boolean isMultiCell)
{
Map<Pair<AbstractType<?>, AbstractType<?>>, MapType> internMap = isMultiCell ? instances : frozenInstances;
Pair<AbstractType<?>, AbstractType<?>> p = Pair.<AbstractType<?>, AbstractType<?>>create(keys, values);
MapType<K, V> t = instances.get(p);
MapType<K, V> t = internMap.get(p);
if (t == null)
{
t = new MapType<K, V>(keys, values);
instances.put(p, t);
t = new MapType<>(keys, values, isMultiCell);
internMap.put(p, t);
}
return t;
}
private MapType(AbstractType<K> keys, AbstractType<V> values)
private MapType(AbstractType<K> keys, AbstractType<V> values, boolean isMultiCell)
{
super(Kind.MAP);
this.keys = keys;
this.values = values;
this.serializer = MapSerializer.getInstance(keys.getSerializer(), values.getSerializer());
this.isMultiCell = isMultiCell;
}
public AbstractType<K> getKeysType()
{
return keys;
}
public AbstractType<V> getValuesType()
{
return values;
}
public AbstractType<K> nameComparator()
@ -77,30 +90,66 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
return values;
}
@Override
public boolean isMultiCell()
{
return isMultiCell;
}
@Override
public AbstractType<?> freeze()
{
if (isMultiCell)
return getInstance(this.keys, this.values, false);
else
return this;
}
@Override
public boolean isCompatibleWithFrozen(CollectionType<?> previous)
{
assert !isMultiCell;
MapType tprev = (MapType) previous;
return keys.isCompatibleWith(tprev.keys) && values.isCompatibleWith(tprev.values);
}
@Override
public boolean isValueCompatibleWithFrozen(CollectionType<?> previous)
{
assert !isMultiCell;
MapType tprev = (MapType) previous;
return keys.isCompatibleWith(tprev.keys) && values.isValueCompatibleWith(tprev.values);
}
@Override
public int compare(ByteBuffer o1, ByteBuffer o2)
{
// Note that this is only used if the collection is inside an UDT
if (!o1.hasRemaining() || !o2.hasRemaining())
return compareMaps(keys, values, o1, o2);
}
public static int compareMaps(AbstractType<?> keysComparator, AbstractType<?> valuesComparator, ByteBuffer o1, ByteBuffer o2)
{
if (!o1.hasRemaining() || !o2.hasRemaining())
return o1.hasRemaining() ? 1 : o2.hasRemaining() ? -1 : 0;
ByteBuffer bb1 = o1.duplicate();
ByteBuffer bb2 = o2.duplicate();
int size1 = CollectionSerializer.readCollectionSize(bb1, 3);
int size2 = CollectionSerializer.readCollectionSize(bb2, 3);
int protocolVersion = Server.VERSION_3;
int size1 = CollectionSerializer.readCollectionSize(bb1, protocolVersion);
int size2 = CollectionSerializer.readCollectionSize(bb2, protocolVersion);
for (int i = 0; i < Math.min(size1, size2); i++)
{
ByteBuffer k1 = CollectionSerializer.readValue(bb1, 3);
ByteBuffer k2 = CollectionSerializer.readValue(bb2, 3);
int cmp = keys.compare(k1, k2);
ByteBuffer k1 = CollectionSerializer.readValue(bb1, protocolVersion);
ByteBuffer k2 = CollectionSerializer.readValue(bb2, protocolVersion);
int cmp = keysComparator.compare(k1, k2);
if (cmp != 0)
return cmp;
ByteBuffer v1 = CollectionSerializer.readValue(bb1, 3);
ByteBuffer v2 = CollectionSerializer.readValue(bb2, 3);
cmp = values.compare(v1, v2);
ByteBuffer v1 = CollectionSerializer.readValue(bb1, protocolVersion);
ByteBuffer v2 = CollectionSerializer.readValue(bb2, protocolVersion);
cmp = valuesComparator.compare(v1, v2);
if (cmp != 0)
return cmp;
}
@ -119,13 +168,23 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
return keys.isByteOrderComparable();
}
protected void appendToStringBuilder(StringBuilder sb)
@Override
public String toString(boolean ignoreFreezing)
{
sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Arrays.asList(keys, values)));
boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
StringBuilder sb = new StringBuilder();
if (includeFrozenType)
sb.append(FrozenType.class.getName()).append("(");
sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Arrays.asList(keys, values), ignoreFreezing || !isMultiCell));
if (includeFrozenType)
sb.append(")");
return sb.toString();
}
public List<ByteBuffer> serializedValues(List<Cell> cells)
{
assert isMultiCell;
List<ByteBuffer> bbs = new ArrayList<ByteBuffer>(cells.size() * 2);
for (Cell c : cells)
{

View File

@ -23,16 +23,17 @@ import java.util.*;
import org.apache.cassandra.db.Cell;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.SetSerializer;
public class SetType<T> extends CollectionType<Set<T>>
{
// interning instances
private static final Map<AbstractType<?>, SetType> instances = new HashMap<AbstractType<?>, SetType>();
private static final Map<AbstractType<?>, SetType> instances = new HashMap<>();
private static final Map<AbstractType<?>, SetType> frozenInstances = new HashMap<>();
public final AbstractType<T> elements;
private final AbstractType<T> elements;
private final SetSerializer<T> serializer;
private final boolean isMultiCell;
public static SetType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
{
@ -40,25 +41,32 @@ public class SetType<T> extends CollectionType<Set<T>>
if (l.size() != 1)
throw new ConfigurationException("SetType takes exactly 1 type parameter");
return getInstance(l.get(0));
return getInstance(l.get(0), true);
}
public static synchronized <T> SetType<T> getInstance(AbstractType<T> elements)
public static synchronized <T> SetType<T> getInstance(AbstractType<T> elements, boolean isMultiCell)
{
SetType<T> t = instances.get(elements);
Map<AbstractType<?>, SetType> internMap = isMultiCell ? instances : frozenInstances;
SetType<T> t = internMap.get(elements);
if (t == null)
{
t = new SetType<T>(elements);
instances.put(elements, t);
t = new SetType<T>(elements, isMultiCell);
internMap.put(elements, t);
}
return t;
}
public SetType(AbstractType<T> elements)
public SetType(AbstractType<T> elements, boolean isMultiCell)
{
super(Kind.SET);
this.elements = elements;
this.serializer = SetSerializer.getInstance(elements.getSerializer());
this.isMultiCell = isMultiCell;
}
public AbstractType<T> getElementsType()
{
return elements;
}
public AbstractType<T> nameComparator()
@ -71,6 +79,35 @@ public class SetType<T> extends CollectionType<Set<T>>
return EmptyType.instance;
}
@Override
public boolean isMultiCell()
{
return isMultiCell;
}
@Override
public AbstractType<?> freeze()
{
if (isMultiCell)
return getInstance(this.elements, false);
else
return this;
}
@Override
public boolean isCompatibleWithFrozen(CollectionType<?> previous)
{
assert !isMultiCell;
return this.elements.isCompatibleWith(((SetType) previous).elements);
}
@Override
public boolean isValueCompatibleWithFrozen(CollectionType<?> previous)
{
// because sets are ordered, any changes to the type must maintain the ordering
return isCompatibleWithFrozen(previous);
}
@Override
public int compare(ByteBuffer o1, ByteBuffer o2)
{
@ -87,9 +124,19 @@ public class SetType<T> extends CollectionType<Set<T>>
return elements.isByteOrderComparable();
}
protected void appendToStringBuilder(StringBuilder sb)
@Override
public String toString(boolean ignoreFreezing)
{
sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements)));
boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
StringBuilder sb = new StringBuilder();
if (includeFrozenType)
sb.append(FrozenType.class.getName()).append("(");
sb.append(getClass().getName());
sb.append(TypeParser.stringifyTypeParameters(Collections.<AbstractType<?>>singletonList(elements), ignoreFreezing || !isMultiCell));
if (includeFrozenType)
sb.append(")");
return sb.toString();
}
public List<ByteBuffer> serializedValues(List<Cell> cells)

View File

@ -41,12 +41,17 @@ public class TupleType extends AbstractType<ByteBuffer>
public TupleType(List<AbstractType<?>> types)
{
for (int i = 0; i < types.size(); i++)
types.set(i, types.get(i).freeze());
this.types = types;
}
public static TupleType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
{
return new TupleType(parser.getTypeParameters());
List<AbstractType<?>> types = parser.getTypeParameters();
for (int i = 0; i < types.size(); i++)
types.set(i, types.get(i).freeze());
return new TupleType(types);
}
public AbstractType<?> type(int i)
@ -293,6 +298,6 @@ public class TupleType extends AbstractType<ByteBuffer>
@Override
public String toString()
{
return getClass().getName() + TypeParser.stringifyTypeParameters(types);
return getClass().getName() + TypeParser.stringifyTypeParameters(types, true);
}
}

View File

@ -241,7 +241,7 @@ public class TypeParser
public Map<ByteBuffer, CollectionType> getCollectionsParameters() throws SyntaxException, ConfigurationException
{
Map<ByteBuffer, CollectionType> map = new HashMap<ByteBuffer, CollectionType>();
Map<ByteBuffer, CollectionType> map = new HashMap<>();
if (isEOS())
return map;
@ -539,25 +539,37 @@ public class TypeParser
*/
public static String stringifyTypeParameters(List<AbstractType<?>> types)
{
StringBuilder sb = new StringBuilder();
sb.append('(').append(StringUtils.join(types, ",")).append(')');
return sb.toString();
return stringifyTypeParameters(types, false);
}
public static String stringifyCollectionsParameters(Map<ByteBuffer, CollectionType> collections)
/**
* Helper function to ease the writing of AbstractType.toString() methods.
*/
public static String stringifyTypeParameters(List<AbstractType<?>> types, boolean ignoreFreezing)
{
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < types.size(); i++)
{
if (i > 0)
sb.append(",");
sb.append(types.get(i).toString(ignoreFreezing));
}
return sb.append(')').toString();
}
public static String stringifyCollectionsParameters(Map<ByteBuffer, ? extends CollectionType> collections)
{
StringBuilder sb = new StringBuilder();
sb.append('(');
boolean first = true;
for (Map.Entry<ByteBuffer, CollectionType> entry : collections.entrySet())
for (Map.Entry<ByteBuffer, ? extends CollectionType> entry : collections.entrySet())
{
if (!first)
{
sb.append(',');
}
first = false;
sb.append(ByteBufferUtil.bytesToHex(entry.getKey())).append(":");
entry.getValue().appendToStringBuilder(sb);
sb.append(entry.getValue());
}
sb.append(')');
return sb.toString();
@ -572,7 +584,9 @@ public class TypeParser
{
sb.append(',');
sb.append(ByteBufferUtil.bytesToHex(columnNames.get(i))).append(":");
sb.append(columnTypes.get(i).toString());
// omit FrozenType(...) from fields because it is currently implicit
sb.append(columnTypes.get(i).toString(true));
}
sb.append(')');
return sb.toString();

View File

@ -61,7 +61,7 @@ public class UserType extends TupleType
for (Pair<ByteBuffer, AbstractType> p : params.right)
{
columnNames.add(p.left);
columnTypes.add(p.right);
columnTypes.add(p.right.freeze());
}
return new UserType(keyspace, name, columnNames, columnTypes);
}

View File

@ -148,9 +148,9 @@ public class CqlStorage extends AbstractCassandraStorage
}
AbstractType elementValidator;
if (validator instanceof SetType)
elementValidator = ((SetType<?>) validator).elements;
elementValidator = ((SetType<?>) validator).getElementsType();
else if (validator instanceof ListType)
elementValidator = ((ListType<?>) validator).elements;
elementValidator = ((ListType<?>) validator).getElementsType();
else
return;
@ -167,8 +167,8 @@ public class CqlStorage extends AbstractCassandraStorage
/** set the values of set/list at and after the position of the tuple */
private void setMapTupleValues(Tuple tuple, int position, Object value, AbstractType<?> validator) throws ExecException
{
AbstractType<?> keyValidator = ((MapType<?, ?>) validator).keys;
AbstractType<?> valueValidator = ((MapType<?, ?>) validator).values;
AbstractType<?> keyValidator = ((MapType<?, ?>) validator).getKeysType();
AbstractType<?> valueValidator = ((MapType<?, ?>) validator).getValuesType();
int i = 0;
Tuple innerTuple = TupleFactory.getInstance().newTuple(((Map<?,?>) value).size());

View File

@ -19,8 +19,10 @@
package org.apache.cassandra.serializers;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.List;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
public abstract class CollectionSerializer<T> implements TypeSerializer<T>
@ -35,17 +37,17 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
{
List<ByteBuffer> values = serializeValues(value);
// See deserialize() for why using the protocol v3 variant is the right thing to do.
return pack(values, getElementCount(value), 3);
return pack(values, getElementCount(value), Server.VERSION_3);
}
public T deserialize(ByteBuffer bytes)
{
// The only cases we serialize/deserialize collections internally (i.e. not for the protocol sake),
// is:
// 1) when collections are in UDT values
// 1) when collections are frozen
// 2) for internal calls.
// In both case, using the protocol 3 version variant is the right thing to do.
return deserializeForNativeProtocol(bytes, 3);
return deserializeForNativeProtocol(bytes, Server.VERSION_3);
}
public ByteBuffer reserializeToV3(ByteBuffer bytes)
@ -55,11 +57,11 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
public void validate(ByteBuffer bytes) throws MarshalException
{
// Same thing than above
validateForNativeProtocol(bytes, 3);
// Same thing as above
validateForNativeProtocol(bytes, Server.VERSION_3);
}
public static ByteBuffer pack(List<ByteBuffer> buffers, int elements, int version)
public static ByteBuffer pack(Collection<ByteBuffer> buffers, int elements, int version)
{
int size = 0;
for (ByteBuffer bb : buffers)
@ -74,7 +76,7 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
protected static void writeCollectionSize(ByteBuffer output, int elements, int version)
{
if (version >= 3)
if (version >= Server.VERSION_3)
output.putInt(elements);
else
output.putShort((short)elements);
@ -82,12 +84,12 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
public static int readCollectionSize(ByteBuffer input, int version)
{
return version >= 3 ? input.getInt() : ByteBufferUtil.readShortLength(input);
return version >= Server.VERSION_3 ? input.getInt() : ByteBufferUtil.readShortLength(input);
}
protected static int sizeOfCollectionSize(int elements, int version)
{
return version >= 3 ? 4 : 2;
return version >= Server.VERSION_3 ? 4 : 2;
}
protected static void writeValue(ByteBuffer output, ByteBuffer value, int version)
@ -113,7 +115,7 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
public static ByteBuffer readValue(ByteBuffer input, int version)
{
if (version >= 3)
if (version >= Server.VERSION_3)
{
int size = input.getInt();
if (size < 0)
@ -129,7 +131,7 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
protected static int sizeOfValue(ByteBuffer value, int version)
{
if (version >= 3)
if (version >= Server.VERSION_3)
{
return value == null ? 4 : 4 + value.remaining();
}

View File

@ -18,12 +18,12 @@
package org.apache.cassandra.serializers;
import org.apache.cassandra.transport.Server;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.utils.ByteBufferUtil;
public class ListSerializer<T> extends CollectionSerializer<List<T>>
{
// interning instances
@ -111,6 +111,34 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
}
}
/**
* Returns the element at the given index in a list.
* @param serializedList a serialized list
* @param index the index to get
* @return the serialized element at the given index, or null if the index exceeds the list size
*/
public ByteBuffer getElement(ByteBuffer serializedList, int index)
{
try
{
ByteBuffer input = serializedList.duplicate();
int n = readCollectionSize(input, Server.VERSION_3);
if (n <= index)
return null;
for (int i = 0; i < index; i++)
{
int length = input.getInt();
input.position(input.position() + length);
}
return readValue(input, Server.VERSION_3);
}
catch (BufferUnderflowException e)
{
throw new MarshalException("Not enough bytes to read a list");
}
}
public String toString(List<T> value)
{
StringBuilder sb = new StringBuilder();
@ -118,13 +146,9 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
for (T element : value)
{
if (isFirst)
{
isFirst = false;
}
else
{
sb.append("; ");
}
sb.append(elements.toString(element));
}
return sb.toString();

View File

@ -22,6 +22,8 @@ import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
@ -114,6 +116,38 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
}
}
/**
* Given a serialized map, gets the value associated with a given key.
* @param serializedMap a serialized map
* @param serializedKey a serialized key
* @param keyType the key type for the map
* @return the value associated with the key if one exists, null otherwise
*/
public ByteBuffer getSerializedValue(ByteBuffer serializedMap, ByteBuffer serializedKey, AbstractType keyType)
{
try
{
ByteBuffer input = serializedMap.duplicate();
int n = readCollectionSize(input, Server.VERSION_3);
for (int i = 0; i < n; i++)
{
ByteBuffer kbb = readValue(input, Server.VERSION_3);
ByteBuffer vbb = readValue(input, Server.VERSION_3);
int comparison = keyType.compare(kbb, serializedKey);
if (comparison == 0)
return vbb;
else if (comparison > 0)
// since the map is in sorted order, we know we've gone too far and the element doesn't exist
return null;
}
return null;
}
catch (BufferUnderflowException e)
{
throw new MarshalException("Not enough bytes to read a map");
}
}
public String toString(Map<K, V> value)
{
StringBuilder sb = new StringBuilder();
@ -121,13 +155,9 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
for (Map.Entry<K, V> element : value.entrySet())
{
if (isFirst)
{
isFirst = false;
}
else
{
sb.append("; ");
}
sb.append('(');
sb.append(keys.toString(element.getKey()));
sb.append(", ");

View File

@ -214,18 +214,18 @@ public enum DataType implements OptionCodec.Codecable<DataType>
{
if (type instanceof ListType)
{
return Pair.<DataType, Object>create(LIST, ((ListType)type).elements);
return Pair.<DataType, Object>create(LIST, ((ListType)type).getElementsType());
}
else if (type instanceof MapType)
{
MapType mt = (MapType)type;
return Pair.<DataType, Object>create(MAP, Arrays.asList(mt.keys, mt.values));
return Pair.<DataType, Object>create(MAP, Arrays.asList(mt.getKeysType(), mt.getValuesType()));
}
else
else if (type instanceof SetType)
{
assert type instanceof SetType;
return Pair.<DataType, Object>create(SET, ((SetType)type).elements);
return Pair.<DataType, Object>create(SET, ((SetType)type).getElementsType());
}
throw new AssertionError();
}
if (type instanceof UserType && version >= 3)
@ -251,12 +251,12 @@ public enum DataType implements OptionCodec.Codecable<DataType>
case CUSTOM:
return TypeParser.parse((String)entry.right);
case LIST:
return ListType.getInstance((AbstractType)entry.right);
return ListType.getInstance((AbstractType)entry.right, true);
case SET:
return SetType.getInstance((AbstractType)entry.right);
return SetType.getInstance((AbstractType)entry.right, true);
case MAP:
List<AbstractType> l = (List<AbstractType>)entry.right;
return MapType.getInstance(l.get(0), l.get(1));
return MapType.getInstance(l.get(0), l.get(1), true);
case UDT:
return (AbstractType)entry.right;
case TUPLE:

View File

@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.*;
@ -45,7 +44,6 @@ import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* Base class for CQL tests.
@ -96,10 +94,10 @@ public abstract class CQLTester
{
try
{
schemaChange(String.format("DROP TABLE %s.%s", KEYSPACE, tableToDrop));
schemaChange(String.format("DROP TABLE IF EXISTS %s.%s", KEYSPACE, tableToDrop));
for (String typeName : typesToDrop)
schemaChange(String.format("DROP TYPE %s.%s", KEYSPACE, typeName));
schemaChange(String.format("DROP TYPE IF EXISTS %s.%s", KEYSPACE, typeName));
// Dropping doesn't delete the sstables. It's not a huge deal but it's cleaner to cleanup after us
// Thas said, we shouldn't delete blindly before the SSTableDeletingTask for the table we drop
@ -186,6 +184,21 @@ public abstract class CQLTester
schemaChange(fullQuery);
}
protected void createTableMayThrow(String query) throws Throwable
{
currentTable = "table_" + seqNumber.getAndIncrement();
String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
logger.info(fullQuery);
try
{
QueryProcessor.executeOnceInternal(fullQuery);
}
catch (RuntimeException ex)
{
throw ex.getCause();
}
}
protected void alterTable(String query)
{
String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
@ -193,6 +206,20 @@ public abstract class CQLTester
schemaChange(fullQuery);
}
protected void alterTableMayThrow(String query) throws Throwable
{
String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
logger.info(fullQuery);
try
{
QueryProcessor.executeOnceInternal(fullQuery);
}
catch (RuntimeException ex)
{
throw ex.getCause();
}
}
protected void createIndex(String query)
{
String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
@ -200,6 +227,20 @@ public abstract class CQLTester
schemaChange(fullQuery);
}
protected void createIndexMayThrow(String query) throws Throwable
{
String fullQuery = String.format(query, KEYSPACE + "." + currentTable);
logger.info(fullQuery);
try
{
QueryProcessor.executeOnceInternal(fullQuery);
}
catch (RuntimeException ex)
{
throw ex.getCause();
}
}
private static void schemaChange(String query)
{
try
@ -262,7 +303,7 @@ public abstract class CQLTester
int i = 0;
while (iter.hasNext() && i < rows.length)
{
Object[] expected = rows[i++];
Object[] expected = rows[i];
UntypedResultSet.Row actual = iter.next();
Assert.assertEquals(String.format("Invalid number of (expected) values provided for row %d", i), meta.size(), expected.length);
@ -278,6 +319,7 @@ public abstract class CQLTester
Assert.fail(String.format("Invalid value for row %d column %d (%s of type %s), expected <%s> but got <%s>",
i, j, column.name, column.type.asCQL3Type(), formatValue(expectedByteValue, column.type), formatValue(actualValue, column.type)));
}
i++;
}
if (iter.hasNext())
@ -310,6 +352,11 @@ public abstract class CQLTester
}
protected void assertInvalid(String query, Object... values) throws Throwable
{
assertInvalidMessage(null, query, values);
}
protected void assertInvalidMessage(String errorMessage, String query, Object... values) throws Throwable
{
try
{
@ -321,7 +368,11 @@ public abstract class CQLTester
}
catch (InvalidRequestException e)
{
// This is what we expect
if (errorMessage != null)
{
Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + e.getMessage() + "'",
e.getMessage().contains(errorMessage));
}
}
}
@ -401,7 +452,15 @@ public abstract class CQLTester
continue;
}
buffers[i] = typeFor(value).decompose(serializeTuples(value));
try
{
buffers[i] = typeFor(value).decompose(serializeTuples(value));
}
catch (Exception ex)
{
logger.info("Error serializing query parameter {}:", value, ex);
throw ex;
}
}
return buffers;
}
@ -613,14 +672,14 @@ public abstract class CQLTester
{
List l = (List)value;
AbstractType elt = l.isEmpty() ? BytesType.instance : typeFor(l.get(0));
return ListType.getInstance(elt);
return ListType.getInstance(elt, true);
}
if (value instanceof Set)
{
Set s = (Set)value;
AbstractType elt = s.isEmpty() ? BytesType.instance : typeFor(s.iterator().next());
return SetType.getInstance(elt);
return SetType.getInstance(elt, true);
}
if (value instanceof Map)
@ -638,7 +697,7 @@ public abstract class CQLTester
keys = typeFor(entry.getKey());
values = typeFor(entry.getValue());
}
return MapType.getInstance(keys, values);
return MapType.getInstance(keys, values, true);
}
throw new IllegalArgumentException("Unsupported value type (value is " + value + ")");
@ -674,5 +733,10 @@ public abstract class CQLTester
sb.append(")");
return sb.toString();
}
public String toString()
{
return "TupleValue" + toCQLString();
}
}
}

View File

@ -154,9 +154,9 @@ public class ColumnConditionTest
{
CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c list<int>)", "ks");
Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
typeMap.put(ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance));
typeMap.put(ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance, true));
CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap));
ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), ListType.getInstance(Int32Type.instance, true), 0, ColumnDefinition.Kind.REGULAR);
List<Cell> cells = new ArrayList<>(columnValues.size());
if (columnValues != null)
@ -169,14 +169,14 @@ public class ColumnConditionTest
};
}
return bound.listAppliesTo(ListType.getInstance(Int32Type.instance), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
return bound.listAppliesTo(ListType.getInstance(Int32Type.instance, true), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
}
@Test
// sets use the same check as lists
public void testListCollectionBoundAppliesTo() throws InvalidRequestException
{
ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), ListType.getInstance(Int32Type.instance), null, null, null, null, null);
ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), ListType.getInstance(Int32Type.instance, true), null, null, null, null, null);
// EQ
ColumnCondition condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.EQ);
@ -275,9 +275,9 @@ public class ColumnConditionTest
assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
}
private static Set<ByteBuffer> set(ByteBuffer... values)
private static SortedSet<ByteBuffer> set(ByteBuffer... values)
{
Set results = new HashSet<ByteBuffer>(values.length);
SortedSet<ByteBuffer> results = new TreeSet<>(Int32Type.instance);
results.addAll(Arrays.asList(values));
return results;
}
@ -286,9 +286,9 @@ public class ColumnConditionTest
{
CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c set<int>)", "ks");
Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
typeMap.put(ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance));
typeMap.put(ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance, true));
CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap));
ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("c"), SetType.getInstance(Int32Type.instance, true), 0, ColumnDefinition.Kind.REGULAR);
List<Cell> cells = new ArrayList<>(columnValues.size());
if (columnValues != null)
@ -300,13 +300,13 @@ public class ColumnConditionTest
};
}
return bound.setAppliesTo(SetType.getInstance(Int32Type.instance), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
return bound.setAppliesTo(SetType.getInstance(Int32Type.instance, true), cells == null ? null : cells.iterator(), conditionValues, bound.operator);
}
@Test
public void testSetCollectionBoundAppliesTo() throws InvalidRequestException
{
ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), SetType.getInstance(Int32Type.instance), null, null, null, null, null);
ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("c", true), SetType.getInstance(Int32Type.instance, true), null, null, null, null, null);
// EQ
ColumnCondition condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Operator.EQ);
@ -419,9 +419,9 @@ public class ColumnConditionTest
{
CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b map<int, int>)", "ks");
Map<ByteBuffer, CollectionType> typeMap = new HashMap<>();
typeMap.put(ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance));
typeMap.put(ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
CompoundSparseCellNameType.WithCollection nameType = new CompoundSparseCellNameType.WithCollection(Collections.EMPTY_LIST, ColumnToCollectionType.getInstance(typeMap));
ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance), 0, ColumnDefinition.Kind.REGULAR);
ColumnDefinition definition = new ColumnDefinition(cfm, ByteBufferUtil.bytes("b"), MapType.getInstance(Int32Type.instance, Int32Type.instance, true), 0, ColumnDefinition.Kind.REGULAR);
List<Cell> cells = new ArrayList<>(columnValues.size());
if (columnValues != null)
@ -430,13 +430,13 @@ public class ColumnConditionTest
cells.add(new BufferCell(nameType.create(Composites.EMPTY, definition, entry.getKey()), entry.getValue()));
}
return bound.mapAppliesTo(MapType.getInstance(Int32Type.instance, Int32Type.instance), cells.iterator(), conditionValues, bound.operator);
return bound.mapAppliesTo(MapType.getInstance(Int32Type.instance, Int32Type.instance, true), cells.iterator(), conditionValues, bound.operator);
}
@Test
public void testMapCollectionBoundIsSatisfiedByValue() throws InvalidRequestException
{
ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("b", true), MapType.getInstance(Int32Type.instance, Int32Type.instance), null, null, null, null, null);
ColumnDefinition definition = new ColumnDefinition("ks", "cf", new ColumnIdentifier("b", true), MapType.getInstance(Int32Type.instance, Int32Type.instance, true), null, null, null, null, null);
Map<ByteBuffer, ByteBuffer> placeholderMap = new TreeMap<>();
placeholderMap.put(ONE, ONE);

View File

@ -0,0 +1,791 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class FrozenCollectionsTest extends CQLTester
{
@Test
public void testPartitionKeyUsage() throws Throwable
{
createTable("CREATE TABLE %s (k frozen<set<int>> PRIMARY KEY, v int)");
execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(), 1);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(1, 2, 3), 1);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(4, 5, 6), 0);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", set(7, 8, 9), 0);
// overwrite with an update
execute("UPDATE %s SET v=? WHERE k=?", 0, set());
execute("UPDATE %s SET v=? WHERE k=?", 0, set(1, 2, 3));
assertRows(execute("SELECT * FROM %s"),
row(set(), 0),
row(set(1, 2, 3), 0),
row(set(4, 5, 6), 0),
row(set(7, 8, 9), 0)
);
assertRows(execute("SELECT k FROM %s"),
row(set()),
row(set(1, 2, 3)),
row(set(4, 5, 6)),
row(set(7, 8, 9))
);
assertRows(execute("SELECT * FROM %s LIMIT 2"),
row(set(), 0),
row(set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k=?", set(4, 5, 6)),
row(set(4, 5, 6), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k=?", set()),
row(set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k IN ?", list(set(4, 5, 6), set())),
row(set(4, 5, 6), 0),
row(set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE token(k) >= token(?)", set(4, 5, 6)),
row(set(4, 5, 6), 0),
row(set(7, 8, 9), 0)
);
assertInvalid("INSERT INTO %s (k, v) VALUES (null, 0)");
execute("DELETE FROM %s WHERE k=?", set());
execute("DELETE FROM %s WHERE k=?", set(4, 5, 6));
assertRows(execute("SELECT * FROM %s"),
row(set(1, 2, 3), 0),
row(set(7, 8, 9), 0)
);
}
@Test
public void testNestedPartitionKeyUsage() throws Throwable
{
createTable("CREATE TABLE %s (k frozen<map<set<int>, list<int>>> PRIMARY KEY, v int)");
execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(), 1);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(), list(1, 2, 3)), 0);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(1, 2, 3), list(1, 2, 3)), 1);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(4, 5, 6), list(1, 2, 3)), 0);
execute("INSERT INTO %s (k, v) VALUES (?, ?)", map(set(7, 8, 9), list(1, 2, 3)), 0);
// overwrite with an update
execute("UPDATE %s SET v=? WHERE k=?", 0, map());
execute("UPDATE %s SET v=? WHERE k=?", 0, map(set(1, 2, 3), list(1, 2, 3)));
assertRows(execute("SELECT * FROM %s"),
row(map(), 0),
row(map(set(), list(1, 2, 3)), 0),
row(map(set(1, 2, 3), list(1, 2, 3)), 0),
row(map(set(4, 5, 6), list(1, 2, 3)), 0),
row(map(set(7, 8, 9), list(1, 2, 3)), 0)
);
assertRows(execute("SELECT k FROM %s"),
row(map()),
row(map(set(), list(1, 2, 3))),
row(map(set(1, 2, 3), list(1, 2, 3))),
row(map(set(4, 5, 6), list(1, 2, 3))),
row(map(set(7, 8, 9), list(1, 2, 3)))
);
assertRows(execute("SELECT * FROM %s LIMIT 3"),
row(map(), 0),
row(map(set(), list(1, 2, 3)), 0),
row(map(set(1, 2, 3), list(1, 2, 3)), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k=?", map(set(4, 5, 6), list(1, 2, 3))),
row(map(set(4, 5, 6), list(1, 2, 3)), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k=?", map()),
row(map(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k=?", map(set(), list(1, 2, 3))),
row(map(set(), list(1, 2, 3)), 0)
);
assertRows(execute("SELECT * FROM %s WHERE k IN ?", list(map(set(4, 5, 6), list(1, 2, 3)), map(), map(set(), list(1, 2, 3)))),
row(map(set(4, 5, 6), list(1, 2, 3)), 0),
row(map(), 0),
row(map(set(), list(1, 2, 3)), 0)
);
assertRows(execute("SELECT * FROM %s WHERE token(k) >= token(?)", map(set(4, 5, 6), list(1, 2, 3))),
row(map(set(4, 5, 6), list(1, 2, 3)), 0),
row(map(set(7, 8, 9), list(1, 2, 3)), 0)
);
execute("DELETE FROM %s WHERE k=?", map());
execute("DELETE FROM %s WHERE k=?", map(set(), list(1, 2, 3)));
execute("DELETE FROM %s WHERE k=?", map(set(4, 5, 6), list(1, 2, 3)));
assertRows(execute("SELECT * FROM %s"),
row(map(set(1, 2, 3), list(1, 2, 3)), 0),
row(map(set(7, 8, 9), list(1, 2, 3)), 0)
);
}
@Test
public void testClusteringKeyUsage() throws Throwable
{
for (String option : Arrays.asList("", " WITH COMPACT STORAGE"))
{
createTable("CREATE TABLE %s (a int, b frozen<set<int>>, c int, PRIMARY KEY (a, b))" + option);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(), 1);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(1, 2, 3), 1);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(4, 5, 6), 0);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, set(7, 8, 9), 0);
// overwrite with an update
execute("UPDATE %s SET c=? WHERE a=? AND b=?", 0, 0, set());
execute("UPDATE %s SET c=? WHERE a=? AND b=?", 0, 0, set(1, 2, 3));
assertRows(execute("SELECT * FROM %s"),
row(0, set(), 0),
row(0, set(1, 2, 3), 0),
row(0, set(4, 5, 6), 0),
row(0, set(7, 8, 9), 0)
);
assertRows(execute("SELECT b FROM %s"),
row(set()),
row(set(1, 2, 3)),
row(set(4, 5, 6)),
row(set(7, 8, 9))
);
assertRows(execute("SELECT * FROM %s LIMIT 2"),
row(0, set(), 0),
row(0, set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, set(4, 5, 6)),
row(0, set(4, 5, 6), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, set()),
row(0, set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b IN ?", 0, list(set(4, 5, 6), set())),
row(0, set(), 0),
row(0, set(4, 5, 6), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ?", 0, set(4, 5, 6)),
row(0, set(7, 8, 9), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b >= ?", 0, set(4, 5, 6)),
row(0, set(4, 5, 6), 0),
row(0, set(7, 8, 9), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b < ?", 0, set(4, 5, 6)),
row(0, set(), 0),
row(0, set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b <= ?", 0, set(4, 5, 6)),
row(0, set(), 0),
row(0, set(1, 2, 3), 0),
row(0, set(4, 5, 6), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ? AND b <= ?", 0, set(1, 2, 3), set(4, 5, 6)),
row(0, set(4, 5, 6), 0)
);
execute("DELETE FROM %s WHERE a=? AND b=?", 0, set());
execute("DELETE FROM %s WHERE a=? AND b=?", 0, set(4, 5, 6));
assertRows(execute("SELECT * FROM %s"),
row(0, set(1, 2, 3), 0),
row(0, set(7, 8, 9), 0)
);
}
}
@Test
public void testNestedClusteringKeyUsage() throws Throwable
{
for (String option : Arrays.asList("", " WITH COMPACT STORAGE"))
{
createTable("CREATE TABLE %s (a int, b frozen<map<set<int>, list<int>>>, c frozen<set<int>>, d int, PRIMARY KEY (a, b, c))" + option);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(), set(), 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(), list(1, 2, 3)), set(), 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0);
assertRows(execute("SELECT * FROM %s"),
row(0, map(), set(), 0),
row(0, map(set(), list(1, 2, 3)), set(), 0),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT b FROM %s"),
row(map()),
row(map(set(), list(1, 2, 3))),
row(map(set(1, 2, 3), list(1, 2, 3))),
row(map(set(4, 5, 6), list(1, 2, 3))),
row(map(set(7, 8, 9), list(1, 2, 3)))
);
assertRows(execute("SELECT c FROM %s"),
row(set()),
row(set()),
row(set(1, 2, 3)),
row(set(1, 2, 3)),
row(set(1, 2, 3))
);
assertRows(execute("SELECT * FROM %s LIMIT 3"),
row(0, map(), set(), 0),
row(0, map(set(), list(1, 2, 3)), set(), 0),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=0 ORDER BY b DESC LIMIT 4"),
row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(), list(1, 2, 3)), set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, map()),
row(0, map(), set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, map(set(), list(1, 2, 3))),
row(0, map(set(), list(1, 2, 3)), set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, map(set(1, 2, 3), list(1, 2, 3))),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(), list(1, 2, 3)), set()),
row(0, map(set(), list(1, 2, 3)), set(), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND (b, c) IN ?", 0, list(tuple(map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3)),
tuple(map(), set()))),
row(0, map(), set(), 0),
row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b >= ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b < ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
row(0, map(), set(), 0),
row(0, map(set(), list(1, 2, 3)), set(), 0),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b <= ?", 0, map(set(4, 5, 6), list(1, 2, 3))),
row(0, map(), set(), 0),
row(0, map(set(), list(1, 2, 3)), set(), 0),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b > ? AND b <= ?", 0, map(set(1, 2, 3), list(1, 2, 3)), map(set(4, 5, 6), list(1, 2, 3))),
row(0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3), 0)
);
execute("DELETE FROM %s WHERE a=? AND b=? AND c=?", 0, map(), set());
assertEmpty(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(), set()));
execute("DELETE FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(), list(1, 2, 3)), set());
assertEmpty(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(), list(1, 2, 3)), set()));
execute("DELETE FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3));
assertEmpty(execute("SELECT * FROM %s WHERE a=? AND b=? AND c=?", 0, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3)));
assertRows(execute("SELECT * FROM %s"),
row(0, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3), 0),
row(0, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3), 0)
);
}
}
@Test
public void testNormalColumnUsage() throws Throwable
{
for (String option : Arrays.asList("", " WITH COMPACT STORAGE"))
{
createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<map<set<int>, list<int>>>, c frozen<set<int>>)" + option);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, map(), set());
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, map(set(), list(99999, 999999, 99999)), set());
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3));
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 3, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3));
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 4, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3));
// overwrite with update
execute ("UPDATE %s SET b=? WHERE a=?", map(set(), list(1, 2, 3)), 1);
assertRows(execute("SELECT * FROM %s"),
row(0, map(), set()),
row(1, map(set(), list(1, 2, 3)), set()),
row(2, map(set(1, 2, 3), list(1, 2, 3)), set(1, 2, 3)),
row(3, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3)),
row(4, map(set(7, 8, 9), list(1, 2, 3)), set(1, 2, 3))
);
assertRows(execute("SELECT b FROM %s"),
row(map()),
row(map(set(), list(1, 2, 3))),
row(map(set(1, 2, 3), list(1, 2, 3))),
row(map(set(4, 5, 6), list(1, 2, 3))),
row(map(set(7, 8, 9), list(1, 2, 3)))
);
assertRows(execute("SELECT c FROM %s"),
row(set()),
row(set()),
row(set(1, 2, 3)),
row(set(1, 2, 3)),
row(set(1, 2, 3))
);
assertRows(execute("SELECT * FROM %s WHERE a=?", 3),
row(3, map(set(4, 5, 6), list(1, 2, 3)), set(1, 2, 3))
);
execute("UPDATE %s SET b=? WHERE a=?", null, 1);
assertRows(execute("SELECT * FROM %s WHERE a=?", 1),
row(1, null, set())
);
execute("UPDATE %s SET b=? WHERE a=?", map(), 1);
assertRows(execute("SELECT * FROM %s WHERE a=?", 1),
row(1, map(), set())
);
execute("UPDATE %s SET c=? WHERE a=?", null, 2);
assertRows(execute("SELECT * FROM %s WHERE a=?", 2),
row(2, map(set(1, 2, 3), list(1, 2, 3)), null)
);
execute("UPDATE %s SET c=? WHERE a=?", set(), 2);
assertRows(execute("SELECT * FROM %s WHERE a=?", 2),
row(2, map(set(1, 2, 3), list(1, 2, 3)), set())
);
execute("DELETE b FROM %s WHERE a=?", 3);
assertRows(execute("SELECT * FROM %s WHERE a=?", 3),
row(3, null, set(1, 2, 3))
);
execute("DELETE c FROM %s WHERE a=?", 4);
assertRows(execute("SELECT * FROM %s WHERE a=?", 4),
row(4, map(set(7, 8, 9), list(1, 2, 3)), null)
);
}
}
@Test
public void testStaticColumnUsage() throws Throwable
{
createTable("CREATE TABLE %s (a int, b int, c frozen<map<set<int>, list<int>>> static, d int, PRIMARY KEY (a, b))");
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, map(), 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, map(), 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, map(set(), list(1, 2, 3)), 0);
execute("INSERT INTO %s (a, b, d) VALUES (?, ?, ?)", 1, 1, 0);
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0);
assertRows(execute("SELECT * FROM %s"),
row(0, 0, map(), 0),
row(0, 1, map(), 0),
row(1, 0, map(set(), list(1, 2, 3)), 0),
row(1, 1, map(set(), list(1, 2, 3)), 0),
row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
);
assertRows(execute("SELECT * FROM %s WHERE a=? AND b=?", 0, 1),
row(0, 1, map(), 0)
);
execute("DELETE c FROM %s WHERE a=?", 0);
assertRows(execute("SELECT * FROM %s"),
row(0, 0, null, 0),
row(0, 1, null, 0),
row(1, 0, map(set(), list(1, 2, 3)), 0),
row(1, 1, map(set(), list(1, 2, 3)), 0),
row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
);
execute("DELETE FROM %s WHERE a=?", 0);
assertRows(execute("SELECT * FROM %s"),
row(1, 0, map(set(), list(1, 2, 3)), 0),
row(1, 1, map(set(), list(1, 2, 3)), 0),
row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
);
execute("UPDATE %s SET c=? WHERE a=?", map(set(1, 2, 3), list(1, 2, 3)), 1);
assertRows(execute("SELECT * FROM %s"),
row(1, 0, map(set(1, 2, 3), list(1, 2, 3)), 0),
row(1, 1, map(set(1, 2, 3), list(1, 2, 3)), 0),
row(2, 0, map(set(1, 2, 3), list(1, 2, 3)), 0)
);
}
private void assertInvalidCreateWithMessage(String createTableStatement, String errorMessage) throws Throwable
{
try
{
createTableMayThrow(createTableStatement);
Assert.fail("Expected CREATE TABLE statement to error: " + createTableStatement);
}
catch (InvalidRequestException | ConfigurationException | SyntaxException ex)
{
Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + ex.getMessage() + "'",
ex.getMessage().contains(errorMessage));
}
}
private void assertInvalidAlterWithMessage(String createTableStatement, String errorMessage) throws Throwable
{
try
{
alterTableMayThrow(createTableStatement);
Assert.fail("Expected CREATE TABLE statement to error: " + createTableStatement);
}
catch (InvalidRequestException | ConfigurationException ex)
{
Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + ex.getMessage() + "'",
ex.getMessage().contains(errorMessage));
}
}
@Test
public void testInvalidOperations() throws Throwable
{
// lists
createTable("CREATE TABLE %s (k int PRIMARY KEY, l frozen<list<int>>)");
assertInvalid("UPDATE %s SET l[?]=? WHERE k=?", 0, 0, 0);
assertInvalid("UPDATE %s SET l = ? + l WHERE k=?", list(0), 0);
assertInvalid("UPDATE %s SET l = l + ? WHERE k=?", list(4), 0);
assertInvalid("UPDATE %s SET l = l - ? WHERE k=?", list(3), 0);
assertInvalid("DELETE l[?] FROM %s WHERE k=?", 0, 0);
// sets
createTable("CREATE TABLE %s (k int PRIMARY KEY, s frozen<set<int>>)");
assertInvalid("UPDATE %s SET s = s + ? WHERE k=?", set(0), 0);
assertInvalid("UPDATE %s SET s = s - ? WHERE k=?", set(3), 0);
// maps
createTable("CREATE TABLE %s (k int PRIMARY KEY, m frozen<map<int, int>>)");
assertInvalid("UPDATE %s SET m[?]=? WHERE k=?", 0, 0, 0);
assertInvalid("UPDATE %s SET m = m + ? WHERE k=?", map(4, 4), 0);
assertInvalid("DELETE m[?] FROM %s WHERE k=?", 0, 0);
assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t set<set<int>>)",
"Non-frozen collections are not allowed inside collections");
assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t frozen<set<counter>>)",
"Counters are not allowed inside collections");
assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t frozen<text>)",
"frozen<> is only allowed on collections, tuples, and user-defined types");
}
@Test
public void testAltering() throws Throwable
{
createTable("CREATE TABLE %s (a int, b frozen<list<int>>, c frozen<list<int>>, PRIMARY KEY (a, b))");
alterTable("ALTER TABLE %s ALTER c TYPE frozen<list<blob>>");
assertInvalidAlterWithMessage("ALTER TABLE %s ALTER b TYPE frozen<list<blob>>",
"types are not order-compatible");
assertInvalidAlterWithMessage("ALTER TABLE %s ALTER b TYPE list<int>",
"types are not order-compatible");
assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE list<blob>",
"types are incompatible");
alterTable("ALTER TABLE %s DROP c");
alterTable("ALTER TABLE %s ADD c frozen<set<int>>");
assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE frozen<set<blob>>",
"types are incompatible");
alterTable("ALTER TABLE %s DROP c");
alterTable("ALTER TABLE %s ADD c frozen<map<int, int>>");
assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE frozen<map<blob, int>>",
"types are incompatible");
alterTable("ALTER TABLE %s ALTER c TYPE frozen<map<int, blob>>");
}
private void assertInvalidIndexCreationWithMessage(String statement, String errorMessage) throws Throwable
{
try
{
createIndexMayThrow(statement);
Assert.fail("Expected index creation to fail: " + statement);
}
catch (InvalidRequestException ex)
{
Assert.assertTrue("Expected error message to contain '" + errorMessage + "', but got '" + ex.getMessage() + "'",
ex.getMessage().contains(errorMessage));
}
}
@Test
public void testSecondaryIndex() throws Throwable
{
createTable("CREATE TABLE %s (a frozen<map<int, text>> PRIMARY KEY, b frozen<map<int, text>>)");
// for now, we don't support indexing values or keys of collections in the primary key
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (full(a))", "Cannot create secondary index on partition key column");
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(a))", "Cannot create index on keys of frozen<map> column");
assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(b))", "Cannot create index on keys of frozen<map> column");
createTable("CREATE TABLE %s (a int, b frozen<list<int>>, c frozen<set<int>>, d frozen<map<int, text>>, PRIMARY KEY (a, b))");
createIndex("CREATE INDEX ON %s (full(b))");
createIndex("CREATE INDEX ON %s (full(c))");
createIndex("CREATE INDEX ON %s (full(d))");
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, list(1, 2, 3), set(1, 2, 3), map(1, "a"));
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, list(4, 5, 6), set(1, 2, 3), map(1, "a"));
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, list(1, 2, 3), set(4, 5, 6), map(2, "b"));
execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, list(4, 5, 6), set(4, 5, 6), map(2, "b"));
// CONTAINS KEY doesn't work on non-maps
assertInvalidMessage("Cannot use CONTAINS KEY on non-map column",
"SELECT * FROM %s WHERE b CONTAINS KEY ?", 1);
assertInvalidMessage("Cannot use CONTAINS KEY on non-map column",
"SELECT * FROM %s WHERE b CONTAINS KEY ? ALLOW FILTERING", 1);
assertInvalidMessage("Cannot use CONTAINS KEY on non-map column",
"SELECT * FROM %s WHERE c CONTAINS KEY ?", 1);
// normal indexes on frozen collections don't support CONTAINS or CONTAINS KEY
assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
"SELECT * FROM %s WHERE b CONTAINS ?", 1);
assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
"SELECT * FROM %s WHERE b CONTAINS ? ALLOW FILTERING", 1);
assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
"SELECT * FROM %s WHERE d CONTAINS KEY ?", 1);
assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
"SELECT * FROM %s WHERE d CONTAINS KEY ? ALLOW FILTERING", 1);
assertInvalidMessage("No secondary indexes on the restricted columns support the provided operator",
"SELECT * FROM %s WHERE b CONTAINS ? AND d CONTAINS KEY ? ALLOW FILTERING", 1, 1);
// index lookup on b
assertRows(execute("SELECT * FROM %s WHERE b=?", list(1, 2, 3)),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b"))
);
assertEmpty(execute("SELECT * FROM %s WHERE b=?", list(-1)));
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND c=?", list(1, 2, 3), set(4, 5, 6));
assertRows(execute("SELECT * FROM %s WHERE b=? AND c=? ALLOW FILTERING", list(1, 2, 3), set(4, 5, 6)),
row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND c CONTAINS ?", list(1, 2, 3), 5);
assertRows(execute("SELECT * FROM %s WHERE b=? AND c CONTAINS ? ALLOW FILTERING", list(1, 2, 3), 5),
row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND d=?", list(1, 2, 3), map(1, "a"));
assertRows(execute("SELECT * FROM %s WHERE b=? AND d=? ALLOW FILTERING", list(1, 2, 3), map(1, "a")),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND d CONTAINS ?", list(1, 2, 3), "a");
assertRows(execute("SELECT * FROM %s WHERE b=? AND d CONTAINS ? ALLOW FILTERING", list(1, 2, 3), "a"),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE b=? AND d CONTAINS KEY ?", list(1, 2, 3), 1);
assertRows(execute("SELECT * FROM %s WHERE b=? AND d CONTAINS KEY ? ALLOW FILTERING", list(1, 2, 3), 1),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
// index lookup on c
assertRows(execute("SELECT * FROM %s WHERE c=?", set(1, 2, 3)),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
// ordering of c should not matter
assertRows(execute("SELECT * FROM %s WHERE c=?", set(2, 1, 3)),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
assertEmpty(execute("SELECT * FROM %s WHERE c=?", set(-1)));
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND b=?", set(1, 2, 3), list(1, 2, 3));
assertRows(execute("SELECT * FROM %s WHERE c=? AND b=? ALLOW FILTERING", set(1, 2, 3), list(1, 2, 3)),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND b CONTAINS ?", set(1, 2, 3), 1);
assertRows(execute("SELECT * FROM %s WHERE c=? AND b CONTAINS ? ALLOW FILTERING", set(1, 2, 3), 1),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND d = ?", set(1, 2, 3), map(1, "a"));
assertRows(execute("SELECT * FROM %s WHERE c=? AND d = ? ALLOW FILTERING", set(1, 2, 3), map(1, "a")),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND d CONTAINS ?", set(1, 2, 3), "a");
assertRows(execute("SELECT * FROM %s WHERE c=? AND d CONTAINS ? ALLOW FILTERING", set(1, 2, 3), "a"),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE c=? AND d CONTAINS KEY ?", set(1, 2, 3), 1);
assertRows(execute("SELECT * FROM %s WHERE c=? AND d CONTAINS KEY ? ALLOW FILTERING", set(1, 2, 3), 1),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
// index lookup on d
assertRows(execute("SELECT * FROM %s WHERE d=?", map(1, "a")),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
assertRows(execute("SELECT * FROM %s WHERE d=?", map(2, "b")),
row(1, list(1, 2, 3), set(4, 5, 6), map(2, "b")),
row(1, list(4, 5, 6), set(4, 5, 6), map(2, "b"))
);
assertEmpty(execute("SELECT * FROM %s WHERE d=?", map(3, "c")));
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE d=? AND c=?", map(1, "a"), set(1, 2, 3));
assertRows(execute("SELECT * FROM %s WHERE d=? AND b=? ALLOW FILTERING", map(1, "a"), list(1, 2, 3)),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE d=? AND b CONTAINS ?", map(1, "a"), 3);
assertRows(execute("SELECT * FROM %s WHERE d=? AND b CONTAINS ? ALLOW FILTERING", map(1, "a"), 3),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertInvalidMessage("ALLOW FILTERING", "SELECT * FROM %s WHERE d=? AND b=? AND c=?", map(1, "a"), list(1, 2, 3), set(1, 2, 3));
assertRows(execute("SELECT * FROM %s WHERE d=? AND b=? AND c=? ALLOW FILTERING", map(1, "a"), list(1, 2, 3), set(1, 2, 3)),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
assertRows(execute("SELECT * FROM %s WHERE d=? AND b CONTAINS ? AND c CONTAINS ? ALLOW FILTERING", map(1, "a"), 2, 2),
row(0, list(1, 2, 3), set(1, 2, 3), map(1, "a"))
);
execute("DELETE d FROM %s WHERE a=? AND b=?", 0, list(1, 2, 3));
assertRows(execute("SELECT * FROM %s WHERE d=?", map(1, "a")),
row(0, list(4, 5, 6), set(1, 2, 3), map(1, "a"))
);
}
@Test
public void testUserDefinedTypes() throws Throwable
{
String myType = createType("CREATE TYPE %s (a set<int>, b tuple<list<int>>)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, v frozen<" + myType + ">)");
execute("INSERT INTO %s (k, v) VALUES (?, {a: ?, b: ?})", 0, set(1, 2, 3), tuple(list(1, 2, 3)));
assertRows(execute("SELECT v.a, v.b FROM %s WHERE k=?", 0),
row(set(1, 2, 3), tuple(list(1, 2, 3)))
);
}
private static String clean(String classname)
{
return StringUtils.remove(classname, "org.apache.cassandra.db.marshal.");
}
@Test
public void testToString()
{
// set<frozen<list<int>>>
SetType t = SetType.getInstance(ListType.getInstance(Int32Type.instance, false), true);
assertEquals("SetType(FrozenType(ListType(Int32Type)))", clean(t.toString()));
assertEquals("SetType(ListType(Int32Type))", clean(t.toString(true)));
// frozen<set<list<int>>>
t = SetType.getInstance(ListType.getInstance(Int32Type.instance, false), false);
assertEquals("FrozenType(SetType(ListType(Int32Type)))", clean(t.toString()));
assertEquals("SetType(ListType(Int32Type))", clean(t.toString(true)));
// map<frozen<list<int>>, int>
MapType m = MapType.getInstance(ListType.getInstance(Int32Type.instance, false), Int32Type.instance, true);
assertEquals("MapType(FrozenType(ListType(Int32Type)),Int32Type)", clean(m.toString()));
assertEquals("MapType(ListType(Int32Type),Int32Type)", clean(m.toString(true)));
// frozen<map<list<int>, int>>
m = MapType.getInstance(ListType.getInstance(Int32Type.instance, false), Int32Type.instance, false);
assertEquals("FrozenType(MapType(ListType(Int32Type),Int32Type))", clean(m.toString()));
assertEquals("MapType(ListType(Int32Type),Int32Type)", clean(m.toString(true)));
// tuple<set<int>>
List<AbstractType<?>> types = new ArrayList<>();
types.add(SetType.getInstance(Int32Type.instance, true));
TupleType tuple = new TupleType(types);
assertEquals("TupleType(SetType(Int32Type))", clean(tuple.toString()));
}
}

View File

@ -24,26 +24,30 @@ public class TupleTypeTest extends CQLTester
@Test
public void testTuplePutAndGet() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, t frozen<tuple<int, text, double>>)");
String[] valueTypes = {"frozen<tuple<int, text, double>>", "tuple<int, text, double>"};
for (String valueType : valueTypes)
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, t " + valueType + ")");
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, "foo", 3.4));
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 1, tuple(8, "bar", 0.2));
assertAllRows(
row(0, tuple(3, "foo", 3.4)),
row(1, tuple(8, "bar", 0.2))
);
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, "foo", 3.4));
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 1, tuple(8, "bar", 0.2));
assertAllRows(
row(0, tuple(3, "foo", 3.4)),
row(1, tuple(8, "bar", 0.2))
);
// nulls
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 2, tuple(5, null, 3.4));
assertRows(execute("SELECT * FROM %s WHERE k=?", 2),
row(2, tuple(5, null, 3.4))
);
// nulls
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 2, tuple(5, null, 3.4));
assertRows(execute("SELECT * FROM %s WHERE k=?", 2),
row(2, tuple(5, null, 3.4))
);
// incomplete tuple
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 3, tuple(5, "bar"));
assertRows(execute("SELECT * FROM %s WHERE k=?", 3),
row(3, tuple(5, "bar"))
);
// incomplete tuple
execute("INSERT INTO %s (k, t) VALUES (?, ?)", 3, tuple(5, "bar"));
assertRows(execute("SELECT * FROM %s WHERE k=?", 3),
row(3, tuple(5, "bar"))
);
}
}
@Test
@ -94,10 +98,4 @@ public class TupleTypeTest extends CQLTester
assertInvalidSyntax("INSERT INTO %s (k, t) VALUES (0, ())");
assertInvalid("INSERT INTO %s (k, t) VALUES (0, (2, 'foo', 3.1, 'bar'))");
}
@Test
public void testNonFrozenTuple() throws Throwable
{
assertInvalid("CREATE TABLE wrong (k int PRIMARY KEY, v tuple<int, text>)");
}
}

View File

@ -36,7 +36,7 @@ public class CollectionTypeTest
@Test
public void testListComparison()
{
ListType<String> lt = ListType.getInstance(UTF8Type.instance);
ListType<String> lt = ListType.getInstance(UTF8Type.instance, true);
ByteBuffer[] lists = new ByteBuffer[] {
ByteBufferUtil.EMPTY_BYTE_BUFFER,
@ -63,7 +63,7 @@ public class CollectionTypeTest
@Test
public void testSetComparison()
{
SetType<String> st = SetType.getInstance(UTF8Type.instance);
SetType<String> st = SetType.getInstance(UTF8Type.instance, true);
ByteBuffer[] sets = new ByteBuffer[] {
ByteBufferUtil.EMPTY_BYTE_BUFFER,
@ -90,7 +90,7 @@ public class CollectionTypeTest
@Test
public void testMapComparison()
{
MapType<String, String> mt = MapType.getInstance(UTF8Type.instance, UTF8Type.instance);
MapType<String, String> mt = MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true);
ByteBuffer[] maps = new ByteBuffer[] {
ByteBufferUtil.EMPTY_BYTE_BUFFER,
@ -119,8 +119,8 @@ public class CollectionTypeTest
@Test
public void listSerDerTest()
{
ListSerializer<String> sls = ListType.getInstance(UTF8Type.instance).getSerializer();
ListSerializer<Integer> ils = ListType.getInstance(Int32Type.instance).getSerializer();
ListSerializer<String> sls = ListType.getInstance(UTF8Type.instance, true).getSerializer();
ListSerializer<Integer> ils = ListType.getInstance(Int32Type.instance, true).getSerializer();
List<String> sl = Arrays.asList("Foo", "Bar");
List<Integer> il = Arrays.asList(3, 1, 5);
@ -143,8 +143,8 @@ public class CollectionTypeTest
@Test
public void setSerDerTest()
{
SetSerializer<String> sss = SetType.getInstance(UTF8Type.instance).getSerializer();
SetSerializer<Integer> iss = SetType.getInstance(Int32Type.instance).getSerializer();
SetSerializer<String> sss = SetType.getInstance(UTF8Type.instance, true).getSerializer();
SetSerializer<Integer> iss = SetType.getInstance(Int32Type.instance, true).getSerializer();
Set<String> ss = new HashSet(){{ add("Foo"); add("Bar"); }};
Set<Integer> is = new HashSet(){{ add(3); add(1); add(5); }};
@ -167,8 +167,8 @@ public class CollectionTypeTest
@Test
public void setMapDerTest()
{
MapSerializer<String, String> sms = MapType.getInstance(UTF8Type.instance, UTF8Type.instance).getSerializer();
MapSerializer<Integer, Integer> ims = MapType.getInstance(Int32Type.instance, Int32Type.instance).getSerializer();
MapSerializer<String, String> sms = MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true).getSerializer();
MapSerializer<Integer, Integer> ims = MapType.getInstance(Int32Type.instance, Int32Type.instance, true).getSerializer();
Map<String, String> sm = new HashMap(){{ put("Foo", "xxx"); put("Bar", "yyy"); }};
Map<Integer, Integer> im = new HashMap(){{ put(3, 0); put(1, 8); put(5, 2); }};
@ -187,8 +187,8 @@ public class CollectionTypeTest
// non map value
assertInvalid(sms, UTF8Type.instance.getSerializer().serialize("foo"));
MapSerializer<Integer, String> sims = MapType.getInstance(Int32Type.instance, UTF8Type.instance).getSerializer();
MapSerializer<String, Integer> isms = MapType.getInstance(UTF8Type.instance, Int32Type.instance).getSerializer();
MapSerializer<Integer, String> sims = MapType.getInstance(Int32Type.instance, UTF8Type.instance, true).getSerializer();
MapSerializer<String, Integer> isms = MapType.getInstance(UTF8Type.instance, Int32Type.instance, true).getSerializer();
// only key are invalid
assertInvalid(isms, sb);

View File

@ -31,7 +31,6 @@ import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.transport.Event.TopologyChange;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.StatusChange;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
@ -53,7 +52,7 @@ public class SerDeserTest
public void collectionSerDeserTest(int version) throws Exception
{
// Lists
ListType<?> lt = ListType.getInstance(Int32Type.instance);
ListType<?> lt = ListType.getInstance(Int32Type.instance, true);
List<Integer> l = Arrays.asList(2, 6, 1, 9);
List<ByteBuffer> lb = new ArrayList<>(l.size());
@ -63,7 +62,7 @@ public class SerDeserTest
assertEquals(l, lt.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(lb, lb.size(), version), version));
// Sets
SetType<?> st = SetType.getInstance(UTF8Type.instance);
SetType<?> st = SetType.getInstance(UTF8Type.instance, true);
Set<String> s = new LinkedHashSet<>();
s.addAll(Arrays.asList("bar", "foo", "zee"));
@ -74,7 +73,7 @@ public class SerDeserTest
assertEquals(s, st.getSerializer().deserializeForNativeProtocol(CollectionSerializer.pack(sb, sb.size(), version), version));
// Maps
MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance);
MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance, true);
Map<String, Long> m = new LinkedHashMap<>();
m.put("bar", 12L);
m.put("foo", 42L);
@ -165,9 +164,9 @@ public class SerDeserTest
public void udtSerDeserTest(int version) throws Exception
{
ListType<?> lt = ListType.getInstance(Int32Type.instance);
SetType<?> st = SetType.getInstance(UTF8Type.instance);
MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance);
ListType<?> lt = ListType.getInstance(Int32Type.instance, true);
SetType<?> st = SetType.getInstance(UTF8Type.instance, true);
MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance, true);
UserType udt = new UserType("ks",
bb("myType"),

View File

@ -33,7 +33,7 @@ public class Lists extends Generator<List>
public Lists(String name, Generator valueType, GeneratorConfig config)
{
super(ListType.getInstance(valueType.type), config, name, List.class);
super(ListType.getInstance(valueType.type, true), config, name, List.class);
this.valueType = valueType;
buffer = new Object[(int) sizeDistribution.maxValue()];
}

View File

@ -32,7 +32,7 @@ public class Sets extends Generator<Set>
public Sets(String name, Generator valueType, GeneratorConfig config)
{
super(SetType.getInstance(valueType.type), config, name, Set.class);
super(SetType.getInstance(valueType.type, true), config, name, Set.class);
this.valueType = valueType;
}