diff --git a/CHANGES.txt b/CHANGES.txt index b1b5df8780..5b63f48b66 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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: diff --git a/bin/cqlsh b/bin/cqlsh index 6ace9141db..f105273f52 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -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 '': diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index b1179caf2e..dbd3709157 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -255,13 +255,20 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; ::= utname= ; - ::= | | ; + ::= | | | ; +# 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. ::= "map" "<" "," ( | ) ">" | "list" "<" ( | ) ">" | "set" "<" ( | ) ">" ; + ::= "frozen" "<" "map" "<" "," ">" ">" + | "frozen" "<" "list" "<" ">" ">" + | "frozen" "<" "set" "<" ">" ">" + ; + ::= ( ksname= dot="." )? cfname= ; ::= ( ksname= dot="." )? utname= ; @@ -906,11 +913,11 @@ syntax_rules += r''' ::= [ordercol]= ( "ASC" | "DESC" ) ; - ::= [newcolname]= "PRIMARY" "KEY" + ::= [newcolname]= "PRIMARY" "KEY" ( "," [newcolname]= )* ; - ::= [newcolname]= + ::= [newcolname]= "," [newcolname]= ( "static" )? ( "," [newcolname]= ( "static" )? )* "," "PRIMARY" k="KEY" p="(" ( partkey= | [pkey]= ) @@ -986,7 +993,11 @@ def create_cf_composite_primary_key_comma_completer(ctxt, cass): syntax_rules += r''' ::= "CREATE" "CUSTOM"? "INDEX" ("IF" "NOT" "EXISTS")? indexname=? "ON" - cf= ( "(" col= ")" | "(" "KEYS" "(" col= ")" ")") + cf= "(" ( + col= | + "keys(" col= ")" | + "fullCollection(" col= ")" + ) ")" ( "USING" ( "WITH" "OPTIONS" "=" )? )? ; diff --git a/src/java/org/apache/cassandra/cql3/AbstractMarker.java b/src/java/org/apache/cassandra/cql3/AbstractMarker.java index 10a4dff27d..d18790cc9e 100644 --- a/src/java/org/apache/cassandra/cql3/AbstractMarker.java +++ b/src/java/org/apache/cassandra/cql3/AbstractMarker.java @@ -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 diff --git a/src/java/org/apache/cassandra/cql3/CQL3Row.java b/src/java/org/apache/cassandra/cql3/CQL3Row.java index 6fa2b64d0d..e3e76d18e6 100644 --- a/src/java/org/apache/cassandra/cql3/CQL3Row.java +++ b/src/java/org/apache/cassandra/cql3/CQL3Row.java @@ -27,7 +27,7 @@ public interface CQL3Row { public ByteBuffer getClusteringColumn(int i); public Cell getColumn(ColumnIdentifier name); - public List getCollection(ColumnIdentifier name); + public List getMultiCellColumn(ColumnIdentifier name); public interface Builder { diff --git a/src/java/org/apache/cassandra/cql3/CQL3Type.java b/src/java/org/apache/cassandra/cql3/CQL3Type.java index 6d5528510e..b656de8a21 100644 --- a/src/java/org/apache/cassandra/cql3/CQL3Type.java +++ b/src/java/org/apache/cassandra/cql3/CQL3Type.java @@ -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 ts) throws InvalidRequestException + public static Raw tuple(List 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> 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)); } diff --git a/src/java/org/apache/cassandra/cql3/ColumnCondition.java b/src/java/org/apache/cassandra/cql3/ColumnCondition.java index 25cb07dba1..1a8e5a3cbe 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java @@ -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 iter = collectionColumns(current.metadata().comparator.create(rowPrefix, column), current, now); + if (type.isMultiCell()) + { + Iterator 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 iter, Term.Terminal value, Operator operator) @@ -495,15 +563,15 @@ public class ColumnCondition static boolean listAppliesTo(ListType type, Iterator iter, List 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 iter, Set elements, Operator operator) { ArrayList 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 iter, Map 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 inValues; + private final List 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 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 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> 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); } } diff --git a/src/java/org/apache/cassandra/cql3/Constants.java b/src/java/org/apache/cassandra/cql3/Constants.java index a8f012081c..01fbdf0459 100644 --- a/src/java/org/apache/cassandra/cql3/Constants.java +++ b/src/java/org/apache/cassandra/cql3/Constants.java @@ -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)); diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index b1c598bc7f..06ba81c2f3 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -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 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; diff --git a/src/java/org/apache/cassandra/cql3/Lists.java b/src/java/org/apache/cassandra/cql3/Lists.java index 9d22364c89..d5174d1843 100644 --- a/src/java/org/apache/cassandra/cql3/Lists.java +++ b/src/java/org/apache/cassandra/cql3/Lists.java @@ -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 elements; @@ -143,7 +142,7 @@ public abstract class Lists List elements = new ArrayList(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 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 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 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"); diff --git a/src/java/org/apache/cassandra/cql3/Maps.java b/src/java/org/apache/cassandra/cql3/Maps.java index ce0ba2afd0..5b5883300a 100644 --- a/src/java/org/apache/cassandra/cql3/Maps.java +++ b/src/java/org/apache/cassandra/cql3/Maps.java @@ -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 map; @@ -152,7 +152,7 @@ public abstract class Maps Map m = (Map)type.getSerializer().deserializeForNativeProtocol(value, version); Map map = new LinkedHashMap(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 buffers = new ArrayList(2 * map.size()); + return getWithProtocolVersion(options.getProtocolVersion()); + } + + public ByteBuffer getWithProtocolVersion(int protocolVersion) + { + List buffers = new ArrayList<>(2 * map.size()); for (Map.Entry entry : map.entrySet()) { buffers.add(entry.getKey()); buffers.add(entry.getValue()); } - return CollectionSerializer.pack(buffers, map.size(), 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 thisEntry = thisIter.next(); Map.Entry 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 toAdd = ((Maps.Value)value).map; - for (Map.Entry 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 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"); diff --git a/src/java/org/apache/cassandra/cql3/Operation.java b/src/java/org/apache/cassandra/cql3/Operation.java index 32b6a12f73..816acb20ab 100644 --- a/src/java/org/apache/cassandra/cql3/Operation.java +++ b/src/java/org/apache/cassandra/cql3/Operation.java @@ -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) { diff --git a/src/java/org/apache/cassandra/cql3/Sets.java b/src/java/org/apache/cassandra/cql3/Sets.java index 315d7d337a..c5a8e05cf3 100644 --- a/src/java/org/apache/cassandra/cql3/Sets.java +++ b/src/java/org/apache/cassandra/cql3/Sets.java @@ -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 elements; + public final SortedSet elements; - public Value(Set elements) + public Value(SortedSet 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 elements = new LinkedHashSet(s.size()); + SortedSet elements = new TreeSet(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(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 thisIter = elements.iterator(); Iterator 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 buffers = new TreeSet(comparator); + SortedSet 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 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 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; diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java index e5206c8d15..1587df1a67 100644 --- a/src/java/org/apache/cassandra/cql3/Term.java +++ b/src/java/org/apache/cassandra/cql3/Term.java @@ -138,6 +138,12 @@ public interface Term public abstract List 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. diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java b/src/java/org/apache/cassandra/cql3/Tuples.java index 883cc60026..4f1f1414bb 100644 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ b/src/java/org/apache/cassandra/cql3/Tuples.java @@ -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> 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 receivers) throws InvalidRequestException diff --git a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java index 42d0cb8258..fef70fb79b 100644 --- a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java +++ b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java @@ -267,19 +267,19 @@ public abstract class UntypedResultSet implements Iterable public Set getSet(String column, AbstractType 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 List getList(String column, AbstractType 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 Map getMap(String column, AbstractType keyType, AbstractType 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 getColumns() diff --git a/src/java/org/apache/cassandra/cql3/UpdateParameters.java b/src/java/org/apache/cassandra/cql3/UpdateParameters.java index 62ec09c05e..74c3214bda 100644 --- a/src/java/org/apache/cassandra/cql3/UpdateParameters.java +++ b/src/java/org/apache/cassandra/cql3/UpdateParameters.java @@ -97,6 +97,6 @@ public class UpdateParameters return Collections.emptyList(); CQL3Row row = prefetchedLists.get(rowKey); - return row == null ? Collections.emptyList() : row.getCollection(cql3ColumnName); + return row == null ? Collections.emptyList() : row.getMultiCellColumn(cql3ColumnName); } } diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index 9d66c16e87..c92bc496a0 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/UserTypes.java @@ -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; diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java index 7be635f59d..a17ee9244e 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java @@ -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> 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> newTypes = new ArrayList>(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: diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java index cfdd65fa91..576011fd50 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java @@ -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 diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java index c93adc262a..3032897ff6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java @@ -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 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 (full())'."); + } 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); diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java index 672bb50f52..5f70ab8cd9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java @@ -199,16 +199,16 @@ public class CreateTableStatement extends SchemaAlteringStatement CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns); - Map definedCollections = null; + Map definedMultiCellCollections = null; for (Map.Entry 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(); - 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.>emptyList()) - : new CompoundSparseCellNameType.WithCollection(Collections.>emptyList(), ColumnToCollectionType.getInstance(definedCollections)); + : new CompoundSparseCellNameType.WithCollection(Collections.>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); diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java index b49f60b680..33c61e7f5b 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java @@ -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()) diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java index 8bcaaf6bfc..a3b82a4bd9 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java @@ -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; } diff --git a/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java b/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java index dc77bcc938..eeee90757b 100644 --- a/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java +++ b/src/java/org/apache/cassandra/cql3/statements/IndexTarget.java @@ -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); } } } diff --git a/src/java/org/apache/cassandra/cql3/statements/Restriction.java b/src/java/org/apache/cassandra/cql3/statements/Restriction.java index 5307cbb4f1..659ed95ca8 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Restriction.java +++ b/src/java/org/apache/cassandra/cql3/statements/Restriction.java @@ -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 values(QueryOptions options) throws InvalidRequestException; diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 84cbdc0f6f..09d1e52b7f 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -707,33 +707,52 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache ColumnDefinition def = idIter.next(); assert r != null && !r.isSlice(); - List 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 columns = new TreeSet(cfm.comparator); - Iterator 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 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 columns = new TreeSet(cfm.comparator); + Iterator 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> values = ((MultiColumnRestriction.IN) r).splitValues(options); + TreeSet inValues = new TreeSet<>(cfm.comparator); + for (List 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 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 collection = row.getCollection(def.name); - ByteBuffer value = collection == null + List 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; } diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java index 7f8156e5b6..b1c6ccc570 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java @@ -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() diff --git a/src/java/org/apache/cassandra/db/CFRowAdder.java b/src/java/org/apache/cassandra/db/CFRowAdder.java index dfe49ee557..3ff91711cd 100644 --- a/src/java/org/apache/cassandra/db/CFRowAdder.java +++ b/src/java/org/apache/cassandra/db/CFRowAdder.java @@ -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); } diff --git a/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java b/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java index e33cc6308e..c62f890ccb 100644 --- a/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java +++ b/src/java/org/apache/cassandra/db/composites/AbstractCellNameType.java @@ -304,7 +304,7 @@ public abstract class AbstractCellNameType extends AbstractCType implements Cell return cell; } - public List getCollection(ColumnIdentifier name) + public List 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 getCollection(ColumnIdentifier name) + public List getMultiCellColumn(ColumnIdentifier name) { return collections == null ? null : collections.get(name); } diff --git a/src/java/org/apache/cassandra/db/composites/CellNameType.java b/src/java/org/apache/cassandra/db/composites/CellNameType.java index 4f45d41949..1e87296a19 100644 --- a/src/java/org/apache/cassandra/db/composites/CellNameType.java +++ b/src/java/org/apache/cassandra/db/composites/CellNameType.java @@ -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(); diff --git a/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java b/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java index 27b327185d..c88c6f442d 100644 --- a/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java +++ b/src/java/org/apache/cassandra/db/composites/CompoundSparseCellNameType.java @@ -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; diff --git a/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java b/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java index 2d56bfa6aa..6f2760a85f 100644 --- a/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java +++ b/src/java/org/apache/cassandra/db/filter/ExtendedFilter.java @@ -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()) diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndex.java b/src/java/org/apache/cassandra/db/index/SecondaryIndex.java index 044912fd5e..62b9e4c604 100644 --- a/src/java/org/apache/cassandra/db/index/SecondaryIndex.java +++ b/src/java/org/apache/cassandra/db/index/SecondaryIndex.java @@ -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; diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java index 7f8e845a1d..fbbec1df6d 100644 --- a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java +++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java @@ -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> expressions : expressionsByIndexType.entrySet()) { Set 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> 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 = ""; + } + sb.append(columnName).append(" ").append(expression.operator).append(" ").append("'"); + } + } + + throw new InvalidRequestException(sb.toString()); + } } /** diff --git a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java index b9ccd8e781..93e064357f 100644 --- a/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java +++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexSearcher.java @@ -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 diff --git a/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java b/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java index 410ea83975..ec965fd85b 100644 --- a/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java +++ b/src/java/org/apache/cassandra/db/index/composites/CompositesIndex.java @@ -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) { diff --git a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java index e69e65623e..a11a0d95f9 100644 --- a/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java +++ b/src/java/org/apache/cassandra/db/index/composites/CompositesIndexOnCollectionValue.java @@ -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 diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java index 4e1f2a38b3..8dd2ff36b1 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java @@ -223,6 +223,24 @@ public abstract class AbstractType implements Comparator 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. diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java b/src/java/org/apache/cassandra/db/marshal/CollectionType.java index e63f2a5e74..24ad5333e5 100644 --- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java +++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java @@ -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 extends AbstractType { @@ -56,27 +56,9 @@ public abstract class CollectionType extends AbstractType public abstract AbstractType nameComparator(); public abstract AbstractType valueComparator(); - protected abstract void appendToStringBuilder(StringBuilder sb); - - public abstract List serializedValues(List cells); - @Override public abstract CollectionSerializer 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 extends AbstractType } } - @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 true if this collection is a Map, false otherwise. @@ -124,9 +99,11 @@ public abstract class CollectionType extends AbstractType return kind == Kind.MAP; } - protected List enforceLimit(List cells, int version) + public List enforceLimit(List 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 extends AbstractType return cells.subList(0, MAX_ELEMENTS); } + public abstract List serializedValues(List cells); + public ByteBuffer serializeForNativeProtocol(List cells, int version) { + assert isMultiCell(); cells = enforceLimit(cells, version); List 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); + } } diff --git a/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java b/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java index a28b87414c..6fb32fb8d4 100644 --- a/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java +++ b/src/java/org/apache/cassandra/db/marshal/ColumnToCollectionType.java @@ -33,7 +33,7 @@ import org.apache.cassandra.utils.ByteBufferUtil; public class ColumnToCollectionType extends AbstractType { // interning instances - private static final Map, ColumnToCollectionType> instances = new HashMap, ColumnToCollectionType>(); + private static final Map, ColumnToCollectionType> instances = new HashMap<>(); public final Map defined; diff --git a/src/java/org/apache/cassandra/db/marshal/FrozenType.java b/src/java/org/apache/cassandra/db/marshal/FrozenType.java new file mode 100644 index 0000000000..f440c901f2 --- /dev/null +++ b/src/java/org/apache/cassandra/db/marshal/FrozenType.java @@ -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 +{ + public static AbstractType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException + { + List> 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 getSerializer() + { + throw new UnsupportedOperationException(); + } +} diff --git a/src/java/org/apache/cassandra/db/marshal/ListType.java b/src/java/org/apache/cassandra/db/marshal/ListType.java index 171e17921a..510a526309 100644 --- a/src/java/org/apache/cassandra/db/marshal/ListType.java +++ b/src/java/org/apache/cassandra/db/marshal/ListType.java @@ -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 extends CollectionType> { - // interning instances - private static final Map, ListType> instances = new HashMap, ListType>(); + private static final Logger logger = LoggerFactory.getLogger(ListType.class); - public final AbstractType elements; + // interning instances + private static final Map, ListType> instances = new HashMap<>(); + private static final Map, ListType> frozenInstances = new HashMap<>(); + + private final AbstractType elements; public final ListSerializer serializer; + private final boolean isMultiCell; public static ListType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException { @@ -42,25 +46,32 @@ public class ListType extends CollectionType> 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 ListType getInstance(AbstractType elements) + public static synchronized ListType getInstance(AbstractType elements, boolean isMultiCell) { - ListType t = instances.get(elements); + Map, ListType> internMap = isMultiCell ? instances : frozenInstances; + ListType t = internMap.get(elements); if (t == null) { - t = new ListType(elements); - instances.put(elements, t); + t = new ListType(elements, isMultiCell); + internMap.put(elements, t); } return t; } - private ListType(AbstractType elements) + private ListType(AbstractType elements, boolean isMultiCell) { super(Kind.LIST); this.elements = elements; this.serializer = ListSerializer.getInstance(elements.getSerializer()); + this.isMultiCell = isMultiCell; + } + + public AbstractType getElementsType() + { + return elements; } public AbstractType nameComparator() @@ -78,6 +89,35 @@ public class ListType extends CollectionType> 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 extends CollectionType> 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 extends CollectionType> 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.>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.>singletonList(elements), ignoreFreezing || !isMultiCell)); + if (includeFrozenType) + sb.append(")"); + return sb.toString(); } public List serializedValues(List cells) { + assert isMultiCell; List bbs = new ArrayList(cells.size()); for (Cell c : cells) bbs.add(c.value()); diff --git a/src/java/org/apache/cassandra/db/marshal/MapType.java b/src/java/org/apache/cassandra/db/marshal/MapType.java index dbf6721035..64f3e2a98b 100644 --- a/src/java/org/apache/cassandra/db/marshal/MapType.java +++ b/src/java/org/apache/cassandra/db/marshal/MapType.java @@ -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 extends CollectionType> { // interning instances - private static final Map, AbstractType>, MapType> instances = new HashMap, AbstractType>, MapType>(); + private static final Map, AbstractType>, MapType> instances = new HashMap<>(); + private static final Map, AbstractType>, MapType> frozenInstances = new HashMap<>(); - public final AbstractType keys; - public final AbstractType values; + private final AbstractType keys; + private final AbstractType values; private final MapSerializer serializer; + private final boolean isMultiCell; public static MapType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException { @@ -44,27 +45,39 @@ public class MapType extends CollectionType> 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 MapType getInstance(AbstractType keys, AbstractType values) + public static synchronized MapType getInstance(AbstractType keys, AbstractType values, boolean isMultiCell) { + Map, AbstractType>, MapType> internMap = isMultiCell ? instances : frozenInstances; Pair, AbstractType> p = Pair., AbstractType>create(keys, values); - MapType t = instances.get(p); + MapType t = internMap.get(p); if (t == null) { - t = new MapType(keys, values); - instances.put(p, t); + t = new MapType<>(keys, values, isMultiCell); + internMap.put(p, t); } return t; } - private MapType(AbstractType keys, AbstractType values) + private MapType(AbstractType keys, AbstractType 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 getKeysType() + { + return keys; + } + + public AbstractType getValuesType() + { + return values; } public AbstractType nameComparator() @@ -77,30 +90,66 @@ public class MapType extends CollectionType> 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 extends CollectionType> 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 serializedValues(List cells) { + assert isMultiCell; List bbs = new ArrayList(cells.size() * 2); for (Cell c : cells) { diff --git a/src/java/org/apache/cassandra/db/marshal/SetType.java b/src/java/org/apache/cassandra/db/marshal/SetType.java index d2f7f12231..e10f2a16f3 100644 --- a/src/java/org/apache/cassandra/db/marshal/SetType.java +++ b/src/java/org/apache/cassandra/db/marshal/SetType.java @@ -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 extends CollectionType> { // interning instances - private static final Map, SetType> instances = new HashMap, SetType>(); + private static final Map, SetType> instances = new HashMap<>(); + private static final Map, SetType> frozenInstances = new HashMap<>(); - public final AbstractType elements; + private final AbstractType elements; private final SetSerializer serializer; + private final boolean isMultiCell; public static SetType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException { @@ -40,25 +41,32 @@ public class SetType extends CollectionType> 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 SetType getInstance(AbstractType elements) + public static synchronized SetType getInstance(AbstractType elements, boolean isMultiCell) { - SetType t = instances.get(elements); + Map, SetType> internMap = isMultiCell ? instances : frozenInstances; + SetType t = internMap.get(elements); if (t == null) { - t = new SetType(elements); - instances.put(elements, t); + t = new SetType(elements, isMultiCell); + internMap.put(elements, t); } return t; } - public SetType(AbstractType elements) + public SetType(AbstractType elements, boolean isMultiCell) { super(Kind.SET); this.elements = elements; this.serializer = SetSerializer.getInstance(elements.getSerializer()); + this.isMultiCell = isMultiCell; + } + + public AbstractType getElementsType() + { + return elements; } public AbstractType nameComparator() @@ -71,6 +79,35 @@ public class SetType extends CollectionType> 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 extends CollectionType> return elements.isByteOrderComparable(); } - protected void appendToStringBuilder(StringBuilder sb) + @Override + public String toString(boolean ignoreFreezing) { - sb.append(getClass().getName()).append(TypeParser.stringifyTypeParameters(Collections.>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.>singletonList(elements), ignoreFreezing || !isMultiCell)); + if (includeFrozenType) + sb.append(")"); + return sb.toString(); } public List serializedValues(List cells) diff --git a/src/java/org/apache/cassandra/db/marshal/TupleType.java b/src/java/org/apache/cassandra/db/marshal/TupleType.java index 42aaba13cf..ddaf53f8a1 100644 --- a/src/java/org/apache/cassandra/db/marshal/TupleType.java +++ b/src/java/org/apache/cassandra/db/marshal/TupleType.java @@ -41,12 +41,17 @@ public class TupleType extends AbstractType public TupleType(List> 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> 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 @Override public String toString() { - return getClass().getName() + TypeParser.stringifyTypeParameters(types); + return getClass().getName() + TypeParser.stringifyTypeParameters(types, true); } } diff --git a/src/java/org/apache/cassandra/db/marshal/TypeParser.java b/src/java/org/apache/cassandra/db/marshal/TypeParser.java index 1b831808f1..cdb5679d6c 100644 --- a/src/java/org/apache/cassandra/db/marshal/TypeParser.java +++ b/src/java/org/apache/cassandra/db/marshal/TypeParser.java @@ -241,7 +241,7 @@ public class TypeParser public Map getCollectionsParameters() throws SyntaxException, ConfigurationException { - Map map = new HashMap(); + Map map = new HashMap<>(); if (isEOS()) return map; @@ -539,25 +539,37 @@ public class TypeParser */ public static String stringifyTypeParameters(List> types) { - StringBuilder sb = new StringBuilder(); - sb.append('(').append(StringUtils.join(types, ",")).append(')'); - return sb.toString(); + return stringifyTypeParameters(types, false); } - public static String stringifyCollectionsParameters(Map collections) + /** + * Helper function to ease the writing of AbstractType.toString() methods. + */ + public static String stringifyTypeParameters(List> 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 collections) { StringBuilder sb = new StringBuilder(); sb.append('('); boolean first = true; - for (Map.Entry entry : collections.entrySet()) + for (Map.Entry 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(); diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java index 44c208f32e..180d713fb3 100644 --- a/src/java/org/apache/cassandra/db/marshal/UserType.java +++ b/src/java/org/apache/cassandra/db/marshal/UserType.java @@ -61,7 +61,7 @@ public class UserType extends TupleType for (Pair p : params.right) { columnNames.add(p.left); - columnTypes.add(p.right); + columnTypes.add(p.right.freeze()); } return new UserType(keyspace, name, columnNames, columnTypes); } diff --git a/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java b/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java index 7a6be71bbd..2ba4dbf626 100644 --- a/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java +++ b/src/java/org/apache/cassandra/hadoop/pig/CqlStorage.java @@ -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()); diff --git a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java index 2a5e809fe4..29ae2fd8f2 100644 --- a/src/java/org/apache/cassandra/serializers/CollectionSerializer.java +++ b/src/java/org/apache/cassandra/serializers/CollectionSerializer.java @@ -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 implements TypeSerializer @@ -35,17 +37,17 @@ public abstract class CollectionSerializer implements TypeSerializer { List 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 implements TypeSerializer 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 buffers, int elements, int version) + public static ByteBuffer pack(Collection buffers, int elements, int version) { int size = 0; for (ByteBuffer bb : buffers) @@ -74,7 +76,7 @@ public abstract class CollectionSerializer implements TypeSerializer 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 implements TypeSerializer 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 implements TypeSerializer 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 implements TypeSerializer protected static int sizeOfValue(ByteBuffer value, int version) { - if (version >= 3) + if (version >= Server.VERSION_3) { return value == null ? 4 : 4 + value.remaining(); } diff --git a/src/java/org/apache/cassandra/serializers/ListSerializer.java b/src/java/org/apache/cassandra/serializers/ListSerializer.java index 7387e1bed9..aeee2b9dd7 100644 --- a/src/java/org/apache/cassandra/serializers/ListSerializer.java +++ b/src/java/org/apache/cassandra/serializers/ListSerializer.java @@ -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 extends CollectionSerializer> { // interning instances @@ -111,6 +111,34 @@ public class ListSerializer extends CollectionSerializer> } } + /** + * 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 value) { StringBuilder sb = new StringBuilder(); @@ -118,13 +146,9 @@ public class ListSerializer extends CollectionSerializer> for (T element : value) { if (isFirst) - { isFirst = false; - } else - { sb.append("; "); - } sb.append(elements.toString(element)); } return sb.toString(); diff --git a/src/java/org/apache/cassandra/serializers/MapSerializer.java b/src/java/org/apache/cassandra/serializers/MapSerializer.java index dadadd0679..bc8e509b7f 100644 --- a/src/java/org/apache/cassandra/serializers/MapSerializer.java +++ b/src/java/org/apache/cassandra/serializers/MapSerializer.java @@ -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 extends CollectionSerializer> } } + /** + * 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 value) { StringBuilder sb = new StringBuilder(); @@ -121,13 +155,9 @@ public class MapSerializer extends CollectionSerializer> for (Map.Entry element : value.entrySet()) { if (isFirst) - { isFirst = false; - } else - { sb.append("; "); - } sb.append('('); sb.append(keys.toString(element.getKey())); sb.append(", "); diff --git a/src/java/org/apache/cassandra/transport/DataType.java b/src/java/org/apache/cassandra/transport/DataType.java index a45d7ced11..0ea353aa1a 100644 --- a/src/java/org/apache/cassandra/transport/DataType.java +++ b/src/java/org/apache/cassandra/transport/DataType.java @@ -214,18 +214,18 @@ public enum DataType implements OptionCodec.Codecable { if (type instanceof ListType) { - return Pair.create(LIST, ((ListType)type).elements); + return Pair.create(LIST, ((ListType)type).getElementsType()); } else if (type instanceof MapType) { MapType mt = (MapType)type; - return Pair.create(MAP, Arrays.asList(mt.keys, mt.values)); + return Pair.create(MAP, Arrays.asList(mt.getKeysType(), mt.getValuesType())); } - else + else if (type instanceof SetType) { - assert type instanceof SetType; - return Pair.create(SET, ((SetType)type).elements); + return Pair.create(SET, ((SetType)type).getElementsType()); } + throw new AssertionError(); } if (type instanceof UserType && version >= 3) @@ -251,12 +251,12 @@ public enum DataType implements OptionCodec.Codecable 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 l = (List)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: diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index 6e4a5a9394..aacfb4bbac 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -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(); + } } } diff --git a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java index 205babc4f2..3b1a82613c 100644 --- a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java +++ b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java @@ -154,9 +154,9 @@ public class ColumnConditionTest { CFMetaData cfm = CFMetaData.compile("create table foo(a int PRIMARY KEY, b int, c list)", "ks"); Map 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 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 set(ByteBuffer... values) + private static SortedSet set(ByteBuffer... values) { - Set results = new HashSet(values.length); + SortedSet 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)", "ks"); Map 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 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)", "ks"); Map 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 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 placeholderMap = new TreeMap<>(); placeholderMap.put(ONE, ONE); diff --git a/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java b/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java new file mode 100644 index 0000000000..203adae326 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/FrozenCollectionsTest.java @@ -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> 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, list>> 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>, 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, list>>, c frozen>, 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, list>>, c frozen>)" + 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, list>> 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>)"); + 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>)"); + 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>)"); + 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>)", + "Non-frozen collections are not allowed inside collections"); + + assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t frozen>)", + "Counters are not allowed inside collections"); + + assertInvalidCreateWithMessage("CREATE TABLE %s (k int PRIMARY KEY, t frozen)", + "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>, c frozen>, PRIMARY KEY (a, b))"); + + alterTable("ALTER TABLE %s ALTER c TYPE frozen>"); + + assertInvalidAlterWithMessage("ALTER TABLE %s ALTER b TYPE frozen>", + "types are not order-compatible"); + + assertInvalidAlterWithMessage("ALTER TABLE %s ALTER b TYPE list", + "types are not order-compatible"); + + assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE list", + "types are incompatible"); + + alterTable("ALTER TABLE %s DROP c"); + alterTable("ALTER TABLE %s ADD c frozen>"); + assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE frozen>", + "types are incompatible"); + + alterTable("ALTER TABLE %s DROP c"); + alterTable("ALTER TABLE %s ADD c frozen>"); + assertInvalidAlterWithMessage("ALTER TABLE %s ALTER c TYPE frozen>", + "types are incompatible"); + alterTable("ALTER TABLE %s ALTER c TYPE frozen>"); + } + + 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> PRIMARY KEY, b frozen>)"); + + // 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 column"); + assertInvalidIndexCreationWithMessage("CREATE INDEX ON %s (keys(b))", "Cannot create index on keys of frozen column"); + + createTable("CREATE TABLE %s (a int, b frozen>, c frozen>, d frozen>, 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, b tuple>)"); + 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>> + 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>> + 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>, 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, 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> + List> types = new ArrayList<>(); + types.add(SetType.getInstance(Int32Type.instance, true)); + TupleType tuple = new TupleType(types); + assertEquals("TupleType(SetType(Int32Type))", clean(tuple.toString())); + } +} diff --git a/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java b/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java index 53e7e71bd5..ce935e362e 100644 --- a/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java +++ b/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java @@ -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>)"); + String[] valueTypes = {"frozen>", "tuple"}; + 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)"); - } } diff --git a/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java b/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java index 18156c3cee..92990e9597 100644 --- a/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java +++ b/test/unit/org/apache/cassandra/db/marshal/CollectionTypeTest.java @@ -36,7 +36,7 @@ public class CollectionTypeTest @Test public void testListComparison() { - ListType lt = ListType.getInstance(UTF8Type.instance); + ListType 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 st = SetType.getInstance(UTF8Type.instance); + SetType 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 mt = MapType.getInstance(UTF8Type.instance, UTF8Type.instance); + MapType 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 sls = ListType.getInstance(UTF8Type.instance).getSerializer(); - ListSerializer ils = ListType.getInstance(Int32Type.instance).getSerializer(); + ListSerializer sls = ListType.getInstance(UTF8Type.instance, true).getSerializer(); + ListSerializer ils = ListType.getInstance(Int32Type.instance, true).getSerializer(); List sl = Arrays.asList("Foo", "Bar"); List il = Arrays.asList(3, 1, 5); @@ -143,8 +143,8 @@ public class CollectionTypeTest @Test public void setSerDerTest() { - SetSerializer sss = SetType.getInstance(UTF8Type.instance).getSerializer(); - SetSerializer iss = SetType.getInstance(Int32Type.instance).getSerializer(); + SetSerializer sss = SetType.getInstance(UTF8Type.instance, true).getSerializer(); + SetSerializer iss = SetType.getInstance(Int32Type.instance, true).getSerializer(); Set ss = new HashSet(){{ add("Foo"); add("Bar"); }}; Set is = new HashSet(){{ add(3); add(1); add(5); }}; @@ -167,8 +167,8 @@ public class CollectionTypeTest @Test public void setMapDerTest() { - MapSerializer sms = MapType.getInstance(UTF8Type.instance, UTF8Type.instance).getSerializer(); - MapSerializer ims = MapType.getInstance(Int32Type.instance, Int32Type.instance).getSerializer(); + MapSerializer sms = MapType.getInstance(UTF8Type.instance, UTF8Type.instance, true).getSerializer(); + MapSerializer ims = MapType.getInstance(Int32Type.instance, Int32Type.instance, true).getSerializer(); Map sm = new HashMap(){{ put("Foo", "xxx"); put("Bar", "yyy"); }}; Map 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 sims = MapType.getInstance(Int32Type.instance, UTF8Type.instance).getSerializer(); - MapSerializer isms = MapType.getInstance(UTF8Type.instance, Int32Type.instance).getSerializer(); + MapSerializer sims = MapType.getInstance(Int32Type.instance, UTF8Type.instance, true).getSerializer(); + MapSerializer isms = MapType.getInstance(UTF8Type.instance, Int32Type.instance, true).getSerializer(); // only key are invalid assertInvalid(isms, sb); diff --git a/test/unit/org/apache/cassandra/transport/SerDeserTest.java b/test/unit/org/apache/cassandra/transport/SerDeserTest.java index 9b66efb921..649f7a2973 100644 --- a/test/unit/org/apache/cassandra/transport/SerDeserTest.java +++ b/test/unit/org/apache/cassandra/transport/SerDeserTest.java @@ -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 l = Arrays.asList(2, 6, 1, 9); List 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 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 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"), diff --git a/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java b/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java index 6480d7aa55..bfa58ea37f 100644 --- a/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java +++ b/tools/stress/src/org/apache/cassandra/stress/generate/values/Lists.java @@ -33,7 +33,7 @@ public class Lists extends Generator 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()]; } diff --git a/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java b/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java index 82462868d9..5c17b4e9a7 100644 --- a/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java +++ b/tools/stress/src/org/apache/cassandra/stress/generate/values/Sets.java @@ -32,7 +32,7 @@ public class Sets extends Generator 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; }