Add CompositeType and DynamicCompositeType

patch by slebresne; reviewed by edanuff for CASSANDRA-2231


git-svn-id: https://svn.apache.org/repos/asf/cassandra/branches/cassandra-0.8.1@1101120 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Sylvain Lebresne 2011-05-09 17:24:34 +00:00
parent 522d0dd1e4
commit 5d98581e9c
8 changed files with 1240 additions and 13 deletions

View File

@ -6,6 +6,7 @@
0.8.1
* add support for comparator parameters and a generic ReverseType
(CASSANDRA-2355)
* add CompositeType and DynamicCompositeType (CASSANDRA-2231)
0.8.0-?
* faster flushes and compaction from fixing excessively pessimistic

View File

@ -0,0 +1,294 @@
/**
* 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.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.sql.Types;
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>
{
// changes bb position
protected static int getShortLength(ByteBuffer bb)
{
int length = (bb.get() & 0xFF) << 8;
return length | (bb.get() & 0xFF);
}
// changes bb position
protected static void putShortLength(ByteBuffer bb, int length)
{
bb.put((byte) ((length >> 8) & 0xFF));
bb.put((byte) (length & 0xFF));
}
// changes bb position
protected static ByteBuffer getBytes(ByteBuffer bb, int length)
{
ByteBuffer copy = bb.duplicate();
copy.limit(copy.position() + length);
bb.position(bb.position() + length);
return copy;
}
// changes bb position
protected static ByteBuffer getWithShortLength(ByteBuffer bb)
{
int length = getShortLength(bb);
return getBytes(bb, length);
}
public int compare(ByteBuffer o1, ByteBuffer o2)
{
if (null == o1)
return null == o2 ? 0 : -1;
ByteBuffer bb1 = o1.duplicate();
ByteBuffer bb2 = o2.duplicate();
int i = 0;
while (bb1.remaining() > 0 && bb2.remaining() > 0)
{
AbstractType comparator = getNextComparator(i, bb1, bb2);
ByteBuffer value1 = getWithShortLength(bb1);
ByteBuffer value2 = getWithShortLength(bb2);
int cmp = comparator.compare(value1, value2);
if (cmp != 0)
return cmp;
byte b1 = bb1.get();
byte b2 = bb2.get();
if (b1 < 0)
{
if (b2 >= 0)
return -1;
}
else if (b1 > 0)
{
if (b2 <= 0)
return 1;
}
else
{
// b1 == 0
if (b2 != 0)
return -b2;
}
++i;
}
if (bb1.remaining() == 0)
return bb2.remaining() == 0 ? 0 : -1;
// bb1.remaining() > 0 && bb2.remaining() == 0
return 1;
}
public String getString(ByteBuffer bytes)
{
StringBuilder sb = new StringBuilder();
ByteBuffer bb = bytes.duplicate();
int i = 0;
while (bb.remaining() > 0)
{
if (bb.remaining() != bytes.remaining())
sb.append(":");
AbstractType comparator = getAndAppendNextComparator(i, bb, sb);
ByteBuffer value = getWithShortLength(bb);
sb.append(comparator.getString(value));
byte b = bb.get();
if (b != 0)
{
sb.append(":!");
break;
}
++i;
}
return sb.toString();
}
/*
* FIXME: this would break if some of the component string representation
* contains ':'. None of our current comparator do so, so this is probably
* not an urgent matter, but this could break for custom comparator.
* (DynamicCompositeType would break on '@' too)
*/
public ByteBuffer fromString(String source)
{
String[] parts = source.split(":");
List<ByteBuffer> components = new ArrayList<ByteBuffer>();
List<ParsedComparator> comparators = new ArrayList<ParsedComparator>();
int totalLength = 0, i = 0;
boolean lastByteIsOne = false;
for (String part : parts)
{
if (part.equals("!"))
{
lastByteIsOne = true;
break;
}
ParsedComparator p = parseNextComparator(i, part);
AbstractType type = p.getAbstractType();
part = p.getRemainingPart();
ByteBuffer component = type.fromString(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);
putShortLength(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);
bb.rewind();
return bb;
}
public void validate(ByteBuffer bytes) throws MarshalException
{
ByteBuffer bb = bytes.duplicate();
int i = 0;
while (bb.remaining() > 0)
{
AbstractType comparator = validateNextComparator(i, bb);
if (bb.remaining() < 2)
throw new MarshalException("Not enough bytes to read value size of component " + i);
int length = getShortLength(bb);
if (bb.remaining() < length)
throw new MarshalException("Not enough bytes to read value of component " + i);
ByteBuffer value = getBytes(bb, length);
comparator.validate(value);
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);
++i;
}
}
public ByteBuffer compose(ByteBuffer bytes)
{
return bytes;
}
public ByteBuffer decompose(ByteBuffer value)
{
return value;
}
public Class<ByteBuffer> getType()
{
return ByteBuffer.class;
}
public String toString(ByteBuffer value)
{
return getString(value);
}
/*
* JDBC metadata. For now we don't allow the use of compositeType with
* JDBC. We'll have to figure out what is the best solution here.
*/
public boolean isSigned()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public boolean isCaseSensitive()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public boolean isCurrency()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public int getPrecision(ByteBuffer obj)
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public int getScale(ByteBuffer obj)
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public int getJdbcType()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public boolean needsQuotes()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
abstract protected AbstractType getNextComparator(int i, ByteBuffer bb);
abstract protected AbstractType getNextComparator(int i, ByteBuffer bb1, ByteBuffer bb2);
abstract protected AbstractType getAndAppendNextComparator(int i, ByteBuffer bb, StringBuilder sb);
abstract protected ParsedComparator parseNextComparator(int i, String part);
abstract protected AbstractType validateNextComparator(int i, ByteBuffer bb) throws MarshalException;
protected static interface ParsedComparator
{
AbstractType getAbstractType();
String getRemainingPart();
int getComparatorSerializedSize();
void serializeComparator(ByteBuffer bb);
}
}

View File

@ -0,0 +1,144 @@
/**
* 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.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.utils.ByteBufferUtil;
/*
* The encoding of a CompositeType column name should be:
* <component><component><component> ...
* where <component> is:
* <length of value><value><'end-of-component' byte>
* where <length of value> is a 2 bytes unsigned short the and the
* 'end-of-component' byte should always be 0 for actual column name.
* However, it can set to 1 for query bounds. This allows to query for the
* equivalent of 'give me the full super-column'. That is, if during a slice
* query uses:
* start = <3><"foo".getBytes()><0>
* end = <3><"foo".getBytes()><1>
* then he will be sure to get *all* the columns whose first component is "foo".
* If for a component, the 'end-of-component' is != 0, there should not be any
* following component. The end-of-component can also be -1 to allow
* non-inclusive query. For instance:
* start = <3><"foo".getBytes()><-1>
* allows to query everything that is greater than <3><"foo".getBytes()>, but
* not <3><"foo".getBytes()> itself.
*/
public class CompositeType extends AbstractCompositeType
{
// package protected for unit tests sake
final List<AbstractType> types;
// interning instances
private static final Map<List<AbstractType>, CompositeType> instances = new HashMap<List<AbstractType>, CompositeType>();
public static CompositeType getInstance(TypeParser parser) throws ConfigurationException
{
return getInstance(parser.getTypeParameters());
}
public static synchronized CompositeType getInstance(List<AbstractType> types) throws ConfigurationException
{
if (types == null || types.isEmpty())
throw new ConfigurationException("Nonsensical empty parameter list for CompositeType");
CompositeType ct = instances.get(types);
if (ct == null)
{
ct = new CompositeType(types);
instances.put(types, ct);
}
return ct;
}
private CompositeType(List<AbstractType> types)
{
this.types = types;
}
protected AbstractType getNextComparator(int i, ByteBuffer bb)
{
return types.get(i);
}
protected AbstractType getNextComparator(int i, ByteBuffer bb1, ByteBuffer bb2)
{
return types.get(i);
}
protected AbstractType getAndAppendNextComparator(int i, ByteBuffer bb, StringBuilder sb)
{
return types.get(i);
}
protected ParsedComparator parseNextComparator(int i, String part)
{
return new StaticParsedComparator(types.get(i), part);
}
protected AbstractType validateNextComparator(int i, ByteBuffer bb) throws MarshalException
{
if (i >= types.size())
throw new MarshalException("Too many bytes for comparator");
return types.get(i);
}
private static class StaticParsedComparator implements ParsedComparator
{
final AbstractType type;
final String part;
StaticParsedComparator(AbstractType type, String part)
{
this.type = type;
this.part = part;
}
public AbstractType getAbstractType()
{
return type;
}
public String getRemainingPart()
{
return part;
}
public int getComparatorSerializedSize()
{
return 0;
}
public void serializeComparator(ByteBuffer bb) {}
}
@Override
public String toString()
{
return getClass().getName() + TypeParser.stringifyTypeParameters(types);
}
}

View File

@ -0,0 +1,266 @@
/**
* 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.charset.CharacterCodingException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
* The encoding of a DynamicCompositeType column name should be:
* <component><component><component> ...
* where <component> is:
* <comparator part><value><'end-of-component' byte>
* where:
* - <comparator part>: either the comparator full name, or a declared
* aliases. This is at least 2 bytes (those 2 bytes are called header in
* the following). If the first bit of the header is 1, then this
* comparator part is an alias, otherwise it's a comparator full name:
* - aliases: the actual alias is the 2nd byte of header taken as a
* character. The whole <comparator part> is thus 2 byte long.
* - comparator full name: the header is the length of the remaining
* part. The remaining part is the UTF-8 encoded comparator class
* name.
* - <value>: the component value bytes preceded by 2 byte containing the
* size of value (see CompositeType).
* - 'end-of-component' byte is defined as in CompositeType
*/
public class DynamicCompositeType extends AbstractCompositeType
{
private static final Logger logger = LoggerFactory.getLogger(DynamicCompositeType.class);
private final Map<Byte, AbstractType> aliases;
// interning instances
private static final Map<Map<Byte, AbstractType>, DynamicCompositeType> instances = new HashMap<Map<Byte, AbstractType>, DynamicCompositeType>();
public static synchronized DynamicCompositeType getInstance(TypeParser parser) throws ConfigurationException
{
return getInstance(parser.getAliasParameters());
}
public static synchronized DynamicCompositeType getInstance(Map<Byte, AbstractType> aliases)
{
DynamicCompositeType dct = instances.get(aliases);
if (dct == null)
{
dct = new DynamicCompositeType(aliases);
instances.put(aliases, dct);
}
return dct;
}
private DynamicCompositeType(Map<Byte, AbstractType> aliases)
{
this.aliases = aliases;
}
private AbstractType getComparator(ByteBuffer bb)
{
try
{
int header = getShortLength(bb);
if ((header & 0x8000) == 0)
{
String name = ByteBufferUtil.string(getBytes(bb, header));
return TypeParser.parse(name);
}
else
{
return aliases.get((byte)(header & 0xFF));
}
}
catch (CharacterCodingException e)
{
throw new RuntimeException(e);
}
catch (ConfigurationException e)
{
throw new RuntimeException(e);
}
}
protected AbstractType getNextComparator(int i, ByteBuffer bb)
{
return getComparator(bb);
}
protected AbstractType getNextComparator(int i, ByteBuffer bb1, ByteBuffer bb2)
{
AbstractType comp1 = getComparator(bb1);
AbstractType comp2 = getComparator(bb2);
// This rely on comparator always being singleton instances
if (comp1 != comp2)
{
logger.error("Mismatch between {} and {}", comp1, comp2);
throw new RuntimeException("Comparator mismatch while comparing two DynamicCompositeType colum name");
}
return comp1;
}
protected AbstractType getAndAppendNextComparator(int i, ByteBuffer bb, StringBuilder sb)
{
try
{
int header = getShortLength(bb);
if ((header & 0x8000) == 0)
{
String name = ByteBufferUtil.string(getBytes(bb, header));
sb.append(name).append("@");
return TypeParser.parse(name);
}
else
{
sb.append((char)(header & 0xFF)).append("@");
return aliases.get((byte)(header & 0xFF));
}
}
catch (CharacterCodingException e)
{
throw new RuntimeException(e);
}
catch (ConfigurationException e)
{
throw new RuntimeException(e);
}
}
protected ParsedComparator parseNextComparator(int i, String part)
{
return new DynamicParsedComparator(part);
}
protected AbstractType validateNextComparator(int i, ByteBuffer bb) throws MarshalException
{
AbstractType comparator = null;
if (bb.remaining() < 2)
throw new MarshalException("Not enough bytes to header of the comparator part of component " + i);
int header = getShortLength(bb);
if ((header & 0x8000) == 0)
{
if (bb.remaining() < header)
throw new MarshalException("Not enough bytes to read comparator name of component " + i);
ByteBuffer value = getBytes(bb, header);
try
{
comparator = TypeParser.parse(ByteBufferUtil.string(value));
}
catch (Exception e)
{
// we'll deal with this below since comparator == null
}
}
else
{
comparator = aliases.get((byte)(header & 0xFF));
}
if (comparator == null)
throw new MarshalException("Cannot find comparator for component " + i);
else
return comparator;
}
private class DynamicParsedComparator implements ParsedComparator
{
final AbstractType type;
final boolean isAlias;
final String comparatorName;
final String remainingPart;
DynamicParsedComparator(String part)
{
String[] splits = part.split("@");
if (splits.length != 2)
throw new IllegalArgumentException("Invalid component representation: " + part);
comparatorName = splits[0];
remainingPart = splits[1];
try
{
AbstractType t = null;
if (comparatorName.length() == 1)
{
// try for an alias
// Note: the char to byte cast is theorically bogus for unicode character. I take full
// responsibility if someone get hit by this (without making it on purpose)
t = aliases.get((byte)comparatorName.charAt(0));
}
isAlias = t != null;
if (!isAlias)
{
t = TypeParser.parse(comparatorName);
}
type = t;
}
catch (ConfigurationException e)
{
throw new IllegalArgumentException(e);
}
}
public AbstractType getAbstractType()
{
return type;
}
public String getRemainingPart()
{
return remainingPart;
}
public int getComparatorSerializedSize()
{
return isAlias ? 2 : 2 + ByteBufferUtil.bytes(comparatorName).remaining();
}
public void serializeComparator(ByteBuffer bb)
{
int header = 0;
if (isAlias)
header = 0x8000 | ((byte)comparatorName.charAt(0));
else
header = comparatorName.length();
putShortLength(bb, header);
if (!isAlias)
bb.put(ByteBufferUtil.bytes(comparatorName));
}
}
@Override
public String toString()
{
return getClass().getName() + TypeParser.stringifyAliasesParameters(aliases);
}
}

View File

@ -27,6 +27,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.utils.FBUtilities;
@ -414,17 +416,7 @@ public class TypeParser
public static String stringifyTypeParameters(List<AbstractType> types)
{
StringBuilder sb = new StringBuilder();
sb.append('(');
Iterator<AbstractType> iter = types.iterator();
if (iter.hasNext())
{
sb.append(iter.next());
}
while (iter.hasNext())
{
sb.append(',').append(iter.next());
}
sb.append(')');
sb.append('(').append(StringUtils.join(types, ",")).append(')');
return sb.toString();
}
}

View File

@ -54,7 +54,7 @@ public class SchemaLoader
}
}
public static Collection<KSMetaData> schemaDefinition()
public static Collection<KSMetaData> schemaDefinition() throws ConfigurationException
{
List<KSMetaData> schema = new ArrayList<KSMetaData>();
@ -78,6 +78,12 @@ public class SchemaLoader
ColumnFamilyType st = ColumnFamilyType.Standard;
ColumnFamilyType su = ColumnFamilyType.Super;
AbstractType bytes = BytesType.instance;
AbstractType composite = CompositeType.getInstance(Arrays.asList(new AbstractType[]{BytesType.instance, TimeUUIDType.instance, IntegerType.instance}));
Map<Byte, AbstractType> aliases = new HashMap<Byte, AbstractType>();
aliases.put((byte)'b', BytesType.instance);
aliases.put((byte)'t', TimeUUIDType.instance);
AbstractType dynamicComposite = DynamicCompositeType.getInstance(aliases);
// these column definitions will will be applied to the jdbc utf and integer column familes respectively.
Map<ByteBuffer, ColumnDefinition> integerColumn = new HashMap<ByteBuffer, ColumnDefinition>();
@ -134,7 +140,17 @@ public class SchemaLoader
jdbcCFMD(ks1, "JdbcUtf8", UTF8Type.instance).columnMetadata(utf8Column),
jdbcCFMD(ks1, "JdbcLong", LongType.instance),
jdbcCFMD(ks1, "JdbcBytes", bytes),
jdbcCFMD(ks1, "JdbcAscii", AsciiType.instance)));
jdbcCFMD(ks1, "JdbcAscii", AsciiType.instance),
new CFMetaData(ks1,
"StandardComposite",
st,
composite,
null),
new CFMetaData(ks1,
"StandardDynamicComposite",
st,
dynamicComposite,
null)));
// Keyspace 2
schema.add(new KSMetaData(ks2,

View File

@ -0,0 +1,268 @@
/*
* 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.Iterator;
import java.util.List;
import java.util.UUID;
import org.junit.Test;
import static org.junit.Assert.fail;
import org.apache.cassandra.CleanupHelper;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.columniterator.IdentityQueryFilter;
import org.apache.cassandra.db.filter.QueryFilter;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.utils.*;
public class CompositeTypeTest extends CleanupHelper
{
private static final String cfName = "StandardComposite";
private static final CompositeType comparator;
static
{
List<AbstractType> subComparators = new ArrayList<AbstractType>();
subComparators.add(BytesType.instance);
subComparators.add(TimeUUIDType.instance);
subComparators.add(IntegerType.instance);
try
{
comparator = CompositeType.getInstance(subComparators);
}
catch (ConfigurationException e)
{
throw new RuntimeException(e);
}
}
private static final int UUID_COUNT = 3;
private static final UUID[] uuids = new UUID[UUID_COUNT];
static
{
for (int i = 0; i < UUID_COUNT; ++i)
uuids[i] = UUIDGen.makeType1UUIDFromHost(FBUtilities.getLocalAddress());
}
@Test
public void testEndOfComponent()
{
ByteBuffer[] cnames = {
createCompositeKey("test1", uuids[0], -1, false),
createCompositeKey("test1", uuids[1], 24, false),
createCompositeKey("test1", uuids[1], 42, false),
createCompositeKey("test1", uuids[1], 83, false),
createCompositeKey("test1", uuids[2], -1, false),
createCompositeKey("test1", uuids[2], 42, false),
};
ByteBuffer start = createCompositeKey("test1", uuids[1], -1, false);
ByteBuffer stop = createCompositeKey("test1", uuids[1], -1, true);
for (int i = 0; i < 1; ++i)
{
assert comparator.compare(start, cnames[i]) > 0;
assert comparator.compare(stop, cnames[i]) > 0;
}
for (int i = 1; i < 4; ++i)
{
assert comparator.compare(start, cnames[i]) < 0;
assert comparator.compare(stop, cnames[i]) > 0;
}
for (int i = 4; i < cnames.length; ++i)
{
assert comparator.compare(start, cnames[i]) < 0;
assert comparator.compare(stop, cnames[i]) < 0;
}
}
@Test
public void testGetString()
{
String test1Hex = ByteBufferUtil.bytesToHex(ByteBufferUtil.bytes("test1"));
ByteBuffer key = createCompositeKey("test1", uuids[1], 42, false);
assert comparator.getString(key).equals(test1Hex + ":" + uuids[1] + ":42");
key = createCompositeKey("test1", uuids[1], -1, true);
assert comparator.getString(key).equals(test1Hex + ":" + uuids[1] + ":!");
}
@Test
public void testFromString()
{
String test1Hex = ByteBufferUtil.bytesToHex(ByteBufferUtil.bytes("test1"));
ByteBuffer key = createCompositeKey("test1", uuids[1], 42, false);
assert key.equals(comparator.fromString(test1Hex + ":" + uuids[1] + ":42"));
key = createCompositeKey("test1", uuids[1], -1, true);
assert key.equals(comparator.fromString(test1Hex + ":" + uuids[1] + ":!"));
}
@Test
public void testValidate()
{
ByteBuffer key = createCompositeKey("test1", uuids[1], 42, false);
comparator.validate(key);
key = createCompositeKey("test1", null, -1, false);
comparator.validate(key);
key = createCompositeKey("test1", uuids[2], -1, true);
comparator.validate(key);
key.get(); // make sure we're not aligned anymore
try
{
comparator.validate(key);
fail("Should not validate");
}
catch (MarshalException e) {}
key = ByteBuffer.allocate(3 + "test1".length() + 3 + 14);
key.putShort((short) "test1".length());
key.put(ByteBufferUtil.bytes("test1"));
key.put((byte) 0);
key.putShort((short) 14);
key.rewind();
try
{
comparator.validate(key);
fail("Should not validate");
}
catch (MarshalException e)
{
assert e.toString().contains("TimeUUID should be 16 or 0 bytes");
}
key = createCompositeKey("test1", UUID.randomUUID(), 42, false);
try
{
comparator.validate(key);
fail("Should not validate");
}
catch (MarshalException e)
{
assert e.toString().contains("Invalid version for TimeUUID type");
}
}
@Test
public void testFullRound() throws Exception
{
Table table = Table.open("Keyspace1");
ColumnFamilyStore cfs = table.getColumnFamilyStore(cfName);
ByteBuffer cname1 = createCompositeKey("test1", null, -1, false);
ByteBuffer cname2 = createCompositeKey("test1", uuids[0], 24, false);
ByteBuffer cname3 = createCompositeKey("test1", uuids[0], 42, false);
ByteBuffer cname4 = createCompositeKey("test2", uuids[0], -1, false);
ByteBuffer cname5 = createCompositeKey("test2", uuids[1], 42, false);
ByteBuffer key = ByteBufferUtil.bytes("k");
RowMutation rm = new RowMutation("Keyspace1", key);
addColumn(rm, cname5);
addColumn(rm, cname1);
addColumn(rm, cname4);
addColumn(rm, cname2);
addColumn(rm, cname3);
rm.apply();
ColumnFamily cf = cfs.getColumnFamily(QueryFilter.getIdentityFilter(Util.dk("k"), new QueryPath(cfName, null, null)));
Iterator<IColumn> iter = cf.getSortedColumns().iterator();
assert iter.next().name().equals(cname1);
assert iter.next().name().equals(cname2);
assert iter.next().name().equals(cname3);
assert iter.next().name().equals(cname4);
assert iter.next().name().equals(cname5);
}
@Test
public void testEmptyParametersNotallowed()
{
try
{
TypeParser.parse("CompositeType");
fail("Shouldn't work");
}
catch (ConfigurationException e) {}
try
{
TypeParser.parse("CompositeType()");
fail("Shouldn't work");
}
catch (ConfigurationException e) {}
}
private void addColumn(RowMutation rm, ByteBuffer cname)
{
rm.add(new QueryPath(cfName, null , cname), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0);
}
private ByteBuffer createCompositeKey(String s, UUID uuid, int i, boolean lastIsOne)
{
ByteBuffer bytes = ByteBufferUtil.bytes(s);
int totalSize = 0;
if (s != null)
{
totalSize += 2 + bytes.remaining() + 1;
if (uuid != null)
{
totalSize += 2 + 16 + 1;
if (i != -1)
{
totalSize += 2 + 1 + 1;
}
}
}
ByteBuffer bb = ByteBuffer.allocate(totalSize);
if (s != null)
{
bb.putShort((short) bytes.remaining());
bb.put(bytes);
bb.put(uuid == null && lastIsOne ? (byte)1 : (byte)0);
if (uuid != null)
{
bb.putShort((short) 16);
bb.put(UUIDGen.decompose(uuid));
bb.put(i == -1 && lastIsOne ? (byte)1 : (byte)0);
if (i != -1)
{
// We are putting a byte only because our test use ints that fit in a byte *and* IntegerType.fromString() will
// return something compatible (i.e, putting a full int here would break 'fromStringTest')
bb.putShort((short) 1);
bb.put((byte)i);
bb.put(lastIsOne ? (byte)1 : (byte)0);
}
}
}
bb.rewind();
return bb;
}
}

View File

@ -0,0 +1,246 @@
/*
* 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.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.junit.Test;
import static org.junit.Assert.fail;
import org.apache.cassandra.CleanupHelper;
import org.apache.cassandra.Util;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.columniterator.IdentityQueryFilter;
import org.apache.cassandra.db.filter.QueryFilter;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.utils.*;
public class DynamicCompositeTypeTest extends CleanupHelper
{
private static final String cfName = "StandardDynamicComposite";
private static final DynamicCompositeType comparator;
static
{
Map<Byte, AbstractType> aliases = new HashMap<Byte, AbstractType>();
aliases.put((byte)'b', BytesType.instance);
aliases.put((byte)'t', TimeUUIDType.instance);
comparator = DynamicCompositeType.getInstance(aliases);
}
private static final int UUID_COUNT = 3;
private static final UUID[] uuids = new UUID[UUID_COUNT];
static
{
for (int i = 0; i < UUID_COUNT; ++i)
uuids[i] = UUIDGen.makeType1UUIDFromHost(FBUtilities.getLocalAddress());
}
@Test
public void testEndOfComponent()
{
ByteBuffer[] cnames = {
createDynamicCompositeKey("test1", uuids[0], -1, false),
createDynamicCompositeKey("test1", uuids[1], 24, false),
createDynamicCompositeKey("test1", uuids[1], 42, false),
createDynamicCompositeKey("test1", uuids[1], 83, false),
createDynamicCompositeKey("test1", uuids[2], -1, false),
createDynamicCompositeKey("test1", uuids[2], 42, false),
};
ByteBuffer start = createDynamicCompositeKey("test1", uuids[1], -1, false);
ByteBuffer stop = createDynamicCompositeKey("test1", uuids[1], -1, true);
for (int i = 0; i < 1; ++i)
{
assert comparator.compare(start, cnames[i]) > 0;
assert comparator.compare(stop, cnames[i]) > 0;
}
for (int i = 1; i < 4; ++i)
{
assert comparator.compare(start, cnames[i]) < 0;
assert comparator.compare(stop, cnames[i]) > 0;
}
for (int i = 4; i < cnames.length; ++i)
{
assert comparator.compare(start, cnames[i]) < 0;
assert comparator.compare(stop, cnames[i]) < 0;
}
}
@Test
public void testGetString()
{
String test1Hex = ByteBufferUtil.bytesToHex(ByteBufferUtil.bytes("test1"));
ByteBuffer key = createDynamicCompositeKey("test1", uuids[1], 42, false);
assert comparator.getString(key).equals("b@" + test1Hex + ":t@" + uuids[1] + ":IntegerType@42");
key = createDynamicCompositeKey("test1", uuids[1], -1, true);
assert comparator.getString(key).equals("b@" + test1Hex + ":t@" + uuids[1] + ":!");
}
@Test
public void testFromString()
{
String test1Hex = ByteBufferUtil.bytesToHex(ByteBufferUtil.bytes("test1"));
ByteBuffer key = createDynamicCompositeKey("test1", uuids[1], 42, false);
assert key.equals(comparator.fromString("b@" + test1Hex + ":t@" + uuids[1] + ":IntegerType@42"));
key = createDynamicCompositeKey("test1", uuids[1], -1, true);
assert key.equals(comparator.fromString("b@" + test1Hex + ":t@" + uuids[1] + ":!"));
}
@Test
public void testValidate()
{
ByteBuffer key = createDynamicCompositeKey("test1", uuids[1], 42, false);
comparator.validate(key);
key = createDynamicCompositeKey("test1", null, -1, false);
comparator.validate(key);
key = createDynamicCompositeKey("test1", uuids[2], -1, true);
comparator.validate(key);
key.get(); // make sure we're not aligned anymore
try
{
comparator.validate(key);
fail("Should not validate");
}
catch (MarshalException e) {}
key = ByteBuffer.allocate(5 + "test1".length() + 5 + 14);
key.putShort((short) (0x8000 | 'b'));
key.putShort((short) "test1".length());
key.put(ByteBufferUtil.bytes("test1"));
key.put((byte) 0);
key.putShort((short) (0x8000 | 't'));
key.putShort((short) 14);
key.rewind();
try
{
comparator.validate(key);
fail("Should not validate");
}
catch (MarshalException e)
{
assert e.toString().contains("TimeUUID should be 16 or 0 bytes");
}
key = createDynamicCompositeKey("test1", UUID.randomUUID(), 42, false);
try
{
comparator.validate(key);
fail("Should not validate");
}
catch (MarshalException e)
{
assert e.toString().contains("Invalid version for TimeUUID type");
}
}
@Test
public void testFullRound() throws Exception
{
Table table = Table.open("Keyspace1");
ColumnFamilyStore cfs = table.getColumnFamilyStore(cfName);
ByteBuffer cname1 = createDynamicCompositeKey("test1", null, -1, false);
ByteBuffer cname2 = createDynamicCompositeKey("test1", uuids[0], 24, false);
ByteBuffer cname3 = createDynamicCompositeKey("test1", uuids[0], 42, false);
ByteBuffer cname4 = createDynamicCompositeKey("test2", uuids[0], -1, false);
ByteBuffer cname5 = createDynamicCompositeKey("test2", uuids[1], 42, false);
ByteBuffer key = ByteBufferUtil.bytes("k");
RowMutation rm = new RowMutation("Keyspace1", key);
addColumn(rm, cname5);
addColumn(rm, cname1);
addColumn(rm, cname4);
addColumn(rm, cname2);
addColumn(rm, cname3);
rm.apply();
ColumnFamily cf = cfs.getColumnFamily(QueryFilter.getIdentityFilter(Util.dk("k"), new QueryPath(cfName, null, null)));
Iterator<IColumn> iter = cf.getSortedColumns().iterator();
assert iter.next().name().equals(cname1);
assert iter.next().name().equals(cname2);
assert iter.next().name().equals(cname3);
assert iter.next().name().equals(cname4);
assert iter.next().name().equals(cname5);
}
private void addColumn(RowMutation rm, ByteBuffer cname)
{
rm.add(new QueryPath(cfName, null , cname), ByteBufferUtil.EMPTY_BYTE_BUFFER, 0);
}
private ByteBuffer createDynamicCompositeKey(String s, UUID uuid, int i, boolean lastIsOne)
{
ByteBuffer bytes = ByteBufferUtil.bytes(s);
int totalSize = 0;
if (s != null)
{
totalSize += 2 + 2 + bytes.remaining() + 1;
if (uuid != null)
{
totalSize += 2 + 2 + 16 + 1;
if (i != -1)
{
totalSize += 2 + "IntegerType".length() + 2 + 1 + 1;
}
}
}
ByteBuffer bb = ByteBuffer.allocate(totalSize);
if (s != null)
{
bb.putShort((short)(0x8000 | 'b'));
bb.putShort((short) bytes.remaining());
bb.put(bytes);
bb.put(uuid == null && lastIsOne ? (byte)1 : (byte)0);
if (uuid != null)
{
bb.putShort((short)(0x8000 | 't'));
bb.putShort((short) 16);
bb.put(UUIDGen.decompose(uuid));
bb.put(i == -1 && lastIsOne ? (byte)1 : (byte)0);
if (i != -1)
{
bb.putShort((short) "IntegerType".length());
bb.put(ByteBufferUtil.bytes("IntegerType"));
// We are putting a byte only because our test use ints that fit in a byte *and* IntegerType.fromString() will
// return something compatible (i.e, putting a full int here would break 'fromStringTest')
bb.putShort((short) 1);
bb.put((byte)i);
bb.put(lastIsOne ? (byte)1 : (byte)0);
}
}
}
bb.rewind();
return bb;
}
}