mirror of https://github.com/apache/cassandra
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:
parent
522d0dd1e4
commit
5d98581e9c
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue