| /* |
| * 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.io.IOException; |
| import java.lang.reflect.Method; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.cassandra.cql3.CQL3Type; |
| import org.apache.cassandra.cql3.Term; |
| import org.apache.cassandra.db.TypeSizes; |
| import org.apache.cassandra.exceptions.SyntaxException; |
| import org.apache.cassandra.serializers.TypeSerializer; |
| import org.apache.cassandra.serializers.MarshalException; |
| |
| import org.apache.cassandra.utils.FastByteOperations; |
| import org.github.jamm.Unmetered; |
| import org.apache.cassandra.io.util.DataOutputPlus; |
| import org.apache.cassandra.io.util.DataInputPlus; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| |
| import static org.apache.cassandra.db.marshal.AbstractType.ComparisonType.CUSTOM; |
| |
| /** |
| * Specifies a Comparator for a specific type of ByteBuffer. |
| * |
| * Note that empty ByteBuffer are used to represent "start at the beginning" |
| * or "stop at the end" arguments to get_slice, so the Comparator |
| * should always handle those values even if they normally do not |
| * represent a valid ByteBuffer for the type being compared. |
| */ |
| @Unmetered |
| public abstract class AbstractType<T> implements Comparator<ByteBuffer> |
| { |
| private static final Logger logger = LoggerFactory.getLogger(AbstractType.class); |
| |
| public final Comparator<ByteBuffer> reverseComparator; |
| |
| public enum ComparisonType |
| { |
| /** |
| * This type should never be compared |
| */ |
| NOT_COMPARABLE, |
| /** |
| * This type is always compared by its sequence of unsigned bytes |
| */ |
| BYTE_ORDER, |
| /** |
| * This type can only be compared by calling the type's compareCustom() method, which may be expensive. |
| * Support for this may be removed in a major release of Cassandra, however upgrade facilities will be |
| * provided if and when this happens. |
| */ |
| CUSTOM |
| } |
| |
| public final ComparisonType comparisonType; |
| public final boolean isByteOrderComparable; |
| |
| protected AbstractType(ComparisonType comparisonType) |
| { |
| this.comparisonType = comparisonType; |
| this.isByteOrderComparable = comparisonType == ComparisonType.BYTE_ORDER; |
| reverseComparator = (o1, o2) -> AbstractType.this.compare(o2, o1); |
| try |
| { |
| Method custom = getClass().getMethod("compareCustom", ByteBuffer.class, ByteBuffer.class); |
| if ((custom.getDeclaringClass() == AbstractType.class) == (comparisonType == CUSTOM)) |
| throw new IllegalStateException((comparisonType == CUSTOM ? "compareCustom must be overridden if ComparisonType is CUSTOM" |
| : "compareCustom should not be overridden if ComparisonType is not CUSTOM") |
| + " (" + getClass().getSimpleName() + ")"); |
| } |
| catch (NoSuchMethodException e) |
| { |
| throw new IllegalStateException(); |
| } |
| } |
| |
| public static List<String> asCQLTypeStringList(List<AbstractType<?>> abstractTypes) |
| { |
| List<String> r = new ArrayList<>(abstractTypes.size()); |
| for (AbstractType<?> abstractType : abstractTypes) |
| r.add(abstractType.asCQL3Type().toString()); |
| return r; |
| } |
| |
| public T compose(ByteBuffer bytes) |
| { |
| return getSerializer().deserialize(bytes); |
| } |
| |
| public ByteBuffer decompose(T value) |
| { |
| return getSerializer().serialize(value); |
| } |
| |
| /** get a string representation of the bytes used for various identifier (NOT just for log messages) */ |
| public String getString(ByteBuffer bytes) |
| { |
| if (bytes == null) |
| return "null"; |
| |
| TypeSerializer<T> serializer = getSerializer(); |
| serializer.validate(bytes); |
| |
| return serializer.toString(serializer.deserialize(bytes)); |
| } |
| |
| /** get a byte representation of the given string. */ |
| public abstract ByteBuffer fromString(String source) throws MarshalException; |
| |
| /** Given a parsed JSON string, return a byte representation of the object. |
| * @param parsed the result of parsing a json string |
| **/ |
| public abstract Term fromJSONObject(Object parsed) throws MarshalException; |
| |
| /** Converts a value to a JSON string. */ |
| public String toJSONString(ByteBuffer buffer, int protocolVersion) |
| { |
| return '"' + getSerializer().deserialize(buffer).toString() + '"'; |
| } |
| |
| /* validate that the byte array is a valid sequence for the type we are supposed to be comparing */ |
| public void validate(ByteBuffer bytes) throws MarshalException |
| { |
| getSerializer().validate(bytes); |
| } |
| |
| public final int compare(ByteBuffer left, ByteBuffer right) |
| { |
| return isByteOrderComparable |
| ? FastByteOperations.compareUnsigned(left, right) |
| : compareCustom(left, right); |
| } |
| |
| /** |
| * Implement IFF ComparisonType is CUSTOM |
| * |
| * Compares the ByteBuffer representation of two instances of this class, |
| * for types where this cannot be done by simple in-order comparison of the |
| * unsigned bytes |
| * |
| * Standard Java compare semantics |
| */ |
| public int compareCustom(ByteBuffer left, ByteBuffer right) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Validate cell value. Unlike {@linkplain #validate(java.nio.ByteBuffer)}, |
| * cell value is passed to validate its content. |
| * Usually, this is the same as validate except collection. |
| * |
| * @param cellValue ByteBuffer representing cell value |
| * @throws MarshalException |
| */ |
| public void validateCellValue(ByteBuffer cellValue) throws MarshalException |
| { |
| validate(cellValue); |
| } |
| |
| /* Most of our internal type should override that. */ |
| public CQL3Type asCQL3Type() |
| { |
| return new CQL3Type.Custom(this); |
| } |
| |
| /** |
| * Same as compare except that this ignore ReversedType. This is to be use when |
| * comparing 2 values to decide for a CQL condition (see Operator.isSatisfiedBy) as |
| * for CQL, ReversedType is simply an "hint" to the storage engine but it does not |
| * change the meaning of queries per-se. |
| */ |
| public int compareForCQL(ByteBuffer v1, ByteBuffer v2) |
| { |
| return compare(v1, v2); |
| } |
| |
| public abstract TypeSerializer<T> getSerializer(); |
| |
| /* convenience method */ |
| public String getString(Collection<ByteBuffer> names) |
| { |
| StringBuilder builder = new StringBuilder(); |
| for (ByteBuffer name : names) |
| { |
| builder.append(getString(name)).append(","); |
| } |
| return builder.toString(); |
| } |
| |
| public boolean isCounter() |
| { |
| return false; |
| } |
| |
| public boolean isFrozenCollection() |
| { |
| return isCollection() && !isMultiCell(); |
| } |
| |
| public boolean isReversed() |
| { |
| return false; |
| } |
| |
| public static AbstractType<?> parseDefaultParameters(AbstractType<?> baseType, TypeParser parser) throws SyntaxException |
| { |
| Map<String, String> parameters = parser.getKeyValueParameters(); |
| String reversed = parameters.get("reversed"); |
| if (reversed != null && (reversed.isEmpty() || reversed.equals("true"))) |
| { |
| return ReversedType.getInstance(baseType); |
| } |
| else |
| { |
| return baseType; |
| } |
| } |
| |
| /** |
| * Returns true if this comparator is compatible with the provided |
| * previous comparator, that is if previous can safely be replaced by this. |
| * A comparator cn should be compatible with a previous one cp if forall columns c1 and c2, |
| * if cn.validate(c1) and cn.validate(c2) and cn.compare(c1, c2) == v, |
| * then cp.validate(c1) and cp.validate(c2) and cp.compare(c1, c2) == v. |
| * |
| * Note that a type should be compatible with at least itself and when in |
| * doubt, keep the default behavior of not being compatible with any other comparator! |
| */ |
| public boolean isCompatibleWith(AbstractType<?> previous) |
| { |
| return this.equals(previous); |
| } |
| |
| /** |
| * Returns true if values of the other AbstractType can be read and "reasonably" interpreted by the this |
| * AbstractType. Note that this is a weaker version of isCompatibleWith, as it does not require that both type |
| * compare values the same way. |
| * |
| * The restriction on the other type being "reasonably" interpreted is to prevent, for example, IntegerType from |
| * being compatible with all other types. Even though any byte string is a valid IntegerType value, it doesn't |
| * necessarily make sense to interpret a UUID or a UTF8 string as an integer. |
| * |
| * Note that a type should be compatible with at least itself. |
| */ |
| public boolean isValueCompatibleWith(AbstractType<?> otherType) |
| { |
| return isValueCompatibleWithInternal((otherType instanceof ReversedType) ? ((ReversedType) otherType).baseType : otherType); |
| } |
| |
| /** |
| * Needed to handle ReversedType in value-compatibility checks. Subclasses should implement this instead of |
| * isValueCompatibleWith(). |
| */ |
| protected boolean isValueCompatibleWithInternal(AbstractType<?> otherType) |
| { |
| return isCompatibleWith(otherType); |
| } |
| |
| /** |
| * An alternative comparison function used by CollectionsType in conjunction with CompositeType. |
| * |
| * This comparator is only called to compare components of a CompositeType. It gets the value of the |
| * previous component as argument (or null if it's the first component of the composite). |
| * |
| * Unless you're doing something very similar to CollectionsType, you shouldn't override this. |
| */ |
| public int compareCollectionMembers(ByteBuffer v1, ByteBuffer v2, ByteBuffer collectionName) |
| { |
| return compare(v1, v2); |
| } |
| |
| /** |
| * An alternative validation function used by CollectionsType in conjunction with CompositeType. |
| * |
| * This is similar to the compare function above. |
| */ |
| public void validateCollectionMember(ByteBuffer bytes, ByteBuffer collectionName) throws MarshalException |
| { |
| validate(bytes); |
| } |
| |
| public boolean isCollection() |
| { |
| return false; |
| } |
| |
| public boolean isMultiCell() |
| { |
| return false; |
| } |
| |
| public AbstractType<?> freeze() |
| { |
| return this; |
| } |
| |
| /** |
| * Returns {@code true} for types where empty should be handled like {@code null} like {@link Int32Type}. |
| */ |
| public boolean isEmptyValueMeaningless() |
| { |
| return false; |
| } |
| |
| /** |
| * @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. |
| */ |
| public int componentsCount() |
| { |
| return 1; |
| } |
| |
| /** |
| * Return a list of the "subcomponents" this type has. |
| * This always return a singleton list with the type itself except for CompositeType. |
| */ |
| public List<AbstractType<?>> getComponents() |
| { |
| return Collections.<AbstractType<?>>singletonList(this); |
| } |
| |
| /** |
| * The length of values for this type if all values are of fixed length, -1 otherwise. |
| */ |
| protected int valueLengthIfFixed() |
| { |
| return -1; |
| } |
| |
| // This assumes that no empty values are passed |
| public void writeValue(ByteBuffer value, DataOutputPlus out) throws IOException |
| { |
| assert value.hasRemaining(); |
| if (valueLengthIfFixed() >= 0) |
| out.write(value); |
| else |
| ByteBufferUtil.writeWithVIntLength(value, out); |
| } |
| |
| public long writtenLength(ByteBuffer value) |
| { |
| assert value.hasRemaining(); |
| return valueLengthIfFixed() >= 0 |
| ? value.remaining() |
| : TypeSizes.sizeofWithVIntLength(value); |
| } |
| |
| public ByteBuffer readValue(DataInputPlus in) throws IOException |
| { |
| return readValue(in, Integer.MAX_VALUE); |
| } |
| |
| public ByteBuffer readValue(DataInputPlus in, int maxValueSize) throws IOException |
| { |
| int length = valueLengthIfFixed(); |
| |
| if (length >= 0) |
| return ByteBufferUtil.read(in, length); |
| else |
| { |
| int l = (int)in.readUnsignedVInt(); |
| if (l < 0) |
| throw new IOException("Corrupt (negative) value length encountered"); |
| |
| if (l > maxValueSize) |
| throw new IOException(String.format("Corrupt value length %d encountered, as it exceeds the maximum of %d, " + |
| "which is set via max_value_size_in_mb in cassandra.yaml", |
| l, maxValueSize)); |
| |
| return ByteBufferUtil.read(in, l); |
| } |
| } |
| |
| public void skipValue(DataInputPlus in) throws IOException |
| { |
| int length = valueLengthIfFixed(); |
| if (length >= 0) |
| in.skipBytesFully(length); |
| else |
| ByteBufferUtil.skipWithVIntLength(in); |
| } |
| |
| public boolean referencesUserType(String userTypeName) |
| { |
| return false; |
| } |
| |
| /** |
| * This must be overriden by subclasses if necessary so that for any |
| * AbstractType, this == TypeParser.parse(toString()). |
| * |
| * Note that for backwards compatibility this includes the full classname. |
| * For CQL purposes the short name is fine. |
| */ |
| @Override |
| public String toString() |
| { |
| return getClass().getName(); |
| } |
| |
| public void checkComparable() |
| { |
| switch (comparisonType) |
| { |
| case NOT_COMPARABLE: |
| throw new IllegalArgumentException(this + " cannot be used in comparisons, so cannot be used as a clustering column"); |
| } |
| } |
| } |