package org.apache.cassandra.db.filter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.context.*;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.db.partitions.ImmutableBTreePartition;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkBindValueSet;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
public abstract class RowFilter implements Iterable<RowFilter.Expression>
{
private static final Logger logger = LoggerFactory.getLogger(RowFilter.class);
public static final Serializer serializer = new Serializer();
public static final RowFilter NONE = new CQLFilter(Collections.emptyList());
protected final List<Expression> expressions;
protected RowFilter(List<Expression> expressions)
{
this.expressions = expressions;
}
public static RowFilter create()
{
return new CQLFilter(new ArrayList<>());
}
public static RowFilter create(int capacity)
{
return new CQLFilter(new ArrayList<>(capacity));
}
public static RowFilter forThrift(int capacity)
{
return new ThriftFilter(new ArrayList<>(capacity));
}
public SimpleExpression add(ColumnDefinition def, Operator op, ByteBuffer value)
{
SimpleExpression expression = new SimpleExpression(def, op, value);
add(expression);
return expression;
}
public void addMapEquality(ColumnDefinition def, ByteBuffer key, Operator op, ByteBuffer value)
{
add(new MapEqualityExpression(def, key, op, value));
}
public void addThriftExpression(CFMetaData metadata, ByteBuffer name, Operator op, ByteBuffer value)
{
assert (this instanceof ThriftFilter);
add(new ThriftExpression(metadata, name, op, value));
}
public void addCustomIndexExpression(CFMetaData cfm, IndexMetadata targetIndex, ByteBuffer value)
{
add(new CustomExpression(cfm, targetIndex, value));
}
private void add(Expression expression)
{
expression.validate();
expressions.add(expression);
}
public void addUserExpression(UserExpression e)
{
expressions.add(e);
}
public List<Expression> getExpressions()
{
return expressions;
}
public boolean hasExpressionOnClusteringOrRegularColumns()
{
for (Expression expression : expressions)
{
ColumnDefinition column = expression.column();
if (column.isClusteringColumn() || column.isRegular())
return true;
}
return false;
}
public abstract UnfilteredPartitionIterator filter(UnfilteredPartitionIterator iter, int nowInSec);
public boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row, int nowInSec)
{
Row purged = row.purge(DeletionPurger.PURGE_ALL, nowInSec, metadata.enforceStrictLiveness());
if (purged == null)
return expressions.isEmpty();
for (Expression e : expressions)
{
if (!e.isSatisfiedBy(metadata, partitionKey, purged))
return false;
}
return true;
}
public boolean partitionKeyRestrictionsAreSatisfiedBy(DecoratedKey key, AbstractType<?> keyValidator)
{
for (Expression e : expressions)
{
if (!e.column.isPartitionKey())
continue;
ByteBuffer value = keyValidator instanceof CompositeType
? ((CompositeType) keyValidator).split(key.getKey())[e.column.position()]
: key.getKey();
if (!e.operator().isSatisfiedBy(e.column.type, value, e.value))
return false;
}
return true;
}
public boolean clusteringKeyRestrictionsAreSatisfiedBy(Clustering clustering)
{
for (Expression e : expressions)
{
if (!e.column.isClusteringColumn())
continue;
if (!e.operator().isSatisfiedBy(e.column.type, clustering.get(e.column.position()), e.value))
{
return false;
}
}
return true;
}
public RowFilter without(Expression expression)
{
assert expressions.contains(expression);
if (expressions.size() == 1)
return RowFilter.NONE;
List<Expression> newExpressions = new ArrayList<>(expressions.size() - 1);
for (Expression e : expressions)
if (!e.equals(expression))
newExpressions.add(e);
return withNewExpressions(newExpressions);
}
public RowFilter withoutExpressions()
{
return withNewExpressions(Collections.emptyList());
}
protected abstract RowFilter withNewExpressions(List<Expression> expressions);
public boolean isEmpty()
{
return expressions.isEmpty();
}
public Iterator<Expression> iterator()
{
return expressions.iterator();
}
private static Clustering makeCompactClustering(CFMetaData metadata, ByteBuffer name)
{
assert metadata.isCompactTable();
if (metadata.isCompound())
{
List<ByteBuffer> values = CompositeType.splitName(name);
return Clustering.make(values.toArray(new ByteBuffer[metadata.comparator.size()]));
}
else
{
return Clustering.make(name);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expressions.size(); i++)
{
if (i > 0)
sb.append(" AND ");
sb.append(expressions.get(i));
}
return sb.toString();
}
private static class CQLFilter extends RowFilter
{
private CQLFilter(List<Expression> expressions)
{
super(expressions);
}
public UnfilteredPartitionIterator filter(UnfilteredPartitionIterator iter, int nowInSec)
{
if (expressions.isEmpty())
return iter;
final CFMetaData metadata = iter.metadata();
List<Expression> partitionLevelExpressions = new ArrayList<>();
List<Expression> rowLevelExpressions = new ArrayList<>();
for (Expression e: expressions)
{
if (e.column.isStatic() || e.column.isPartitionKey())
partitionLevelExpressions.add(e);
else
rowLevelExpressions.add(e);
}
long numberOfRegularColumnExpressions = rowLevelExpressions.size();
final boolean filterNonStaticColumns = numberOfRegularColumnExpressions > 0;
class IsSatisfiedFilter extends Transformation<UnfilteredRowIterator>
{
DecoratedKey pk;
public UnfilteredRowIterator applyToPartition(UnfilteredRowIterator partition)
{
pk = partition.partitionKey();
for (Expression e : partitionLevelExpressions)
if (!e.isSatisfiedBy(metadata, partition.partitionKey(), partition.staticRow()))
{
partition.close();
return null;
}
UnfilteredRowIterator iterator = Transformation.apply(partition, this);
if (filterNonStaticColumns && !iterator.hasNext())
{
iterator.close();
return null;
}
return iterator;
}
public Row applyToRow(Row row)
{
Row purged = row.purge(DeletionPurger.PURGE_ALL, nowInSec, metadata.enforceStrictLiveness());
if (purged == null)
return null;
for (Expression e : rowLevelExpressions)
if (!e.isSatisfiedBy(metadata, pk, purged))
return null;
return row;
}
}
return Transformation.apply(iter, new IsSatisfiedFilter());
}
protected RowFilter withNewExpressions(List<Expression> expressions)
{
return new CQLFilter(expressions);
}
}
private static class ThriftFilter extends RowFilter
{
private ThriftFilter(List<Expression> expressions)
{
super(expressions);
}
public UnfilteredPartitionIterator filter(UnfilteredPartitionIterator iter, final int nowInSec)
{
if (expressions.isEmpty())
return iter;
class IsSatisfiedThriftFilter extends Transformation<UnfilteredRowIterator>
{
@Override
public UnfilteredRowIterator applyToPartition(UnfilteredRowIterator iter)
{
ImmutableBTreePartition result = ImmutableBTreePartition.create(iter);
iter.close();
for (Expression expr : expressions)
{
assert expr instanceof ThriftExpression;
Row row = result.getRow(makeCompactClustering(iter.metadata(), expr.column().name.bytes));
if (row == null || !expr.isSatisfiedBy(iter.metadata(), iter.partitionKey(), row))
return null;
}
return result.unfilteredIterator();
}
}
return Transformation.apply(iter, new IsSatisfiedThriftFilter());
}
protected RowFilter withNewExpressions(List<Expression> expressions)
{
return new ThriftFilter(expressions);
}
}
public static abstract class Expression
{
private static final Serializer serializer = new Serializer();
protected enum Kind { SIMPLE, MAP_EQUALITY, THRIFT_DYN_EXPR, CUSTOM, USER }
protected abstract Kind kind();
protected final ColumnDefinition column;
protected final Operator operator;
protected final ByteBuffer value;
protected Expression(ColumnDefinition column, Operator operator, ByteBuffer value)
{
this.column = column;
this.operator = operator;
this.value = value;
}
public boolean isCustom()
{
return kind() == Kind.CUSTOM;
}
public boolean isUserDefined()
{
return kind() == Kind.USER;
}
public ColumnDefinition column()
{
return column;
}
public Operator operator()
{
return operator;
}
public boolean isContains()
{
return Operator.CONTAINS == operator;
}
public boolean isContainsKey()
{
return Operator.CONTAINS_KEY == operator;
}
public ByteBuffer getIndexValue()
{
return value;
}
public void validate()
{
checkNotNull(value, "Unsupported null value for column %s", column.name);
checkBindValueSet(value, "Unsupported unset value for column %s", column.name);
}
@Deprecated
public void validateForIndexing()
{
checkFalse(value.remaining() > FBUtilities.MAX_UNSIGNED_SHORT,
"Index expression values may not be larger than 64K");
}
public abstract boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row);
protected ByteBuffer getValue(CFMetaData metadata, DecoratedKey partitionKey, Row row)
{
switch (column.kind)
{
case PARTITION_KEY:
return metadata.getKeyValidator() instanceof CompositeType
? CompositeType.extractComponent(partitionKey.getKey(), column.position())
: partitionKey.getKey();
case CLUSTERING:
return row.clustering().get(column.position());
default:
Cell cell = row.getCell(column);
return cell == null ? null : cell.value();
}
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof Expression))
return false;
Expression that = (Expression)o;
return Objects.equal(this.kind(), that.kind())
&& Objects.equal(this.column.name, that.column.name)
&& Objects.equal(this.operator, that.operator)
&& Objects.equal(this.value, that.value);
}
@Override
public int hashCode()
{
return Objects.hashCode(column.name, operator, value);
}
private static class Serializer
{
public void serialize(Expression expression, DataOutputPlus out, int version) throws IOException
{
if (version >= MessagingService.VERSION_30)
out.writeByte(expression.kind().ordinal());
if (expression.kind() == Kind.CUSTOM)
{
assert version >= MessagingService.VERSION_30;
IndexMetadata.serializer.serialize(((CustomExpression)expression).targetIndex, out, version);
ByteBufferUtil.writeWithShortLength(expression.value, out);
return;
}
if (expression.kind() == Kind.USER)
{
assert version >= MessagingService.VERSION_30;
UserExpression.serialize((UserExpression)expression, out, version);
return;
}
ByteBufferUtil.writeWithShortLength(expression.column.name.bytes, out);
expression.operator.writeTo(out);
switch (expression.kind())
{
case SIMPLE:
ByteBufferUtil.writeWithShortLength(((SimpleExpression)expression).value, out);
break;
case MAP_EQUALITY:
MapEqualityExpression mexpr = (MapEqualityExpression)expression;
if (version < MessagingService.VERSION_30)
{
ByteBufferUtil.writeWithShortLength(mexpr.getIndexValue(), out);
}
else
{
ByteBufferUtil.writeWithShortLength(mexpr.key, out);
ByteBufferUtil.writeWithShortLength(mexpr.value, out);
}
break;
case THRIFT_DYN_EXPR:
ByteBufferUtil.writeWithShortLength(((ThriftExpression)expression).value, out);
break;
}
}
public Expression deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
Kind kind = null;
ByteBuffer name;
Operator operator;
ColumnDefinition column;
if (version >= MessagingService.VERSION_30)
{
kind = Kind.values()[in.readByte()];
if (kind == Kind.CUSTOM)
{
return new CustomExpression(metadata,
IndexMetadata.serializer.deserialize(in, version, metadata),
ByteBufferUtil.readWithShortLength(in));
}
if (kind == Kind.USER)
{
return UserExpression.deserialize(in, version, metadata);
}
}
name = ByteBufferUtil.readWithShortLength(in);
operator = Operator.readFrom(in);
column = metadata.getColumnDefinition(name);
if (!metadata.isCompactTable() && column == null)
throw new RuntimeException("Unknown (or dropped) column " + UTF8Type.instance.getString(name) + " during deserialization");
if (version < MessagingService.VERSION_30)
{
if (column == null)
kind = Kind.THRIFT_DYN_EXPR;
else if (column.type instanceof MapType && operator == Operator.EQ)
kind = Kind.MAP_EQUALITY;
else
kind = Kind.SIMPLE;
}
assert kind != null;
switch (kind)
{
case SIMPLE:
return new SimpleExpression(column, operator, ByteBufferUtil.readWithShortLength(in));
case MAP_EQUALITY:
ByteBuffer key, value;
if (version < MessagingService.VERSION_30)
{
ByteBuffer composite = ByteBufferUtil.readWithShortLength(in);
key = CompositeType.extractComponent(composite, 0);
value = CompositeType.extractComponent(composite, 0);
}
else
{
key = ByteBufferUtil.readWithShortLength(in);
value = ByteBufferUtil.readWithShortLength(in);
}
return new MapEqualityExpression(column, key, operator, value);
case THRIFT_DYN_EXPR:
return new ThriftExpression(metadata, name, operator, ByteBufferUtil.readWithShortLength(in));
}
throw new AssertionError();
}
public long serializedSize(Expression expression, int version)
{
long size = version >= MessagingService.VERSION_30 ? 1 : 0;
if (expression.kind() != Kind.CUSTOM && expression.kind() != Kind.USER)
size += ByteBufferUtil.serializedSizeWithShortLength(expression.column().name.bytes)
+ expression.operator.serializedSize();
switch (expression.kind())
{
case SIMPLE:
size += ByteBufferUtil.serializedSizeWithShortLength(((SimpleExpression)expression).value);
break;
case MAP_EQUALITY:
MapEqualityExpression mexpr = (MapEqualityExpression)expression;
if (version < MessagingService.VERSION_30)
size += ByteBufferUtil.serializedSizeWithShortLength(mexpr.getIndexValue());
else
size += ByteBufferUtil.serializedSizeWithShortLength(mexpr.key)
+ ByteBufferUtil.serializedSizeWithShortLength(mexpr.value);
break;
case THRIFT_DYN_EXPR:
size += ByteBufferUtil.serializedSizeWithShortLength(((ThriftExpression)expression).value);
break;
case CUSTOM:
if (version >= MessagingService.VERSION_30)
size += IndexMetadata.serializer.serializedSize(((CustomExpression)expression).targetIndex, version)
+ ByteBufferUtil.serializedSizeWithShortLength(expression.value);
break;
case USER:
if (version >= MessagingService.VERSION_30)
size += UserExpression.serializedSize((UserExpression)expression, version);
}
return size;
}
}
}
public static class SimpleExpression extends Expression
{
SimpleExpression(ColumnDefinition column, Operator operator, ByteBuffer value)
{
super(column, operator, value);
}
public boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row)
{
assert value != null;
switch (operator)
{
case EQ:
case LT:
case LTE:
case GTE:
case GT:
{
assert !column.isComplex() : "Only CONTAINS and CONTAINS_KEY are supported for 'complex' types";
if (column.type.isCounter())
{
ByteBuffer foundValue = getValue(metadata, partitionKey, row);
if (foundValue == null)
return false;
ByteBuffer counterValue = LongType.instance.decompose(CounterContext.instance().total(foundValue));
return operator.isSatisfiedBy(LongType.instance, counterValue, value);
}
else
{
ByteBuffer foundValue = getValue(metadata, partitionKey, row);
return foundValue != null && operator.isSatisfiedBy(column.type, foundValue, value);
}
}
case NEQ:
case LIKE_PREFIX:
case LIKE_SUFFIX:
case LIKE_CONTAINS:
case LIKE_MATCHES:
{
assert !column.isComplex() : "Only CONTAINS and CONTAINS_KEY are supported for 'complex' types";
ByteBuffer foundValue = getValue(metadata, partitionKey, row);
return foundValue != null && operator.isSatisfiedBy(column.type, foundValue, value);
}
case CONTAINS:
assert column.type.isCollection();
CollectionType<?> type = (CollectionType<?>)column.type;
if (column.isComplex())
{
ComplexColumnData complexData = row.getComplexColumnData(column);
if (complexData != null)
{
for (Cell cell : complexData)
{
if (type.kind == CollectionType.Kind.SET)
{
if (type.nameComparator().compare(cell.path().get(0), value) == 0)
return true;
}
else
{
if (type.valueComparator().compare(cell.value(), value) == 0)
return true;
}
}
}
return false;
}
else
{
ByteBuffer foundValue = getValue(metadata, partitionKey, row);
if (foundValue == null)
return false;
switch (type.kind)
{
case LIST:
ListType<?> listType = (ListType<?>)type;
return listType.compose(foundValue).contains(listType.getElementsType().compose(value));
case SET:
SetType<?> setType = (SetType<?>)type;
return setType.compose(foundValue).contains(setType.getElementsType().compose(value));
case MAP:
MapType<?,?> mapType = (MapType<?, ?>)type;
return mapType.compose(foundValue).containsValue(mapType.getValuesType().compose(value));
}
throw new AssertionError();
}
case CONTAINS_KEY:
assert column.type.isCollection() && column.type instanceof MapType;
MapType<?, ?> mapType = (MapType<?, ?>)column.type;
if (column.isComplex())
{
return row.getCell(column, CellPath.create(value)) != null;
}
else
{
ByteBuffer foundValue = getValue(metadata, partitionKey, row);
return foundValue != null && mapType.getSerializer().getSerializedValue(foundValue, value, mapType.getKeysType()) != null;
}
case IN:
throw new AssertionError();
}
throw new AssertionError();
}
@Override
public String toString()
{
AbstractType<?> type = column.type;
switch (operator)
{
case CONTAINS:
assert type instanceof CollectionType;
CollectionType<?> ct = (CollectionType<?>)type;
type = ct.kind == CollectionType.Kind.SET ? ct.nameComparator() : ct.valueComparator();
break;
case CONTAINS_KEY:
assert type instanceof MapType;
type = ((MapType<?, ?>)type).nameComparator();
break;
case IN:
type = ListType.getInstance(type, false);
break;
default:
break;
}
return String.format("%s %s %s", column.name, operator, type.getString(value));
}
@Override
protected Kind kind()
{
return Kind.SIMPLE;
}
}
private static class MapEqualityExpression extends Expression
{
private final ByteBuffer key;
public MapEqualityExpression(ColumnDefinition column, ByteBuffer key, Operator operator, ByteBuffer value)
{
super(column, operator, value);
assert column.type instanceof MapType && operator == Operator.EQ;
this.key = key;
}
@Override
public void validate() throws InvalidRequestException
{
checkNotNull(key, "Unsupported null map key for column %s", column.name);
checkBindValueSet(key, "Unsupported unset map key for column %s", column.name);
checkNotNull(value, "Unsupported null map value for column %s", column.name);
checkBindValueSet(value, "Unsupported unset map value for column %s", column.name);
}
@Override
public ByteBuffer getIndexValue()
{
return CompositeType.build(key, value);
}
public boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row)
{
assert key != null;
assert value != null;
if (row.isStatic() != column.isStatic())
return true;
MapType<?, ?> mt = (MapType<?, ?>)column.type;
if (column.isComplex())
{
Cell cell = row.getCell(column, CellPath.create(key));
return cell != null && mt.valueComparator().compare(cell.value(), value) == 0;
}
else
{
ByteBuffer serializedMap = getValue(metadata, partitionKey, row);
if (serializedMap == null)
return false;
ByteBuffer foundValue = mt.getSerializer().getSerializedValue(serializedMap, key, mt.getKeysType());
return foundValue != null && mt.valueComparator().compare(foundValue, value) == 0;
}
}
@Override
public String toString()
{
MapType<?, ?> mt = (MapType<?, ?>)column.type;
return String.format("%s[%s] = %s", column.name, mt.nameComparator().getString(key), mt.valueComparator().getString(value));
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof MapEqualityExpression))
return false;
MapEqualityExpression that = (MapEqualityExpression)o;
return Objects.equal(this.column.name, that.column.name)
&& Objects.equal(this.operator, that.operator)
&& Objects.equal(this.key, that.key)
&& Objects.equal(this.value, that.value);
}
@Override
public int hashCode()
{
return Objects.hashCode(column.name, operator, key, value);
}
@Override
protected Kind kind()
{
return Kind.MAP_EQUALITY;
}
}
private static class ThriftExpression extends Expression
{
public ThriftExpression(CFMetaData metadata, ByteBuffer name, Operator operator, ByteBuffer value)
{
super(makeDefinition(metadata, name), operator, value);
assert metadata.isCompactTable();
}
private static ColumnDefinition makeDefinition(CFMetaData metadata, ByteBuffer name)
{
ColumnDefinition def = metadata.getColumnDefinition(name);
if (def != null)
return def;
return ColumnDefinition.regularDef(metadata, name, metadata.compactValueColumn().type);
}
public boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row)
{
assert value != null;
assert row.clustering().equals(makeCompactClustering(metadata, column.name.bytes));
Cell cell = row.getCell(metadata.compactValueColumn());
return cell != null && operator.isSatisfiedBy(column.type, cell.value(), value);
}
@Override
public String toString()
{
return String.format("%s %s %s", column.name, operator, column.type.getString(value));
}
@Override
protected Kind kind()
{
return Kind.THRIFT_DYN_EXPR;
}
}
public static final class CustomExpression extends Expression
{
private final IndexMetadata targetIndex;
private final CFMetaData cfm;
public CustomExpression(CFMetaData cfm, IndexMetadata targetIndex, ByteBuffer value)
{
super(makeDefinition(cfm, targetIndex), Operator.EQ, value);
this.targetIndex = targetIndex;
this.cfm = cfm;
}
private static ColumnDefinition makeDefinition(CFMetaData cfm, IndexMetadata index)
{
return ColumnDefinition.regularDef(cfm, ByteBuffer.wrap(index.name.getBytes()), BytesType.instance);
}
public IndexMetadata getTargetIndex()
{
return targetIndex;
}
public ByteBuffer getValue()
{
return value;
}
public String toString()
{
return String.format("expr(%s, %s)",
targetIndex.name,
Keyspace.openAndGetStore(cfm)
.indexManager
.getIndex(targetIndex)
.customExpressionValueType());
}
protected Kind kind()
{
return Kind.CUSTOM;
}
public boolean isSatisfiedBy(CFMetaData metadata, DecoratedKey partitionKey, Row row)
{
return true;
}
}
public static abstract class UserExpression extends Expression
{
private static final DeserializerRegistry deserializers = new DeserializerRegistry();
private static final class DeserializerRegistry
{
private final AtomicInteger counter = new AtomicInteger(0);
private final ConcurrentMap<Integer, Deserializer> deserializers = new ConcurrentHashMap<>();
private final ConcurrentMap<Class<? extends UserExpression>, Integer> registeredClasses = new ConcurrentHashMap<>();
public void registerUserExpressionClass(Class<? extends UserExpression> expressionClass,
UserExpression.Deserializer deserializer)
{
int id = registeredClasses.computeIfAbsent(expressionClass, (cls) -> counter.getAndIncrement());
deserializers.put(id, deserializer);
logger.debug("Registered user defined expression type {} and serializer {} with identifier {}",
expressionClass.getName(), deserializer.getClass().getName(), id);
}
public Integer getId(UserExpression expression)
{
return registeredClasses.get(expression.getClass());
}
public Deserializer getDeserializer(int id)
{
return deserializers.get(id);
}
}
protected static abstract class Deserializer
{
protected abstract UserExpression deserialize(DataInputPlus in,
int version,
CFMetaData metadata) throws IOException;
}
public static void register(Class<? extends UserExpression> expressionClass, Deserializer deserializer)
{
deserializers.registerUserExpressionClass(expressionClass, deserializer);
}
private static UserExpression deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
int id = in.readInt();
Deserializer deserializer = deserializers.getDeserializer(id);
assert deserializer != null : "No user defined expression type registered with id " + id;
return deserializer.deserialize(in, version, metadata);
}
private static void serialize(UserExpression expression, DataOutputPlus out, int version) throws IOException
{
Integer id = deserializers.getId(expression);
assert id != null : "User defined expression type " + expression.getClass().getName() + " is not registered";
out.writeInt(id);
expression.serialize(out, version);
}
private static long serializedSize(UserExpression expression, int version)
{
return 4 + expression.serializedSize(version);
}
protected UserExpression(ColumnDefinition column, Operator operator, ByteBuffer value)
{
super(column, operator, value);
}
protected Kind kind()
{
return Kind.USER;
}
protected abstract void serialize(DataOutputPlus out, int version) throws IOException;
protected abstract long serializedSize(int version);
}
public static class Serializer
{
public void serialize(RowFilter filter, DataOutputPlus out, int version) throws IOException
{
out.writeBoolean(filter instanceof ThriftFilter);
out.writeUnsignedVInt(filter.expressions.size());
for (Expression expr : filter.expressions)
Expression.serializer.serialize(expr, out, version);
}
public RowFilter deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
boolean forThrift = in.readBoolean();
int size = (int)in.readUnsignedVInt();
List<Expression> expressions = new ArrayList<>(size);
for (int i = 0; i < size; i++)
expressions.add(Expression.serializer.deserialize(in, version, metadata));
return forThrift
? new ThriftFilter(expressions)
: new CQLFilter(expressions);
}
public long serializedSize(RowFilter filter, int version)
{
long size = 1
+ TypeSizes.sizeofUnsignedVInt(filter.expressions.size());
for (Expression expr : filter.expressions)
size += Expression.serializer.serializedSize(expr, version);
return size;
}
}
}