http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/UserTypes.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index 0beff06..db46fdd 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/UserTypes.java @@ -20,12 +20,18 @@ package org.apache.cassandra.cql3; import java.nio.ByteBuffer; import java.util.*; +import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.utils.ByteBufferUtil; +import static org.apache.cassandra.cql3.Constants.UNSET_VALUE; + /** * Static helper methods and classes for user types. */ @@ -78,8 +84,10 @@ public abstract class UserTypes { // We had some field that are not part of the type for (ColumnIdentifier id : entries.keySet()) + { if (!ut.fieldNames().contains(id.bytes)) throw new InvalidRequestException(String.format("Unknown field '%s' in value of user defined type %s", id, ut.getNameAsString())); + } } DelayedValue value = new DelayedValue(((UserType)receiver.type), values); @@ -88,7 +96,7 @@ public abstract class UserTypes private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - if (!(receiver.type instanceof UserType)) + if (!receiver.type.isUDT()) throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type())); UserType ut = (UserType)receiver.type; @@ -101,7 +109,10 @@ public abstract class UserTypes ColumnSpecification fieldSpec = fieldSpecOf(receiver, i); if (!value.testAssignment(keyspace, fieldSpec).isAssignable()) - throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", receiver, field, fieldSpec.type.asCQL3Type())); + { + throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", + receiver, field, fieldSpec.type.asCQL3Type())); + } } } @@ -135,7 +146,52 @@ public abstract class UserTypes } } - // Same purpose than Lists.DelayedValue, except we do handle bind marker in that case + public static class Value extends Term.MultiItemTerminal + { + private final UserType type; + public final ByteBuffer[] elements; + + public Value(UserType type, ByteBuffer[] elements) + { + this.type = type; + this.elements = elements; + } + + public static Value fromSerialized(ByteBuffer bytes, UserType type) + { + ByteBuffer[] values = type.split(bytes); + if (values.length > type.size()) + { + throw new InvalidRequestException(String.format( + "UDT value contained too many fields (expected %s, got %s)", type.size(), values.length)); + } + + return new Value(type, type.split(bytes)); + } + + public ByteBuffer get(int protocolVersion) + { + return TupleType.buildValue(elements); + } + + public boolean equals(UserType userType, Value v) + { + if (elements.length != v.elements.length) + return false; + + for (int i = 0; i < elements.length; i++) + if (userType.fieldType(i).compare(elements[i], v.elements[i]) != 0) + return false; + + return true; + } + + public List<ByteBuffer> getElements() + { + return Arrays.asList(elements); + } + } + public static class DelayedValue extends Term.NonTerminal { private final UserType type; @@ -168,20 +224,27 @@ public abstract class UserTypes private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException { + if (values.size() > type.size()) + { + throw new InvalidRequestException(String.format( + "UDT value contained too many fields (expected %s, got %s)", type.size(), values.size())); + } + ByteBuffer[] buffers = new ByteBuffer[values.size()]; for (int i = 0; i < type.size(); i++) { buffers[i] = values.get(i).bindAndGet(options); - // Since A UDT value is always written in its entirety Cassandra can't preserve a pre-existing value by 'not setting' the new value. Reject the query. - if (buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER) + // Since a frozen UDT value is always written in its entirety Cassandra can't preserve a pre-existing + // value by 'not setting' the new value. Reject the query. + if (!type.isMultiCell() && buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER) throw new InvalidRequestException(String.format("Invalid unset value for field '%s' of user defined type %s", type.fieldNameAsString(i), type.getNameAsString())); } return buffers; } - public Constants.Value bind(QueryOptions options) throws InvalidRequestException + public Value bind(QueryOptions options) throws InvalidRequestException { - return new Constants.Value(bindAndGet(options)); + return new Value(type, bindInternal(options)); } @Override @@ -190,4 +253,113 @@ public abstract class UserTypes return UserType.buildValue(bindInternal(options)); } } + + public static class Marker extends AbstractMarker + { + protected Marker(int bindIndex, ColumnSpecification receiver) + { + super(bindIndex, receiver); + assert receiver.type.isUDT(); + } + + public Terminal bind(QueryOptions options) throws InvalidRequestException + { + ByteBuffer value = options.getValues().get(bindIndex); + if (value == null) + return null; + if (value == ByteBufferUtil.UNSET_BYTE_BUFFER) + return UNSET_VALUE; + return Value.fromSerialized(value, (UserType) receiver.type); + } + } + + public static class Setter extends Operation + { + public Setter(ColumnDefinition column, Term t) + { + super(column, t); + } + + public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException + { + Term.Terminal value = t.bind(params.options); + if (value == UNSET_VALUE) + return; + + Value userTypeValue = (Value) value; + if (column.type.isMultiCell()) + { + // setting a whole UDT at once means we overwrite all cells, so delete existing cells + params.setComplexDeletionTimeForOverwrite(column); + if (value == null) + return; + + Iterator<ByteBuffer> fieldNameIter = userTypeValue.type.fieldNames().iterator(); + for (ByteBuffer buffer : userTypeValue.elements) + { + ByteBuffer fieldName = fieldNameIter.next(); + if (buffer == null) + continue; + + CellPath fieldPath = userTypeValue.type.cellPathForField(fieldName); + params.addCell(column, fieldPath, buffer); + } + } + else + { + // for frozen UDTs, we're overwriting the whole cell value + if (value == null) + params.addTombstone(column); + else + params.addCell(column, value.get(params.options.getProtocolVersion())); + } + } + } + + public static class SetterByField extends Operation + { + private final ColumnIdentifier field; + + public SetterByField(ColumnDefinition column, ColumnIdentifier field, Term t) + { + super(column, t); + this.field = field; + } + + public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException + { + // we should not get here for frozen UDTs + assert column.type.isMultiCell() : "Attempted to set an individual field on a frozen UDT"; + + Term.Terminal value = t.bind(params.options); + if (value == UNSET_VALUE) + return; + + CellPath fieldPath = ((UserType) column.type).cellPathForField(field.bytes); + if (value == null) + params.addTombstone(column, fieldPath); + else + params.addCell(column, fieldPath, value.get(params.options.getProtocolVersion())); + } + } + + public static class DeleterByField extends Operation + { + private final ColumnIdentifier field; + + public DeleterByField(ColumnDefinition column, ColumnIdentifier field) + { + super(column, null); + this.field = field; + } + + public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException + { + // we should not get here for frozen UDTs + assert column.type.isMultiCell() : "Attempted to delete a single field from a frozen UDT"; + + CellPath fieldPath = ((UserType) column.type).cellPathForField(field.bytes); + params.addTombstone(column, fieldPath); + } + } }
http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java b/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java index d36a492..cdbc855 100644 --- a/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/AbstractFunction.java @@ -91,7 +91,7 @@ public abstract class AbstractFunction implements Function // We should ignore the fact that the receiver type is frozen in our comparison as functions do not support // frozen types for return type AbstractType<?> returnType = returnType(); - if (receiver.type.isFrozenCollection()) + if (receiver.type.isFreezable() && !receiver.type.isMultiCell()) returnType = returnType.freeze(); if (receiver.type.equals(returnType)) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index 1766a79..5021e10 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -99,16 +99,24 @@ public class FunctionCall extends Term.NonTerminal private static Term.Terminal makeTerminal(Function fun, ByteBuffer result, int version) throws InvalidRequestException { - if (!(fun.returnType() instanceof CollectionType)) - return new Constants.Value(result); - - switch (((CollectionType)fun.returnType()).kind) + if (fun.returnType().isCollection()) + { + switch (((CollectionType) fun.returnType()).kind) + { + case LIST: + return Lists.Value.fromSerialized(result, (ListType) fun.returnType(), version); + case SET: + return Sets.Value.fromSerialized(result, (SetType) fun.returnType(), version); + case MAP: + return Maps.Value.fromSerialized(result, (MapType) fun.returnType(), version); + } + } + else if (fun.returnType().isUDT()) { - case LIST: return Lists.Value.fromSerialized(result, (ListType)fun.returnType(), version); - case SET: return Sets.Value.fromSerialized(result, (SetType)fun.returnType(), version); - case MAP: return Maps.Value.fromSerialized(result, (MapType)fun.returnType(), version); + return UserTypes.Value.fromSerialized(result, (UserType) fun.returnType()); } - throw new AssertionError(); + + return new Constants.Value(result); } public static class Raw extends Term.Raw http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java b/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java index ade69dd..a38cc80 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java @@ -223,7 +223,7 @@ public class UDAggregate extends AbstractFunction implements AggregateFunction && Functions.typesMatch(returnType, that.returnType) && Objects.equal(stateFunction, that.stateFunction) && Objects.equal(finalFunction, that.finalFunction) - && Objects.equal(stateType, that.stateType) + && ((stateType == that.stateType) || ((stateType != null) && stateType.equals(that.stateType, true))) // ignore freezing && Objects.equal(initcond, that.initcond); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java index b00214c..ba1e7c0 100644 --- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java +++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java @@ -124,7 +124,7 @@ public final class StatementRestrictions WhereClause whereClause, VariableSpecifications boundNames, boolean selectsOnlyStaticColumns, - boolean selectACollection, + boolean selectsComplexColumn, boolean useFiltering, boolean forView) throws InvalidRequestException { @@ -218,7 +218,7 @@ public final class StatementRestrictions throw invalidRequest("Cannot restrict clustering columns when selecting only static columns"); } - processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectACollection, forView); + processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectsComplexColumn, forView); // Covers indexes on the first clustering column (among others). if (isKeyRange && hasQueriableClusteringColumnIndex) @@ -447,11 +447,11 @@ public final class StatementRestrictions * @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise * @param selectsOnlyStaticColumns <code>true</code> if the selected or modified columns are all statics, * <code>false</code> otherwise. - * @param selectACollection <code>true</code> if the query should return a collection column + * @param selectsComplexColumn <code>true</code> if the query should return a collection column */ private void processClusteringColumnsRestrictions(boolean hasQueriableIndex, boolean selectsOnlyStaticColumns, - boolean selectACollection, + boolean selectsComplexColumn, boolean forView) throws InvalidRequestException { checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.isSlice(), @@ -466,7 +466,7 @@ public final class StatementRestrictions } else { - checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection, + checkFalse(clusteringColumnsRestrictions.isIN() && selectsComplexColumn, "Cannot restrict clustering columns by IN relations when a collection is selected by the query"); checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex, "Cannot restrict clustering columns by a CONTAINS relation without a secondary index"); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/selection/Selectable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selectable.java b/src/java/org/apache/cassandra/cql3/selection/Selectable.java index c3e0331..faf3f2d 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selectable.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selectable.java @@ -277,23 +277,23 @@ public abstract class Selectable { Selector.Factory factory = selected.newSelectorFactory(cfm, defs); AbstractType<?> type = factory.newInstance().getType(); - if (!(type instanceof UserType)) + if (!type.isUDT()) + { throw new InvalidRequestException( String.format("Invalid field selection: %s of type %s is not a user type", - selected, - type.asCQL3Type())); + selected, + type.asCQL3Type())); + } UserType ut = (UserType) type; - for (int i = 0; i < ut.size(); i++) + int fieldIndex = ((UserType) type).fieldPosition(field); + if (fieldIndex == -1) { - if (!ut.fieldName(i).equals(field.bytes)) - continue; - return FieldSelector.newFactory(ut, i, factory); + throw new InvalidRequestException(String.format("%s of type %s has no field %s", + selected, type.asCQL3Type(), field)); } - throw new InvalidRequestException(String.format("%s of type %s has no field %s", - selected, - type.asCQL3Type(), - field)); + + return FieldSelector.newFactory(ut, fieldIndex, factory); } public static class Raw implements Selectable.Raw http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/selection/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selection.java b/src/java/org/apache/cassandra/cql3/selection/Selection.java index 4518f02..e0e1bd8 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selection.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selection.java @@ -112,14 +112,14 @@ public abstract class Selection } /** - * Checks if this selection contains a collection. + * Checks if this selection contains a complex column. * - * @return <code>true</code> if this selection contains a collection, <code>false</code> otherwise. + * @return <code>true</code> if this selection contains a multicell collection or UDT, <code>false</code> otherwise. */ - public boolean containsACollection() + public boolean containsAComplexColumn() { for (ColumnDefinition def : getColumns()) - if (def.type.isCollection() && def.type.isMultiCell()) + if (def.isComplex()) return true; return false; http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/selection/Selector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selector.java b/src/java/org/apache/cassandra/cql3/selection/Selector.java index 4515cd0..4c7e885 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selector.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selector.java @@ -190,7 +190,7 @@ public abstract class Selector implements AssignmentTestable // We should ignore the fact that the output type is frozen in our comparison as functions do not support // frozen types for arguments AbstractType<?> receiverType = receiver.type; - if (getType().isFrozenCollection()) + if (getType().isFreezable() && !getType().isMultiCell()) receiverType = receiverType.freeze(); if (getType().isReversed()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java index 8e51c26..5a8f3b6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java @@ -166,11 +166,11 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement // If it's directly the type we've updated, then just use the new one. if (keyspace.equals(ut.keyspace) && toReplace.equals(ut.name)) - return updated; + return type.isMultiCell() ? updated : updated.freeze(); // Otherwise, check for nesting List<AbstractType<?>> updatedTypes = updateTypes(ut.fieldTypes(), keyspace, toReplace, updated); - return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames()), updatedTypes); + return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames()), updatedTypes, type.isMultiCell()); } else if (type instanceof TupleType) { @@ -275,7 +275,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement newTypes.addAll(toUpdate.fieldTypes()); newTypes.add(addType); - return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); + return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell()); } private UserType doAlter(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException @@ -294,7 +294,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes()); newTypes.set(idx, type.prepare(keyspace()).getType()); - return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); + return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell()); } protected UserType makeUpdatedType(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException @@ -330,7 +330,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement newNames.set(idx, to.bytes); } - UserType updated = new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); + UserType updated = new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell()); CreateTypeStatement.checkForDuplicateNames(updated); return updated; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java index debb200..8357c02 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java @@ -46,7 +46,7 @@ public class CreateTableStatement extends SchemaAlteringStatement private List<AbstractType<?>> keyTypes; private List<AbstractType<?>> clusteringTypes; - private final Map<ByteBuffer, CollectionType> collections = new HashMap<>(); + private final Map<ByteBuffer, AbstractType> multicellColumns = new HashMap<>(); private final List<ColumnIdentifier> keyAliases = new ArrayList<>(); private final List<ColumnIdentifier> columnAliases = new ArrayList<>(); @@ -223,10 +223,24 @@ public class CreateTableStatement extends SchemaAlteringStatement { ColumnIdentifier id = entry.getKey(); CQL3Type pt = entry.getValue().prepare(keyspace(), udts); - if (pt.isCollection() && ((CollectionType)pt.getType()).isMultiCell()) - stmt.collections.put(id.bytes, (CollectionType)pt.getType()); + if (pt.getType().isMultiCell()) + stmt.multicellColumns.put(id.bytes, pt.getType()); if (entry.getValue().isCounter()) stmt.hasCounters = true; + + // check for non-frozen UDTs or collections in a non-frozen UDT + if (pt.getType().isUDT() && pt.getType().isMultiCell()) + { + for (AbstractType<?> innerType : ((UserType) pt.getType()).fieldTypes()) + { + if (innerType.isMultiCell()) + { + assert innerType.isCollection(); // shouldn't get this far with a nested non-frozen UDT + throw new InvalidRequestException("Non-frozen UDTs with nested non-frozen collections are not supported"); + } + } + } + stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below } @@ -285,8 +299,8 @@ public class CreateTableStatement extends SchemaAlteringStatement // For COMPACT STORAGE, we reject any "feature" that we wouldn't be able to translate back to thrift. if (useCompactStorage) { - if (!stmt.collections.isEmpty()) - throw new InvalidRequestException("Non-frozen collection types are not supported with COMPACT STORAGE"); + if (!stmt.multicellColumns.isEmpty()) + throw new InvalidRequestException("Non-frozen collections and UDTs are not supported with COMPACT STORAGE"); if (!staticColumns.isEmpty()) throw new InvalidRequestException("Static columns are not supported in COMPACT STORAGE tables"); @@ -350,8 +364,13 @@ 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.isCollection() && type.isMultiCell()) - throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t)); + if (type.isMultiCell()) + { + if (type.isCollection()) + throw new InvalidRequestException(String.format("Invalid non-frozen collection type for PRIMARY KEY component %s", t)); + else + throw new InvalidRequestException(String.format("Invalid non-frozen user-defined type for PRIMARY KEY component %s", t)); + } columns.remove(t); Boolean isReversed = properties.definedOrdering.get(t); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java index f62b9ea..e134594 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java @@ -74,8 +74,12 @@ public class CreateTypeStatement extends SchemaAlteringStatement throw new InvalidRequestException(String.format("A user type of name %s already exists", name)); for (CQL3Type.Raw type : columnTypes) + { if (type.isCounter()) throw new InvalidRequestException("A user type cannot contain counters"); + if (type.isUDT() && !type.isFrozen()) + throw new InvalidRequestException("A user type cannot contain non-frozen UDTs"); + } } public static void checkForDuplicateNames(UserType type) throws InvalidRequestException @@ -109,7 +113,7 @@ public class CreateTypeStatement extends SchemaAlteringStatement for (CQL3Type.Raw type : columnTypes) types.add(type.prepare(keyspace()).getType()); - return new UserType(name.getKeyspace(), name.getUserTypeName(), names, types); + return new UserType(name.getKeyspace(), name.getUserTypeName(), names, types, true); } public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java index daeecfe..accbe0c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DeleteStatement.java @@ -147,7 +147,7 @@ public class DeleteStatement extends ModificationStatement // list. However, we support having the value name for coherence with the static/sparse case checkFalse(def.isPrimaryKeyColumn(), "Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", def.name); - Operation op = deletion.prepare(cfm.ksName, def); + Operation op = deletion.prepare(cfm.ksName, def, cfm); op.collectMarkerSpecification(boundNames); operations.add(op); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index 06ff5d4..7243daa 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -831,7 +831,7 @@ public abstract class ModificationStatement implements CQLStatement ColumnDefinition def = metadata.getColumnDefinition(id); checkNotNull(metadata.getColumnDefinition(id), "Unknown identifier %s in IF conditions", id); - ColumnCondition condition = entry.right.prepare(keyspace(), def); + ColumnCondition condition = entry.right.prepare(keyspace(), def, metadata); condition.collectMarkerSpecification(boundNames); checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY column '%s' cannot have IF conditions", id); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index b164e61..9895d67 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -39,6 +39,7 @@ import org.apache.cassandra.db.filter.*; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.db.marshal.CompositeType; import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.db.rows.ComplexColumnData; import org.apache.cassandra.db.rows.Row; @@ -759,13 +760,14 @@ public class SelectStatement implements CQLStatement { if (def.isComplex()) { - // Collections are the only complex types we have so far - assert def.type.isCollection() && def.type.isMultiCell(); + assert def.type.isMultiCell(); ComplexColumnData complexData = row.getComplexColumnData(def); if (complexData == null) - result.add((ByteBuffer)null); + result.add(null); + else if (def.type.isCollection()) + result.add(((CollectionType) def.type).serializeForNativeProtocol(complexData.iterator(), protocolVersion)); else - result.add(((CollectionType)def.type).serializeForNativeProtocol(def, complexData.iterator(), protocolVersion)); + result.add(((UserType) def.type).serializeForNativeProtocol(complexData.iterator(), protocolVersion)); } else { @@ -883,7 +885,7 @@ public class SelectStatement implements CQLStatement whereClause, boundNames, selection.containsOnlyStaticColumns(), - selection.containsACollection(), + selection.containsAComplexColumn(), parameters.allowFiltering, forView); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java index 6f872d4..fcc0ca6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java @@ -170,7 +170,7 @@ public class UpdateStatement extends ModificationStatement } else { - Operation operation = new Operation.SetValue(value).prepare(keyspace(), def); + Operation operation = new Operation.SetValue(value).prepare(cfm, def); operation.collectMarkerSpecification(boundNames); operations.add(operation); } @@ -239,7 +239,7 @@ public class UpdateStatement extends ModificationStatement } else { - Operation operation = new Operation.SetValue(raw).prepare(keyspace(), def); + Operation operation = new Operation.SetValue(raw).prepare(cfm, def); operation.collectMarkerSpecification(boundNames); operations.add(operation); } @@ -308,7 +308,7 @@ public class UpdateStatement extends ModificationStatement checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY part %s found in SET part", def.name); - Operation operation = entry.right.prepare(keyspace(), def); + Operation operation = entry.right.prepare(cfm, def); operation.collectMarkerSpecification(boundNames); operations.add(operation); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/marshal/AbstractType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java index 331f1a4..1b94a74 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java @@ -312,11 +312,21 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer> return false; } + public boolean isUDT() + { + return false; + } + public boolean isMultiCell() { return false; } + public boolean isFreezable() + { + return false; + } + public AbstractType<?> freeze() { return this; @@ -418,6 +428,17 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer> return getClass().getName(); } + /** + * Checks to see if two types are equal when ignoring or not ignoring differences in being frozen, depending on + * the value of the ignoreFreezing parameter. + * @param other type to compare + * @param ignoreFreezing if true, differences in the types being frozen will be ignored + */ + public boolean equals(Object other, boolean ignoreFreezing) + { + return this.equals(other); + } + public void checkComparable() { switch (comparisonType) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/marshal/CollectionType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java b/src/java/org/apache/cassandra/db/marshal/CollectionType.java index d65e3a6..2f5cbb6 100644 --- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java +++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.util.List; import java.util.Iterator; -import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.Lists; @@ -133,13 +132,19 @@ public abstract class CollectionType<T> extends AbstractType<T> return kind == Kind.MAP; } + @Override + public boolean isFreezable() + { + return true; + } + // Overrided by maps protected int collectionSize(List<ByteBuffer> values) { return values.size(); } - public ByteBuffer serializeForNativeProtocol(ColumnDefinition def, Iterator<Cell> cells, int version) + public ByteBuffer serializeForNativeProtocol(Iterator<Cell> cells, int version) { assert isMultiCell(); List<ByteBuffer> values = serializedValues(cells); @@ -204,6 +209,27 @@ public abstract class CollectionType<T> extends AbstractType<T> } @Override + public boolean equals(Object o, boolean ignoreFreezing) + { + if (this == o) + return true; + + if (!(o instanceof CollectionType)) + return false; + + CollectionType other = (CollectionType)o; + + if (kind != other.kind) + return false; + + if (!ignoreFreezing && isMultiCell() != other.isMultiCell()) + return false; + + return nameComparator().equals(other.nameComparator(), ignoreFreezing) && + valueComparator().equals(other.valueComparator(), ignoreFreezing); + } + + @Override public String toString() { return this.toString(false); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/marshal/TupleType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/TupleType.java b/src/java/org/apache/cassandra/db/marshal/TupleType.java index 43cf52c..eaf6653 100644 --- a/src/java/org/apache/cassandra/db/marshal/TupleType.java +++ b/src/java/org/apache/cassandra/db/marshal/TupleType.java @@ -23,11 +23,13 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.google.common.base.Objects; import org.apache.cassandra.cql3.*; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.serializers.*; import org.apache.cassandra.utils.ByteBufferUtil; @@ -51,10 +53,16 @@ public class TupleType extends AbstractType<ByteBuffer> public TupleType(List<AbstractType<?>> types) { + this(types, true); + } + + protected TupleType(List<AbstractType<?>> types, boolean freezeInner) + { super(ComparisonType.CUSTOM); - for (int i = 0; i < types.size(); i++) - types.set(i, types.get(i).freeze()); - this.types = types; + if (freezeInner) + this.types = types.stream().map(AbstractType::freeze).collect(Collectors.toList()); + else + this.types = types; } public static TupleType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException @@ -118,11 +126,22 @@ public class TupleType extends AbstractType<ByteBuffer> return cmp; } - if (bb1.remaining() == 0) - return bb2.remaining() == 0 ? 0 : -1; + // handle trailing nulls + while (bb1.remaining() > 0) + { + int size = bb1.getInt(); + if (size > 0) // non-null + return 1; + } + + while (bb2.remaining() > 0) + { + int size = bb2.getInt(); + if (size > 0) // non-null + return -1; + } - // bb1.remaining() > 0 && bb2.remaining() == 0 - return 1; + return 0; } @Override @@ -171,6 +190,15 @@ public class TupleType extends AbstractType<ByteBuffer> int size = input.getInt(); components[i] = size < 0 ? null : ByteBufferUtil.readBytes(input, size); } + + // error out if we got more values in the tuple/UDT than we expected + if (input.hasRemaining()) + { + throw new InvalidRequestException(String.format( + "Expected %s %s for %s column, but got more", + size(), size() == 1 ? "value" : "values", this.asCQL3Type())); + } + return components; } @@ -200,6 +228,9 @@ public class TupleType extends AbstractType<ByteBuffer> @Override public String getString(ByteBuffer value) { + if (value == null) + return "null"; + StringBuilder sb = new StringBuilder(); ByteBuffer input = value.duplicate(); for (int i = 0; i < size(); i++) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/marshal/TypeParser.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/TypeParser.java b/src/java/org/apache/cassandra/db/marshal/TypeParser.java index 35d15ab..ba8ad13 100644 --- a/src/java/org/apache/cassandra/db/marshal/TypeParser.java +++ b/src/java/org/apache/cassandra/db/marshal/TypeParser.java @@ -538,7 +538,8 @@ public class TypeParser return sb.toString(); } - public static String stringifyUserTypeParameters(String keysace, ByteBuffer typeName, List<ByteBuffer> columnNames, List<AbstractType<?>> columnTypes) + public static String stringifyUserTypeParameters(String keysace, ByteBuffer typeName, List<ByteBuffer> columnNames, + List<AbstractType<?>> columnTypes, boolean ignoreFreezing) { StringBuilder sb = new StringBuilder(); sb.append('(').append(keysace).append(",").append(ByteBufferUtil.bytesToHex(typeName)); @@ -547,8 +548,7 @@ public class TypeParser { sb.append(','); sb.append(ByteBufferUtil.bytesToHex(columnNames.get(i))).append(":"); - // omit FrozenType(...) from fields because it is currently implicit - sb.append(columnTypes.get(i).toString(true)); + sb.append(columnTypes.get(i).toString(ignoreFreezing)); } sb.append(')'); return sb.toString(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/marshal/UserType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java index e766190..72ed895 100644 --- a/src/java/org/apache/cassandra/db/marshal/UserType.java +++ b/src/java/org/apache/cassandra/db/marshal/UserType.java @@ -25,11 +25,15 @@ import java.util.*; import com.google.common.base.Objects; import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.rows.Cell; +import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.serializers.*; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A user defined type. @@ -38,19 +42,24 @@ import org.apache.cassandra.utils.Pair; */ public class UserType extends TupleType { + private static final Logger logger = LoggerFactory.getLogger(UserType.class); + public final String keyspace; public final ByteBuffer name; private final List<ByteBuffer> fieldNames; private final List<String> stringFieldNames; + private final boolean isMultiCell; - public UserType(String keyspace, ByteBuffer name, List<ByteBuffer> fieldNames, List<AbstractType<?>> fieldTypes) + public UserType(String keyspace, ByteBuffer name, List<ByteBuffer> fieldNames, List<AbstractType<?>> fieldTypes, boolean isMultiCell) { - super(fieldTypes); + super(fieldTypes, false); assert fieldNames.size() == fieldTypes.size(); this.keyspace = keyspace; this.name = name; this.fieldNames = fieldNames; this.stringFieldNames = new ArrayList<>(fieldNames.size()); + this.isMultiCell = isMultiCell; + for (ByteBuffer fieldName : fieldNames) { try @@ -74,9 +83,28 @@ public class UserType extends TupleType for (Pair<ByteBuffer, AbstractType> p : params.right) { columnNames.add(p.left); - columnTypes.add(p.right.freeze()); + columnTypes.add(p.right); } - return new UserType(keyspace, name, columnNames, columnTypes); + + return new UserType(keyspace, name, columnNames, columnTypes, true); + } + + @Override + public boolean isUDT() + { + return true; + } + + @Override + public boolean isMultiCell() + { + return isMultiCell; + } + + @Override + public boolean isFreezable() + { + return true; } public AbstractType<?> fieldType(int i) @@ -109,6 +137,55 @@ public class UserType extends TupleType return UTF8Type.instance.compose(name); } + public short fieldPosition(ColumnIdentifier field) + { + return fieldPosition(field.bytes); + } + + public short fieldPosition(ByteBuffer fieldName) + { + for (short i = 0; i < fieldNames.size(); i++) + if (fieldName.equals(fieldNames.get(i))) + return i; + return -1; + } + + public CellPath cellPathForField(ByteBuffer fieldName) + { + // we use the field position instead of the field name to allow for field renaming in ALTER TYPE statements + return CellPath.create(ByteBufferUtil.bytes(fieldPosition(fieldName))); + } + + public ShortType nameComparator() + { + return ShortType.instance; + } + + public ByteBuffer serializeForNativeProtocol(Iterator<Cell> cells, int protocolVersion) + { + assert isMultiCell; + + ByteBuffer[] components = new ByteBuffer[size()]; + short fieldPosition = 0; + while (cells.hasNext()) + { + Cell cell = cells.next(); + + // handle null fields that aren't at the end + short fieldPositionOfCell = ByteBufferUtil.toShort(cell.path().get(0)); + while (fieldPosition < fieldPositionOfCell) + components[fieldPosition++] = null; + + components[fieldPosition++] = cell.value(); + } + + // append trailing nulls for missing cells + while (fieldPosition < size()) + components[fieldPosition++] = null; + + return TupleType.buildValue(components); + } + // Note: the only reason we override this is to provide nicer error message, but since that's not that much code... @Override public void validate(ByteBuffer bytes) throws MarshalException @@ -217,19 +294,79 @@ public class UserType extends TupleType } @Override + public UserType freeze() + { + if (isMultiCell) + return new UserType(keyspace, name, fieldNames, fieldTypes(), false); + else + return this; + } + + @Override public int hashCode() { - return Objects.hashCode(keyspace, name, fieldNames, types); + return Objects.hashCode(keyspace, name, fieldNames, types, isMultiCell); + } + + @Override + public boolean isValueCompatibleWith(AbstractType<?> previous) + { + if (this == previous) + return true; + + if (!(previous instanceof UserType)) + return false; + + UserType other = (UserType) previous; + if (isMultiCell != other.isMultiCell()) + return false; + + if (!keyspace.equals(other.keyspace)) + return false; + + Iterator<AbstractType<?>> thisTypeIter = types.iterator(); + Iterator<AbstractType<?>> previousTypeIter = other.types.iterator(); + while (thisTypeIter.hasNext() && previousTypeIter.hasNext()) + { + if (!thisTypeIter.next().isCompatibleWith(previousTypeIter.next())) + return false; + } + + // it's okay for the new type to have additional fields, but not for the old type to have additional fields + return !previousTypeIter.hasNext(); } @Override public boolean equals(Object o) { + return o instanceof UserType && equals(o, false); + } + + @Override + public boolean equals(Object o, boolean ignoreFreezing) + { if(!(o instanceof UserType)) return false; UserType that = (UserType)o; - return keyspace.equals(that.keyspace) && name.equals(that.name) && fieldNames.equals(that.fieldNames) && types.equals(that.types); + + if (!keyspace.equals(that.keyspace) || !name.equals(that.name) || !fieldNames.equals(that.fieldNames)) + return false; + + if (!ignoreFreezing && isMultiCell != that.isMultiCell) + return false; + + if (this.types.size() != that.types.size()) + return false; + + Iterator<AbstractType<?>> otherTypeIter = that.types.iterator(); + for (AbstractType<?> type : types) + { + if (!type.equals(otherTypeIter.next(), ignoreFreezing)) + return false; + } + + return true; } @Override @@ -248,6 +385,21 @@ public class UserType extends TupleType @Override public String toString() { - return getClass().getName() + TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, types); + return this.toString(false); + } + + @Override + public String toString(boolean ignoreFreezing) + { + boolean includeFrozenType = !ignoreFreezing && !isMultiCell(); + + StringBuilder sb = new StringBuilder(); + if (includeFrozenType) + sb.append(FrozenType.class.getName()).append("("); + sb.append(getClass().getName()); + sb.append(TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, types, ignoreFreezing || !isMultiCell)); + if (includeFrozenType) + sb.append(")"); + return sb.toString(); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/rows/CellPath.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/rows/CellPath.java b/src/java/org/apache/cassandra/db/rows/CellPath.java index 68e3c2b..e2b362c 100644 --- a/src/java/org/apache/cassandra/db/rows/CellPath.java +++ b/src/java/org/apache/cassandra/db/rows/CellPath.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import java.security.MessageDigest; import java.util.Objects; +import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.utils.ByteBufferUtil; @@ -39,11 +40,11 @@ public abstract class CellPath public abstract int size(); public abstract ByteBuffer get(int i); - // The only complex we currently have are collections that have only one value. + // The only complex paths we currently have are collections and UDTs, which both have a depth of one public static CellPath create(ByteBuffer value) { assert value != null; - return new CollectionCellPath(value); + return new SingleItemCellPath(value); } public int dataSize() @@ -98,13 +99,13 @@ public abstract class CellPath public void skip(DataInputPlus in) throws IOException; } - private static class CollectionCellPath extends CellPath + private static class SingleItemCellPath extends CellPath { - private static final long EMPTY_SIZE = ObjectSizes.measure(new CollectionCellPath(ByteBufferUtil.EMPTY_BYTE_BUFFER)); + private static final long EMPTY_SIZE = ObjectSizes.measure(new SingleItemCellPath(ByteBufferUtil.EMPTY_BYTE_BUFFER)); protected final ByteBuffer value; - private CollectionCellPath(ByteBuffer value) + private SingleItemCellPath(ByteBuffer value) { this.value = value; } @@ -122,7 +123,7 @@ public abstract class CellPath public CellPath copy(AbstractAllocator allocator) { - return new CollectionCellPath(allocator.clone(value)); + return new SingleItemCellPath(allocator.clone(value)); } public long unsharedHeapSizeExcludingData() http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java b/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java index ac137e7..f4678b7 100644 --- a/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java +++ b/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java @@ -251,7 +251,6 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell> public void addCell(Cell cell) { - assert cell.column().equals(column); builder.add(cell); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/schema/Functions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/schema/Functions.java b/src/java/org/apache/cassandra/schema/Functions.java index c65f58d..a936d81 100644 --- a/src/java/org/apache/cassandra/schema/Functions.java +++ b/src/java/org/apache/cassandra/schema/Functions.java @@ -131,7 +131,7 @@ public final class Functions implements Iterable<Function> */ public static boolean typesMatch(AbstractType<?> t1, AbstractType<?> t2) { - return t1.asCQL3Type().toString().equals(t2.asCQL3Type().toString()); + return t1.freeze().asCQL3Type().toString().equals(t2.freeze().asCQL3Type().toString()); } public static boolean typesMatch(List<AbstractType<?>> t1, List<AbstractType<?>> t2) http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/schema/LegacySchemaMigrator.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/schema/LegacySchemaMigrator.java b/src/java/org/apache/cassandra/schema/LegacySchemaMigrator.java index 212676d..9d277d6 100644 --- a/src/java/org/apache/cassandra/schema/LegacySchemaMigrator.java +++ b/src/java/org/apache/cassandra/schema/LegacySchemaMigrator.java @@ -827,7 +827,7 @@ public final class LegacySchemaMigrator .map(LegacySchemaMigrator::parseType) .collect(Collectors.toList()); - return new UserType(keyspaceName, bytes(typeName), names, types); + return new UserType(keyspaceName, bytes(typeName), names, types, true); } /* http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/schema/Types.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/schema/Types.java b/src/java/org/apache/cassandra/schema/Types.java index 1b71364..00801b5 100644 --- a/src/java/org/apache/cassandra/schema/Types.java +++ b/src/java/org/apache/cassandra/schema/Types.java @@ -22,11 +22,7 @@ import java.util.*; import javax.annotation.Nullable; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.MapDifference; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; +import com.google.common.collect.*; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.db.marshal.AbstractType; @@ -139,7 +135,30 @@ public final class Types implements Iterable<UserType> @Override public boolean equals(Object o) { - return this == o || (o instanceof Types && types.equals(((Types) o).types)); + if (this == o) + return true; + + if (!(o instanceof Types)) + return false; + + Types other = (Types) o; + + if (types.size() != other.types.size()) + return false; + + Iterator<Map.Entry<ByteBuffer, UserType>> thisIter = this.types.entrySet().iterator(); + Iterator<Map.Entry<ByteBuffer, UserType>> otherIter = other.types.entrySet().iterator(); + while (thisIter.hasNext()) + { + Map.Entry<ByteBuffer, UserType> thisNext = thisIter.next(); + Map.Entry<ByteBuffer, UserType> otherNext = otherIter.next(); + if (!thisNext.getKey().equals(otherNext.getKey())) + return false; + + if (!thisNext.getValue().equals(otherNext.getValue(), true)) // ignore freezing + return false; + } + return true; } @Override @@ -156,7 +175,7 @@ public final class Types implements Iterable<UserType> public static final class Builder { - final ImmutableMap.Builder<ByteBuffer, UserType> types = ImmutableMap.builder(); + final ImmutableSortedMap.Builder<ByteBuffer, UserType> types = ImmutableSortedMap.naturalOrder(); private Builder() { @@ -169,6 +188,7 @@ public final class Types implements Iterable<UserType> public Builder add(UserType type) { + assert type.isMultiCell(); types.put(type.name, type); return this; } @@ -293,7 +313,7 @@ public final class Types implements Iterable<UserType> .map(t -> t.prepareInternal(keyspace, types).getType()) .collect(toList()); - return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes); + return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes, true); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/service/MigrationManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/MigrationManager.java b/src/java/org/apache/cassandra/service/MigrationManager.java index ba8a5fb..571748b 100644 --- a/src/java/org/apache/cassandra/service/MigrationManager.java +++ b/src/java/org/apache/cassandra/service/MigrationManager.java @@ -431,6 +431,7 @@ public class MigrationManager public static void announceTypeUpdate(UserType updatedType, boolean announceLocally) { + logger.info(String.format("Update type '%s.%s' to %s", updatedType.keyspace, updatedType.getNameAsString(), updatedType)); announceNewType(updatedType, announceLocally); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/src/java/org/apache/cassandra/transport/DataType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/transport/DataType.java b/src/java/org/apache/cassandra/transport/DataType.java index e3eaf32..acaa9a3 100644 --- a/src/java/org/apache/cassandra/transport/DataType.java +++ b/src/java/org/apache/cassandra/transport/DataType.java @@ -116,7 +116,7 @@ public enum DataType implements OptionCodec.Codecable<DataType> fieldNames.add(UTF8Type.instance.decompose(CBUtil.readString(cb))); fieldTypes.add(DataType.toType(codec.decodeOne(cb, version))); } - return new UserType(ks, name, fieldNames, fieldTypes); + return new UserType(ks, name, fieldNames, fieldTypes, true); case TUPLE: n = cb.readUnsignedShort(); List<AbstractType<?>> types = new ArrayList<>(n); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java b/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java index 02ed1a8..73e0fca 100644 --- a/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java +++ b/test/unit/org/apache/cassandra/cql3/CQL3TypeLiteralTest.java @@ -636,7 +636,7 @@ public class CQL3TypeLiteralTest names.add(UTF8Type.instance.fromString('f' + randLetters(i))); types.add(randomNestedType(level)); } - return new UserType("ks", UTF8Type.instance.fromString("u" + randInt(1000000)), names, types); + return new UserType("ks", UTF8Type.instance.fromString("u" + randInt(1000000)), names, types, true); } // http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/test/unit/org/apache/cassandra/cql3/CQLTester.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index 8334321..1b36c03 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -139,6 +139,11 @@ public abstract class CQLTester private boolean usePrepared = USE_PREPARED_VALUES; private static boolean reusePrepared = REUSE_PREPARED; + protected boolean usePrepared() + { + return usePrepared; + } + public static void prepareServer() { if (isServerPrepared) @@ -1117,6 +1122,24 @@ public abstract class CQLTester e.getMessage().contains(text)); } + @FunctionalInterface + public interface CheckedFunction { + void apply() throws Throwable; + } + + /** + * Runs the given function before and after a flush of sstables. This is useful for checking that behavior is + * the same whether data is in memtables or sstables. + * @param runnable + * @throws Throwable + */ + public void beforeAndAfterFlush(CheckedFunction runnable) throws Throwable + { + runnable.apply(); + flush(); + runnable.apply(); + } + private static String replaceValues(String query, Object[] values) { StringBuilder sb = new StringBuilder(); @@ -1327,7 +1350,7 @@ public abstract class CQLTester if (value instanceof ByteBuffer) return (ByteBuffer)value; - return type.decompose(value); + return type.decompose(serializeTuples(value)); } private static String formatValue(ByteBuffer bb, AbstractType<?> type) @@ -1354,7 +1377,19 @@ public abstract class CQLTester protected Object userType(Object... values) { - return new TupleValue(values).toByteBuffer(); + if (values.length % 2 != 0) + throw new IllegalArgumentException("userType() requires an even number of arguments"); + + String[] fieldNames = new String[values.length / 2]; + Object[] fieldValues = new Object[values.length / 2]; + int fieldNum = 0; + for (int i = 0; i < values.length; i += 2) + { + fieldNames[fieldNum] = (String) values[i]; + fieldValues[fieldNum] = values[i + 1]; + fieldNum++; + } + return new UserTypeValue(fieldNames, fieldValues); } protected Object list(Object...values) @@ -1468,7 +1503,7 @@ public abstract class CQLTester private static class TupleValue { - private final Object[] values; + protected final Object[] values; TupleValue(Object[] values) { @@ -1502,4 +1537,43 @@ public abstract class CQLTester return "TupleValue" + toCQLString(); } } + + private static class UserTypeValue extends TupleValue + { + private final String[] fieldNames; + + UserTypeValue(String[] fieldNames, Object[] fieldValues) + { + super(fieldValues); + this.fieldNames = fieldNames; + } + + @Override + public String toCQLString() + { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + boolean haveEntry = false; + for (int i = 0; i < values.length; i++) + { + if (values[i] != null) + { + if (haveEntry) + sb.append(", "); + sb.append(ColumnIdentifier.maybeQuote(fieldNames[i])); + sb.append(": "); + sb.append(formatForCQL(values[i])); + haveEntry = true; + } + } + assert haveEntry; + sb.append("}"); + return sb.toString(); + } + + public String toString() + { + return "UserTypeValue" + toCQLString(); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java index 71524c5..989c524 100644 --- a/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java +++ b/test/unit/org/apache/cassandra/cql3/ColumnConditionTest.java @@ -186,7 +186,7 @@ public class ColumnConditionTest ColumnDefinition definition = ColumnDefinition.regularDef("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true)); // EQ - ColumnCondition condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.EQ); + ColumnCondition condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.EQ); ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(listAppliesTo(bound, list(ONE), list(ONE))); assertTrue(listAppliesTo(bound, list(), list())); @@ -202,7 +202,7 @@ public class ColumnConditionTest assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // NEQ - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.NEQ); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.NEQ); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(listAppliesTo(bound, list(ONE), list(ONE))); assertFalse(listAppliesTo(bound, list(), list())); @@ -218,7 +218,7 @@ public class ColumnConditionTest assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // LT - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LT); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LT); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(listAppliesTo(bound, list(ONE), list(ONE))); assertFalse(listAppliesTo(bound, list(), list())); @@ -234,7 +234,7 @@ public class ColumnConditionTest assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // LTE - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LTE); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LTE); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(listAppliesTo(bound, list(ONE), list(ONE))); assertTrue(listAppliesTo(bound, list(), list())); @@ -250,7 +250,7 @@ public class ColumnConditionTest assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // GT - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GT); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GT); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(listAppliesTo(bound, list(ONE), list(ONE))); assertFalse(listAppliesTo(bound, list(), list())); @@ -266,7 +266,7 @@ public class ColumnConditionTest assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // GTE - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GTE); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GTE); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(listAppliesTo(bound, list(ONE), list(ONE))); assertTrue(listAppliesTo(bound, list(), list())); @@ -315,7 +315,7 @@ public class ColumnConditionTest ColumnDefinition definition = ColumnDefinition.regularDef("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true)); // EQ - ColumnCondition condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Operator.EQ); + ColumnCondition condition = ColumnCondition.condition(definition, new Sets.Value(set(ONE)), Operator.EQ); ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(setAppliesTo(bound, set(ONE), list(ONE))); assertTrue(setAppliesTo(bound, set(), list())); @@ -331,7 +331,7 @@ public class ColumnConditionTest assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // NEQ - condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Operator.NEQ); + condition = ColumnCondition.condition(definition, new Sets.Value(set(ONE)), Operator.NEQ); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(setAppliesTo(bound, set(ONE), list(ONE))); assertFalse(setAppliesTo(bound, set(), list())); @@ -347,7 +347,7 @@ public class ColumnConditionTest assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // LT - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LT); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LT); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(setAppliesTo(bound, set(ONE), list(ONE))); assertFalse(setAppliesTo(bound, set(), list())); @@ -363,7 +363,7 @@ public class ColumnConditionTest assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // LTE - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LTE); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LTE); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(setAppliesTo(bound, set(ONE), list(ONE))); assertTrue(setAppliesTo(bound, set(), list())); @@ -379,7 +379,7 @@ public class ColumnConditionTest assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // GT - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GT); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GT); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(setAppliesTo(bound, set(ONE), list(ONE))); assertFalse(setAppliesTo(bound, set(), list())); @@ -395,7 +395,7 @@ public class ColumnConditionTest assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER))); // GTE - condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GTE); + condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GTE); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(setAppliesTo(bound, set(ONE), list(ONE))); assertTrue(setAppliesTo(bound, set(), list())); @@ -448,7 +448,7 @@ public class ColumnConditionTest Maps.Value placeholder = new Maps.Value(placeholderMap); // EQ - ColumnCondition condition = ColumnCondition.condition(definition, null, placeholder, Operator.EQ); + ColumnCondition condition = ColumnCondition.condition(definition, placeholder, Operator.EQ); ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); @@ -470,7 +470,7 @@ public class ColumnConditionTest assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); // NEQ - condition = ColumnCondition.condition(definition, null, placeholder, Operator.NEQ); + condition = ColumnCondition.condition(definition, placeholder, Operator.NEQ); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); @@ -492,7 +492,7 @@ public class ColumnConditionTest assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); // LT - condition = ColumnCondition.condition(definition, null, placeholder, Operator.LT); + condition = ColumnCondition.condition(definition, placeholder, Operator.LT); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); @@ -514,7 +514,7 @@ public class ColumnConditionTest assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); // LTE - condition = ColumnCondition.condition(definition, null, placeholder, Operator.LTE); + condition = ColumnCondition.condition(definition, placeholder, Operator.LTE); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); @@ -536,7 +536,7 @@ public class ColumnConditionTest assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); // GT - condition = ColumnCondition.condition(definition, null, placeholder, Operator.GT); + condition = ColumnCondition.condition(definition, placeholder, Operator.GT); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); @@ -558,7 +558,7 @@ public class ColumnConditionTest assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER))); // GTE - condition = ColumnCondition.condition(definition, null, placeholder, Operator.GTE); + condition = ColumnCondition.condition(definition, placeholder, Operator.GTE); bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT); assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE))); http://git-wip-us.apache.org/repos/asf/cassandra/blob/677230df/test/unit/org/apache/cassandra/cql3/selection/SelectionColumnMappingTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/selection/SelectionColumnMappingTest.java b/test/unit/org/apache/cassandra/cql3/selection/SelectionColumnMappingTest.java index e562aac..3c83da7 100644 --- a/test/unit/org/apache/cassandra/cql3/selection/SelectionColumnMappingTest.java +++ b/test/unit/org/apache/cassandra/cql3/selection/SelectionColumnMappingTest.java @@ -48,7 +48,7 @@ public class SelectionColumnMappingTest extends CQLTester " v1 int," + " v2 ascii," + " v3 frozen<" + typeName + ">)"); - userType = Schema.instance.getKSMetaData(KEYSPACE).types.get(ByteBufferUtil.bytes(typeName)).get(); + userType = Schema.instance.getKSMetaData(KEYSPACE).types.get(ByteBufferUtil.bytes(typeName)).get().freeze(); functionName = createFunction(KEYSPACE, "int, ascii", "CREATE FUNCTION %s (i int, a ascii) " + "CALLED ON NULL INPUT " +
