blob: b0d6a5da15c2ce5faef22b3444030ef0c9bcac2f [file] [log] [blame]
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.BytesSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* A class avoiding class duplication between CompositeType and
* DynamicCompositeType.
* Those two differs only in that for DynamicCompositeType, the comparators
* are in the encoded column name at the front of each component.
*/
public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
{
protected AbstractCompositeType()
{
super(ComparisonType.CUSTOM);
}
public int compareCustom(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();
boolean isStatic1 = readIsStatic(bb1);
boolean isStatic2 = readIsStatic(bb2);
if (isStatic1 != isStatic2)
return isStatic1 ? -1 : 1;
int i = 0;
ByteBuffer previous = null;
while (bb1.remaining() > 0 && bb2.remaining() > 0)
{
AbstractType<?> comparator = getComparator(i, bb1, bb2);
ByteBuffer value1 = ByteBufferUtil.readBytesWithShortLength(bb1);
ByteBuffer value2 = ByteBufferUtil.readBytesWithShortLength(bb2);
int cmp = comparator.compareCollectionMembers(value1, value2, previous);
if (cmp != 0)
return cmp;
previous = value1;
byte b1 = bb1.get();
byte b2 = bb2.get();
if (b1 != b2)
return b1 - b2;
++i;
}
if (bb1.remaining() == 0)
return bb2.remaining() == 0 ? 0 : -1;
// bb1.remaining() > 0 && bb2.remaining() == 0
return 1;
}
// Check if the provided BB represents a static name and advance the
// buffer to the real beginning if so.
protected abstract boolean readIsStatic(ByteBuffer bb);
/**
* Split a composite column names into it's components.
*/
public ByteBuffer[] split(ByteBuffer name)
{
List<ByteBuffer> l = new ArrayList<ByteBuffer>();
ByteBuffer bb = name.duplicate();
readIsStatic(bb);
int i = 0;
while (bb.remaining() > 0)
{
getComparator(i++, bb);
l.add(ByteBufferUtil.readBytesWithShortLength(bb));
bb.get(); // skip end-of-component
}
return l.toArray(new ByteBuffer[l.size()]);
}
/*
* Escapes all occurences of the ':' character from the input, replacing them by "\:".
* Furthermore, if the last character is '\' or '!', a '!' is appended.
*/
public static String escape(String input)
{
if (input.isEmpty())
return input;
String res = input.replaceAll(":", "\\\\:");
char last = res.charAt(res.length() - 1);
return last == '\\' || last == '!' ? res + '!' : res;
}
/*
* Reverses the effect of espace().
* Replaces all occurences of "\:" by ":" and remove last character if it is '!'.
*/
static String unescape(String input)
{
if (input.isEmpty())
return input;
String res = input.replaceAll("\\\\:", ":");
char last = res.charAt(res.length() - 1);
return last == '!' ? res.substring(0, res.length() - 1) : res;
}
/*
* Split the input on character ':', unless the previous character is '\'.
*/
static List<String> split(String input)
{
if (input.isEmpty())
return Collections.<String>emptyList();
List<String> res = new ArrayList<String>();
int prev = 0;
for (int i = 0; i < input.length(); i++)
{
if (input.charAt(i) != ':' || (i > 0 && input.charAt(i-1) == '\\'))
continue;
res.add(input.substring(prev, i));
prev = i + 1;
}
res.add(input.substring(prev, input.length()));
return res;
}
public String getString(ByteBuffer bytes)
{
StringBuilder sb = new StringBuilder();
ByteBuffer bb = bytes.duplicate();
readIsStatic(bb);
int i = 0;
while (bb.remaining() > 0)
{
if (bb.remaining() != bytes.remaining())
sb.append(":");
AbstractType<?> comparator = getAndAppendComparator(i, bb, sb);
ByteBuffer value = ByteBufferUtil.readBytesWithShortLength(bb);
sb.append(escape(comparator.getString(value)));
byte b = bb.get();
if (b != 0)
{
sb.append(b < 0 ? ":_" : ":!");
break;
}
++i;
}
return sb.toString();
}
public ByteBuffer fromString(String source)
{
List<String> parts = split(source);
List<ByteBuffer> components = new ArrayList<ByteBuffer>(parts.size());
List<ParsedComparator> comparators = new ArrayList<ParsedComparator>(parts.size());
int totalLength = 0, i = 0;
boolean lastByteIsOne = false;
boolean lastByteIsMinusOne = false;
for (String part : parts)
{
if (part.equals("!"))
{
lastByteIsOne = true;
break;
}
else if (part.equals("_"))
{
lastByteIsMinusOne = true;
break;
}
ParsedComparator p = parseComparator(i, part);
AbstractType<?> type = p.getAbstractType();
part = p.getRemainingPart();
ByteBuffer component = type.fromString(unescape(part));
totalLength += p.getComparatorSerializedSize() + 2 + component.remaining() + 1;
components.add(component);
comparators.add(p);
++i;
}
ByteBuffer bb = ByteBuffer.allocate(totalLength);
i = 0;
for (ByteBuffer component : components)
{
comparators.get(i).serializeComparator(bb);
ByteBufferUtil.writeShortLength(bb, component.remaining());
bb.put(component); // it's ok to consume component as we won't use it anymore
bb.put((byte)0);
++i;
}
if (lastByteIsOne)
bb.put(bb.limit() - 1, (byte)1);
else if (lastByteIsMinusOne)
bb.put(bb.limit() - 1, (byte)-1);
bb.rewind();
return bb;
}
@Override
public Term fromJSONObject(Object parsed)
{
throw new UnsupportedOperationException();
}
@Override
public String toJSONString(ByteBuffer buffer, int protocolVersion)
{
throw new UnsupportedOperationException();
}
@Override
public void validate(ByteBuffer bytes) throws MarshalException
{
ByteBuffer bb = bytes.duplicate();
readIsStatic(bb);
int i = 0;
ByteBuffer previous = null;
while (bb.remaining() > 0)
{
AbstractType<?> comparator = validateComparator(i, bb);
if (bb.remaining() < 2)
throw new MarshalException("Not enough bytes to read value size of component " + i);
int length = ByteBufferUtil.readShortLength(bb);
if (bb.remaining() < length)
throw new MarshalException("Not enough bytes to read value of component " + i);
ByteBuffer value = ByteBufferUtil.readBytes(bb, length);
comparator.validateCollectionMember(value, previous);
if (bb.remaining() == 0)
throw new MarshalException("Not enough bytes to read the end-of-component byte of component" + i);
byte b = bb.get();
if (b != 0 && bb.remaining() != 0)
throw new MarshalException("Invalid bytes remaining after an end-of-component at component" + i);
previous = value;
++i;
}
}
public abstract ByteBuffer decompose(Object... objects);
public TypeSerializer<ByteBuffer> getSerializer()
{
return BytesSerializer.instance;
}
@Override
public boolean referencesUserType(String name)
{
return getComponents().stream().anyMatch(f -> f.referencesUserType(name));
}
/**
* @return the comparator for the given component. static CompositeType will consult
* @param i DynamicCompositeType will read the type information from @param bb
* @param bb name of type definition
*/
abstract protected AbstractType<?> getComparator(int i, ByteBuffer bb);
/**
* Adds DynamicCompositeType type information from @param bb1 to @param bb2.
* @param i is ignored.
*/
abstract protected AbstractType<?> getComparator(int i, ByteBuffer bb1, ByteBuffer bb2);
/**
* Adds type information from @param bb to @param sb. @param i is ignored.
*/
abstract protected AbstractType<?> getAndAppendComparator(int i, ByteBuffer bb, StringBuilder sb);
/**
* Like getComparator, but validates that @param i does not exceed the defined range
*/
abstract protected AbstractType<?> validateComparator(int i, ByteBuffer bb) throws MarshalException;
/**
* Used by fromString
*/
abstract protected ParsedComparator parseComparator(int i, String part);
protected static interface ParsedComparator
{
AbstractType<?> getAbstractType();
String getRemainingPart();
int getComparatorSerializedSize();
void serializeComparator(ByteBuffer bb);
}
}