Use byte[] directly in QueryOptions instead of ByteBuffer and convert them to ArrayCell instead of BufferCell.

Additionally replace List with array for bind values (we know the size in advance during a decoding), so in total: List<List> is replaced with byte[][] QueryOptions classes support both ways to get values now: using an old API with ByteBuffer and a new API with byte[].

Patch by Dmitry Konstantinov; reviewed by Michael Semb Wever for CASSANDRA-20166
This commit is contained in:
Dmitry Konstantinov 2024-12-29 12:05:48 +00:00
parent 25390eb8d5
commit 8a72868066
32 changed files with 578 additions and 160 deletions

View File

@ -1,4 +1,5 @@
5.1 5.1
* Use byte[] directly in QueryOptions instead of ByteBuffer and convert them to ArrayCell instead of BufferCell to reduce allocations (CASSANDRA-20166)
* Log queries scanning too many SSTables per read (CASSANDRA-21048) * Log queries scanning too many SSTables per read (CASSANDRA-21048)
* Extend nodetool verify to (optionally) validate SAI files (CASSANDRA-20949) * Extend nodetool verify to (optionally) validate SAI files (CASSANDRA-20949)
* Fix CompressionDictionary being closed while still in use (CASSANDRA-21047) * Fix CompressionDictionary being closed while still in use (CASSANDRA-21047)

View File

@ -22,7 +22,6 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.nio.ByteBuffer;
import java.security.AccessControlContext; import java.security.AccessControlContext;
import java.security.AccessController; import java.security.AccessController;
import java.security.Principal; import java.security.Principal;
@ -317,7 +316,7 @@ public class AuditLogManager implements QueryEvents.Listener, AuthEvents.Listene
public void batchSuccess(BatchStatement.Type batchType, public void batchSuccess(BatchStatement.Type batchType,
List<? extends CQLStatement> statements, List<? extends CQLStatement> statements,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions options, QueryOptions options,
QueryState state, QueryState state,
long queryStartTimeMillis, long queryStartTimeMillis,
@ -358,7 +357,7 @@ public class AuditLogManager implements QueryEvents.Listener, AuthEvents.Listene
} }
} }
public void batchFailure(BatchStatement.Type batchType, List<? extends CQLStatement> statements, List<String> queries, List<List<ByteBuffer>> values, QueryOptions options, QueryState state, Exception cause) public void batchFailure(BatchStatement.Type batchType, List<? extends CQLStatement> statements, List<String> queries, List<byte[][]> values, QueryOptions options, QueryState state, Exception cause)
{ {
String auditMessage = String.format("BATCH of %d statements at consistency %s", statements.size(), options.getConsistency()); String auditMessage = String.format("BATCH of %d statements at consistency %s", statements.size(), options.getConsistency());
AuditLogEntry entry = new AuditLogEntry.Builder(state).setOperation(auditMessage) AuditLogEntry entry = new AuditLogEntry.Builder(state).setOperation(auditMessage)

View File

@ -31,6 +31,8 @@ import org.apache.cassandra.service.QueryState;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import static org.apache.cassandra.utils.ByteArrayUtil.convertToByteBufferValue;
public abstract class BatchQueryOptions public abstract class BatchQueryOptions
{ {
public static BatchQueryOptions DEFAULT = withoutPerStatementVariables(QueryOptions.DEFAULT); public static BatchQueryOptions DEFAULT = withoutPerStatementVariables(QueryOptions.DEFAULT);
@ -49,7 +51,7 @@ public abstract class BatchQueryOptions
return new WithoutPerStatementVariables(options, Collections.<Object>emptyList()); return new WithoutPerStatementVariables(options, Collections.<Object>emptyList());
} }
public static BatchQueryOptions withPerStatementVariables(QueryOptions options, List<List<ByteBuffer>> variables, List<Object> queryOrIdList) public static BatchQueryOptions withPerStatementVariables(QueryOptions options, List<byte[][]> variables, List<Object> queryOrIdList)
{ {
return new WithPerStatementVariables(options, variables, queryOrIdList); return new WithPerStatementVariables(options, variables, queryOrIdList);
} }
@ -91,6 +93,50 @@ public abstract class BatchQueryOptions
return wrapped.getNowInSeconds(state); return wrapped.getNowInSeconds(state);
} }
private static class BatchQueryOptionsWrapper extends QueryOptions.QueryOptionsWrapper {
private final byte[][] valuesAsByteArray;
private List<ByteBuffer> values; // initialized on demand
BatchQueryOptionsWrapper(QueryOptions wrapped, byte[][] vars)
{
super(wrapped);
this.valuesAsByteArray = vars;
}
public List<ByteBuffer> getValues()
{
if (values == null)
{
values = new ArrayList<>(valuesAsByteArray.length);
for (byte[] byteArrayValue : valuesAsByteArray)
values.add(convertToByteBufferValue(byteArrayValue));
}
return values;
}
public int getValuesSize()
{
return valuesAsByteArray.length;
}
public ByteBuffer getValue(int index)
{
if (values == null) // we convert values to ByteBuffer in a lazy way, on demand
return convertToByteBufferValue(valuesAsByteArray[index]);
else
return values.get(index);
}
public boolean isByteArrayValuesGetSupported()
{
return true;
}
public byte[][] getByteArrayValues()
{
return valuesAsByteArray;
}
}
private static class WithoutPerStatementVariables extends BatchQueryOptions private static class WithoutPerStatementVariables extends BatchQueryOptions
{ {
private WithoutPerStatementVariables(QueryOptions wrapped, List<Object> queryOrIdList) private WithoutPerStatementVariables(QueryOptions wrapped, List<Object> queryOrIdList)
@ -108,20 +154,12 @@ public abstract class BatchQueryOptions
{ {
private final List<QueryOptions> perStatementOptions; private final List<QueryOptions> perStatementOptions;
private WithPerStatementVariables(QueryOptions wrapped, List<List<ByteBuffer>> variables, List<Object> queryOrIdList) private WithPerStatementVariables(QueryOptions wrapped, List<byte[][]> variables, List<Object> queryOrIdList)
{ {
super(wrapped, queryOrIdList); super(wrapped, queryOrIdList);
this.perStatementOptions = new ArrayList<>(variables.size()); this.perStatementOptions = new ArrayList<>(variables.size());
for (final List<ByteBuffer> vars : variables) for (final byte[][] vars : variables)
{ perStatementOptions.add(new BatchQueryOptionsWrapper(wrapped, vars));
perStatementOptions.add(new QueryOptions.QueryOptionsWrapper(wrapped)
{
public List<ByteBuffer> getValues()
{
return vars;
}
});
}
} }
public QueryOptions forStatement(int i) public QueryOptions forStatement(int i)

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.cql3; package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -143,10 +142,14 @@ public class QueryEvents
} }
} }
/**
* Note: we use {@code byte[][]} for values of batch statement elements instead of {@code List<ByteBuffer>} here
* to reduce memory allocation when we decode and process a batch statement
*/
public void notifyBatchSuccess(BatchStatement.Type batchType, public void notifyBatchSuccess(BatchStatement.Type batchType,
List<? extends CQLStatement> statements, List<? extends CQLStatement> statements,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions options, QueryOptions options,
QueryState state, QueryState state,
long queryTime, long queryTime,
@ -164,10 +167,14 @@ public class QueryEvents
} }
} }
/**
* Note: we use {@code byte[][]} for values of batch statement elements instead of {@code List<ByteBuffer>} here
* to reduce memory allocation when we decode and process a batch statement
*/
public void notifyBatchFailure(List<QueryHandler.Prepared> prepared, public void notifyBatchFailure(List<QueryHandler.Prepared> prepared,
BatchStatement.Type batchType, BatchStatement.Type batchType,
List<Object> queryOrIdList, List<Object> queryOrIdList,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions options, QueryOptions options,
QueryState state, QueryState state,
Exception cause) Exception cause)
@ -288,7 +295,7 @@ public class QueryEvents
default void batchSuccess(BatchStatement.Type batchType, default void batchSuccess(BatchStatement.Type batchType,
List<? extends CQLStatement> statements, List<? extends CQLStatement> statements,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions options, QueryOptions options,
QueryState state, QueryState state,
long queryTime, long queryTime,
@ -296,7 +303,7 @@ public class QueryEvents
default void batchFailure(BatchStatement.Type batchType, default void batchFailure(BatchStatement.Type batchType,
List<? extends CQLStatement> statements, List<? extends CQLStatement> statements,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions options, QueryOptions options,
QueryState state, QueryState state,
Exception cause) {} Exception cause) {}

View File

@ -37,11 +37,14 @@ import org.apache.cassandra.transport.CBCodec;
import org.apache.cassandra.transport.CBUtil; import org.apache.cassandra.transport.CBUtil;
import org.apache.cassandra.transport.ProtocolException; import org.apache.cassandra.transport.ProtocolException;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.CassandraUInt; import org.apache.cassandra.utils.CassandraUInt;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import static org.apache.cassandra.utils.ByteArrayUtil.convertToByteBufferValue;
/** /**
* Options for a query. * Options for a query.
*/ */
@ -49,6 +52,7 @@ public abstract class QueryOptions
{ {
public static final QueryOptions DEFAULT = new DefaultQueryOptions(ConsistencyLevel.ONE, public static final QueryOptions DEFAULT = new DefaultQueryOptions(ConsistencyLevel.ONE,
Collections.emptyList(), Collections.emptyList(),
ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS,
false, false,
SpecificOptions.DEFAULT, SpecificOptions.DEFAULT,
ProtocolVersion.CURRENT); ProtocolVersion.CURRENT);
@ -62,22 +66,22 @@ public abstract class QueryOptions
public static QueryOptions forInternalCalls(ConsistencyLevel consistency, List<ByteBuffer> values) public static QueryOptions forInternalCalls(ConsistencyLevel consistency, List<ByteBuffer> values)
{ {
return new DefaultQueryOptions(consistency, values, false, SpecificOptions.DEFAULT, ProtocolVersion.V3); return new DefaultQueryOptions(consistency, values, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, false, SpecificOptions.DEFAULT, ProtocolVersion.V3);
} }
public static QueryOptions forInternalCallsWithNowInSec(long nowInSec, ConsistencyLevel consistency, List<ByteBuffer> values) public static QueryOptions forInternalCallsWithNowInSec(long nowInSec, ConsistencyLevel consistency, List<ByteBuffer> values)
{ {
return new DefaultQueryOptions(consistency, values, false, SpecificOptions.DEFAULT.withNowInSec(nowInSec), ProtocolVersion.CURRENT); return new DefaultQueryOptions(consistency, values, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, false, SpecificOptions.DEFAULT.withNowInSec(nowInSec), ProtocolVersion.CURRENT);
} }
public static QueryOptions forInternalCalls(List<ByteBuffer> values) public static QueryOptions forInternalCalls(List<ByteBuffer> values)
{ {
return new DefaultQueryOptions(ConsistencyLevel.ONE, values, false, SpecificOptions.DEFAULT, ProtocolVersion.V3); return new DefaultQueryOptions(ConsistencyLevel.ONE, values, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, false, SpecificOptions.DEFAULT, ProtocolVersion.V3);
} }
public static QueryOptions forProtocolVersion(ProtocolVersion protocolVersion) public static QueryOptions forProtocolVersion(ProtocolVersion protocolVersion)
{ {
return new DefaultQueryOptions(null, null, true, null, protocolVersion); return new DefaultQueryOptions(null, null, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, true, null, protocolVersion);
} }
public static QueryOptions create(ConsistencyLevel consistency, public static QueryOptions create(ConsistencyLevel consistency,
@ -105,6 +109,7 @@ public abstract class QueryOptions
{ {
return new DefaultQueryOptions(consistency, return new DefaultQueryOptions(consistency,
values, values,
ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS,
skipMetadata, skipMetadata,
new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds), new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds),
version); version);
@ -127,6 +132,34 @@ public abstract class QueryOptions
public abstract ConsistencyLevel getConsistency(); public abstract ConsistencyLevel getConsistency();
public abstract List<ByteBuffer> getValues(); public abstract List<ByteBuffer> getValues();
public abstract int getValuesSize();
public ByteBuffer getValue(int index)
{
return getValues().get(index);
}
/**
* to check if it is possible to get values as byte[] instead of ByteBuffer
* the logic is added as a memory allocation optimization
* to avoid creating of HeapByteBuffer wrappers for some typical requests
* getValues method is still in use and must be supported even if true is returned
*/
public boolean isByteArrayValuesGetSupported()
{
return false;
}
/**
* an allocation-optimized version of getValues() method
*/
public byte[][] getByteArrayValues()
{
throw new IllegalStateException("getByteArrayValues() method is not implemented, " +
"isByteArrayValuesGetSupported() must be always checked before invoking this method");
}
public abstract boolean skipMetadata(); public abstract boolean skipMetadata();
/** /**
@ -338,7 +371,9 @@ public abstract class QueryOptions
static class DefaultQueryOptions extends QueryOptions static class DefaultQueryOptions extends QueryOptions
{ {
private final ConsistencyLevel consistency; private final ConsistencyLevel consistency;
private final List<ByteBuffer> values; private List<ByteBuffer> values; // initialized on demand
private final byte[][] valuesAsByteArray;
private final boolean isByteArrayGetSupported;
private final boolean skipMetadata; private final boolean skipMetadata;
private final SpecificOptions options; private final SpecificOptions options;
@ -346,10 +381,14 @@ public abstract class QueryOptions
private final transient ProtocolVersion protocolVersion; private final transient ProtocolVersion protocolVersion;
private final transient ReadThresholds readThresholds = ReadThresholds.create(); private final transient ReadThresholds readThresholds = ReadThresholds.create();
DefaultQueryOptions(ConsistencyLevel consistency, List<ByteBuffer> values, boolean skipMetadata, SpecificOptions options, ProtocolVersion protocolVersion) DefaultQueryOptions(ConsistencyLevel consistency, List<ByteBuffer> values,
byte[][] valuesAsByteArray, boolean skipMetadata,
SpecificOptions options, ProtocolVersion protocolVersion)
{ {
this.consistency = consistency; this.consistency = consistency;
this.values = values; this.values = values;
isByteArrayGetSupported = (values == null);
this.valuesAsByteArray = valuesAsByteArray;
this.skipMetadata = skipMetadata; this.skipMetadata = skipMetadata;
this.options = options; this.options = options;
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
@ -362,9 +401,38 @@ public abstract class QueryOptions
public List<ByteBuffer> getValues() public List<ByteBuffer> getValues()
{ {
if (values == null)
{
values = new ArrayList<>(valuesAsByteArray.length);
for (byte[] byteArrayValue : valuesAsByteArray)
values.add(convertToByteBufferValue(byteArrayValue));
}
return values; return values;
} }
public int getValuesSize()
{
return isByteArrayValuesGetSupported() ? valuesAsByteArray.length : values.size();
}
public ByteBuffer getValue(int index)
{
if (values != null)
return values.get(index);
return convertToByteBufferValue(valuesAsByteArray[index]);
}
public boolean isByteArrayValuesGetSupported()
{
return isByteArrayGetSupported;
}
public byte[][] getByteArrayValues()
{
return valuesAsByteArray;
}
public boolean skipMetadata() public boolean skipMetadata()
{ {
return skipMetadata; return skipMetadata;
@ -406,6 +474,26 @@ public abstract class QueryOptions
return wrapped.getConsistency(); return wrapped.getConsistency();
} }
public int getValuesSize()
{
return wrapped.getValuesSize();
}
public ByteBuffer getValue(int index)
{
return this.wrapped.getValue(index);
}
public boolean isByteArrayValuesGetSupported()
{
return wrapped.isByteArrayValuesGetSupported();
}
public byte[][] getByteArrayValues()
{
return wrapped.getByteArrayValues();
}
public boolean skipMetadata() public boolean skipMetadata()
{ {
return wrapped.skipMetadata(); return wrapped.skipMetadata();
@ -625,19 +713,19 @@ public abstract class QueryOptions
? (int)body.readUnsignedInt() ? (int)body.readUnsignedInt()
: (int)body.readUnsignedByte(); : (int)body.readUnsignedByte();
List<ByteBuffer> values = Collections.<ByteBuffer>emptyList(); byte[][] values = ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS;
List<String> names = null; List<String> names = null;
if (Flag.contains(flags, Flag.VALUES)) if (Flag.contains(flags, Flag.VALUES))
{ {
if (Flag.contains(flags, Flag.NAMES_FOR_VALUES)) if (Flag.contains(flags, Flag.NAMES_FOR_VALUES))
{ {
Pair<List<String>, List<ByteBuffer>> namesAndValues = CBUtil.readNameAndValueList(body, version); Pair<List<String>, byte[][]> namesAndValues = CBUtil.readNameAndValueListAsByteArrays(body, version);
names = namesAndValues.left; names = namesAndValues.left;
values = namesAndValues.right; values = namesAndValues.right;
} }
else else
{ {
values = CBUtil.readValueList(body, version); values = CBUtil.readValueListAsByteArrays(body, version);
} }
} }
@ -665,7 +753,7 @@ public abstract class QueryOptions
options = new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds); options = new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds);
} }
DefaultQueryOptions opts = new DefaultQueryOptions(consistency, values, skipMetadata, options, version); DefaultQueryOptions opts = new DefaultQueryOptions(consistency, null, values, skipMetadata, options, version);
return names == null ? opts : new OptionsWithNames(opts, names); return names == null ? opts : new OptionsWithNames(opts, names);
} }

View File

@ -914,19 +914,19 @@ public class QueryProcessor implements QueryHandler
public ResultMessage processPrepared(CQLStatement statement, QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) public ResultMessage processPrepared(CQLStatement statement, QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime)
throws RequestExecutionException, RequestValidationException throws RequestExecutionException, RequestValidationException
{ {
List<ByteBuffer> variables = options.getValues(); int variablesSize = options.getValuesSize();
// Check to see if there are any bound variables to verify // Check to see if there are any bound variables to verify
if (!(variables.isEmpty() && statement.getBindVariables().isEmpty())) if (!(variablesSize == 0 && statement.getBindVariables().isEmpty()))
{ {
if (variables.size() != statement.getBindVariables().size()) if (variablesSize != statement.getBindVariables().size())
throw new InvalidRequestException(String.format("there were %d markers(?) in CQL but %d bound variables", throw new InvalidRequestException(String.format("there were %d markers(?) in CQL but %d bound variables",
statement.getBindVariables().size(), statement.getBindVariables().size(),
variables.size())); variablesSize));
// at this point there is a match in count between markers and variables that is non-zero // at this point there is a match in count between markers and variables that is non-zero
if (logger.isTraceEnabled()) if (logger.isTraceEnabled())
for (int i = 0; i < variables.size(); i++) for (int i = 0; i < variablesSize; i++)
logger.trace("[{}] '{}'", i+1, variables.get(i)); logger.trace("[{}] '{}'", i+1, options.getValues().get(i));
} }
metrics.preparedStatementsExecuted.inc(); metrics.preparedStatementsExecuted.inc();

View File

@ -31,6 +31,7 @@ import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.context.CounterContext; import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.partitions.Partition; import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.rows.ArrayCell;
import org.apache.cassandra.db.rows.BTreeRow; import org.apache.cassandra.db.rows.BTreeRow;
import org.apache.cassandra.db.rows.BufferCell; import org.apache.cassandra.db.rows.BufferCell;
import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.Cell;
@ -165,16 +166,14 @@ public class UpdateParameters
return addCell(column, null, value); return addCell(column, null, value);
} }
public Cell<?> addCell(ColumnMetadata column, byte[] value) throws InvalidRequestException
{
return addCell(column, null, value);
}
public Cell<?> addCell(ColumnMetadata column, CellPath path, ByteBuffer value) throws InvalidRequestException public Cell<?> addCell(ColumnMetadata column, CellPath path, ByteBuffer value) throws InvalidRequestException
{ {
// General column value size validateCell(column, path, value.remaining());
Guardrails.columnValueSize.guard(value.remaining(), column.name.toString(), false, clientState);
// Check specific sizes per column type
validateColumnSize(column, value);
if (path != null && column.type.isMultiCell())
Guardrails.columnValueSize.guard(path.dataSize(), column.name.toString(), false, clientState);
Cell<?> cell = ttl == LivenessInfo.NO_TTL Cell<?> cell = ttl == LivenessInfo.NO_TTL
? BufferCell.live(column, timestamp, value, path) ? BufferCell.live(column, timestamp, value, path)
@ -183,6 +182,29 @@ public class UpdateParameters
return cell; return cell;
} }
public Cell<?> addCell(ColumnMetadata column, CellPath path, byte[] value) throws InvalidRequestException
{
validateCell(column, path, value.length);
Cell<?> cell = ttl == LivenessInfo.NO_TTL
? ArrayCell.live(column, timestamp, value, path)
: ArrayCell.expiring(column, timestamp, ttl, nowInSec, value, path);
builder.addCell(cell);
return cell;
}
private void validateCell(ColumnMetadata column, CellPath path, int valueSize)
{
// General column value size
Guardrails.columnValueSize.guard(valueSize, column.name.toString(), false, clientState);
// Check specific sizes per column type
validateColumnSize(column, valueSize);
if (path != null && column.type.isMultiCell())
Guardrails.columnValueSize.guard(path.dataSize(), column.name.toString(), false, clientState);
}
public void addRow(Row row) public void addRow(Row row)
{ {
newRow(row.clustering()); newRow(row.clustering());
@ -206,20 +228,20 @@ public class UpdateParameters
}); });
} }
private void validateColumnSize(ColumnMetadata column, ByteBuffer value) private void validateColumnSize(ColumnMetadata column, int valueSize)
{ {
CQL3Type cql3Type = column.type.asCQL3Type(); CQL3Type cql3Type = column.type.asCQL3Type();
if (cql3Type.equals(CQL3Type.Native.ASCII)) // Ascii size specific guardrail if (cql3Type.equals(CQL3Type.Native.ASCII)) // Ascii size specific guardrail
{ {
Guardrails.columnAsciiValueSize.guard(value.remaining(), column.name.toString(), false, clientState); Guardrails.columnAsciiValueSize.guard(valueSize, column.name.toString(), false, clientState);
} }
else if (cql3Type.equals(CQL3Type.Native.BLOB)) // Blob size specific guardrail else if (cql3Type.equals(CQL3Type.Native.BLOB)) // Blob size specific guardrail
{ {
Guardrails.columnBlobValueSize.guard(value.remaining(), column.name.toString(), false, clientState); Guardrails.columnBlobValueSize.guard(valueSize, column.name.toString(), false, clientState);
} }
else if (cql3Type.equals(CQL3Type.Native.TEXT)) // text and varchar size specific guardrails else if (cql3Type.equals(CQL3Type.Native.TEXT)) // text and varchar size specific guardrails
{ {
Guardrails.columnTextAndVarcharValueSize.guard(value.remaining(), column.name.toString(), false, clientState); Guardrails.columnTextAndVarcharValueSize.guard(valueSize, column.name.toString(), false, clientState);
} }
} }

View File

@ -52,6 +52,7 @@ import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FastByteOperations; import org.apache.cassandra.utils.FastByteOperations;
@ -479,11 +480,22 @@ public abstract class Constants
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{ {
ByteBuffer value = t.bindAndGet(params.options); if (t.isByteArrayGetSupported(params.options))
if (value == null) {
params.addTombstone(column); byte[] value = t.bindAndGetByteArray(params.options);
else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER) // use reference equality and not object equality if (value == null)
params.addCell(column, value); params.addTombstone(column);
else if (value != ByteArrayUtil.UNSET_BYTE_ARRAY) // use reference equality and not object equality
params.addCell(column, value);
}
else
{
ByteBuffer value = t.bindAndGet(params.options);
if (value == null)
params.addTombstone(column);
else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER) // use reference equality and not object equality
params.addCell(column, value);
}
} }
} }

View File

@ -26,9 +26,11 @@ import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.VariableSpecifications; import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.ByteArrayAccessor;
import org.apache.cassandra.db.marshal.MultiElementType; import org.apache.cassandra.db.marshal.MultiElementType;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
/** /**
@ -74,7 +76,7 @@ public final class Marker extends Term.NonTerminal
{ {
try try
{ {
ByteBuffer bytes = options.getValues().get(bindIndex); ByteBuffer bytes = options.getValue(bindIndex);
if (bytes == null) if (bytes == null)
return null; return null;
@ -93,6 +95,40 @@ public final class Marker extends Term.NonTerminal
} }
} }
public boolean isByteArrayGetSupported(QueryOptions options)
{
return options.isByteArrayValuesGetSupported();
}
/*
Same logic as bind() but it returns byte[] instead of ByteBuffer and there is no Value wrapper usage
*/
@Override
public byte[] bindAndGetByteArray(QueryOptions options) throws InvalidRequestException
{
try
{
byte[] bytes = options.getByteArrayValues()[bindIndex];
if (bytes == null)
return null;
if (bytes == ByteArrayUtil.UNSET_BYTE_ARRAY)
return ByteArrayUtil.UNSET_BYTE_ARRAY;
if (receiver.type instanceof MultiElementType<?>)
{
MultiElementType<?> type = (MultiElementType<?>) receiver.type;
return type.pack(type.filterSortAndValidateElementsFromArrays(type.unpack(bytes)), ByteArrayAccessor.instance);
}
receiver.type.validate(bytes, ByteArrayAccessor.instance);
return bytes;
}
catch (MarshalException e)
{
throw new InvalidRequestException(e.getMessage());
}
}
@Override @Override
public String toString() public String toString()
{ {

View File

@ -89,6 +89,24 @@ public interface Term
*/ */
ByteBuffer bindAndGet(QueryOptions options); ByteBuffer bindAndGet(QueryOptions options);
/**
* return true if an optimized bindAndGetByteArray() method is implemented and can be used for this type of Term
* to retrieve a value as byte[] instead of default ByteBuffer provided by bindAndGet()
*/
default boolean isByteArrayGetSupported(QueryOptions options)
{
return false;
}
/**
* an allocation-optimized version of bindAndGet() method
*/
default byte[] bindAndGetByteArray(QueryOptions options) throws InvalidRequestException
{
throw new IllegalStateException("bindAndGetByteArray() method is not implemented, " +
"isByteArrayGetSupported() must be always checked before invoking this method");
}
/** /**
* A shorter for {@code bind(options).getElements()}. * A shorter for {@code bind(options).getElements()}.
* We expose it mainly because for constants it can avoid allocating a temporary * We expose it mainly because for constants it can avoid allocating a temporary

View File

@ -280,6 +280,18 @@ public class ListType<T> extends CollectionType<List<T>>
return buffers; return buffers;
} }
@Override
public List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers)
{
for (byte[] buffer: buffers)
{
if (buffer == null)
throw new MarshalException("null is not supported inside collections");
elements.validate(buffer, ByteArrayAccessor.instance);
}
return buffers;
}
@Override @Override
protected int compareNextCell(Iterator<Cell<?>> cellIterator, Iterator<ByteBuffer> elementIter) protected int compareNextCell(Iterator<Cell<?>> cellIterator, Iterator<ByteBuffer> elementIter)
{ {

View File

@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -383,26 +384,37 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
@Override @Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers) public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
return filterSortAndValidateElements(buffers, ByteBufferAccessor.instance, getKeysType().comparatorSet.buffer);
}
@Override
public List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers)
{
return filterSortAndValidateElements(buffers, ByteArrayAccessor.instance, getKeysType().comparatorSet.array);
}
private <T> List<T> filterSortAndValidateElements(List<T> buffers, ValueAccessor<T> valueAccessor, Comparator<T> comparator)
{ {
// We depend on Maps to be properly sorted by their keys, so use a sorted map implementation here. // We depend on Maps to be properly sorted by their keys, so use a sorted map implementation here.
SortedMap<ByteBuffer, ByteBuffer> map = new TreeMap<>(getKeysType()); SortedMap<T, T> map = new TreeMap<>(comparator);
Iterator<ByteBuffer> iter = buffers.iterator(); Iterator<T> iter = buffers.iterator();
while (iter.hasNext()) while (iter.hasNext())
{ {
ByteBuffer keyBytes = iter.next(); T keyBytes = iter.next();
ByteBuffer valueBytes = iter.next(); T valueBytes = iter.next();
if (keyBytes == null || valueBytes == null) if (keyBytes == null || valueBytes == null)
throw new MarshalException("null is not supported inside collections"); throw new MarshalException("null is not supported inside collections");
getKeysType().validate(keyBytes); getKeysType().validate(keyBytes, valueAccessor);
getValuesType().validate(valueBytes); getValuesType().validate(valueBytes, valueAccessor);
map.put(keyBytes, valueBytes); map.put(keyBytes, valueBytes);
} }
List<ByteBuffer> sortedBuffers = new ArrayList<>(map.size() << 1); List<T> sortedBuffers = new ArrayList<>(map.size() << 1);
for (Map.Entry<ByteBuffer, ByteBuffer> entry : map.entrySet()) for (Map.Entry<T, T> entry : map.entrySet())
{ {
sortedBuffers.add(entry.getKey()); sortedBuffers.add(entry.getKey());
sortedBuffers.add(entry.getValue()); sortedBuffers.add(entry.getValue());

View File

@ -99,6 +99,8 @@ public abstract class MultiElementType<T> extends AbstractType<T>
*/ */
public abstract List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers); public abstract List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers);
public abstract List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers);
/** /**
* Compares the multicell value represensted by the column data with the specified elements. * Compares the multicell value represensted by the column data with the specified elements.
* @param columnData the column data representing the multicell value * @param columnData the column data representing the multicell value

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -255,12 +256,23 @@ public class SetType<T> extends CollectionType<Set<T>>
@Override @Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers) public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{ {
SortedSet<ByteBuffer> sorted = new TreeSet<>(elements); return filterSortAndValidateElements(buffers, ByteBufferAccessor.instance, elements.comparatorSet.buffer);
for (ByteBuffer buffer: buffers) }
@Override
public List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers)
{
return filterSortAndValidateElements(buffers, ByteArrayAccessor.instance, elements.comparatorSet.array);
}
private <V> List<V> filterSortAndValidateElements(List<V> buffers, ValueAccessor<V> valueAccessor, Comparator<V> comparator)
{
SortedSet<V> sorted = new TreeSet<>(comparator);
for (V buffer: buffers)
{ {
if (buffer == null) if (buffer == null)
throw new MarshalException("null is not supported inside collections"); throw new MarshalException("null is not supported inside collections");
elements.validate(buffer); elements.validate(buffer, valueAccessor);
sorted.add(buffer); sorted.add(buffer);
} }
return new ArrayList<>(sorted); return new ArrayList<>(sorted);

View File

@ -40,6 +40,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.*; import org.apache.cassandra.serializers.*;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.JsonUtils;
import org.apache.cassandra.utils.bytecomparable.ByteComparable; import org.apache.cassandra.utils.bytecomparable.ByteComparable;
@ -388,6 +389,17 @@ public class TupleType extends MultiElementType<ByteBuffer>
@Override @Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers) public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
return filterSortAndValidateElements(buffers, ByteBufferUtil.UNSET_BYTE_BUFFER, ByteBufferAccessor.instance);
}
@Override
public List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers)
{
return filterSortAndValidateElements(buffers, ByteArrayUtil.UNSET_BYTE_ARRAY, ByteArrayAccessor.instance);
}
private <T> List<T> filterSortAndValidateElements(List<T> buffers, T unsetValue, ValueAccessor<T> valueAccessor)
{ {
if (buffers.size() > size()) if (buffers.size() > size())
throw new MarshalException(String.format("Tuple value contains too many fields (expected %s, got %s)", size(), buffers.size())); throw new MarshalException(String.format("Tuple value contains too many fields (expected %s, got %s)", size(), buffers.size()));
@ -395,12 +407,12 @@ public class TupleType extends MultiElementType<ByteBuffer>
for (int i = 0; i < buffers.size(); i++) for (int i = 0; i < buffers.size(); i++)
{ {
// Since A tuple value is always written in its entirety Cassandra can't preserve a pre-existing value by 'not setting' the new value. Reject the query. // Since A tuple value is always written in its entirety Cassandra can't preserve a pre-existing value by 'not setting' the new value. Reject the query.
ByteBuffer buffer = buffers.get(i); T buffer = buffers.get(i);
if (buffer == null) if (buffer == null)
continue; continue;
if (buffer == ByteBufferUtil.UNSET_BYTE_BUFFER) if (buffer == unsetValue)
throw new InvalidRequestException(String.format("Invalid unset value for tuple field number %d", i)); throw new InvalidRequestException(String.format("Invalid unset value for tuple field number %d", i));
type(i).validate(buffer); type(i).validate(buffer, valueAccessor);
} }
return buffers; return buffers;

View File

@ -44,6 +44,7 @@ import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.UserTypeSerializer; import org.apache.cassandra.serializers.UserTypeSerializer;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.JsonUtils;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -644,6 +645,17 @@ public class UserType extends TupleType implements SchemaElement
@Override @Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers) public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
return filterSortAndValidateElements(buffers, ByteBufferUtil.UNSET_BYTE_BUFFER, ByteBufferAccessor.instance);
}
@Override
public List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers)
{
return filterSortAndValidateElements(buffers, ByteArrayUtil.UNSET_BYTE_ARRAY, ByteArrayAccessor.instance);
}
private <T> List<T> filterSortAndValidateElements(List<T> buffers, T unsetValue, ValueAccessor<T> valueAccessor)
{ {
if (buffers.size() > size()) if (buffers.size() > size())
throw new MarshalException(String.format("UDT value contained too many fields (expected %s, got %s)", size(), buffers.size())); throw new MarshalException(String.format("UDT value contained too many fields (expected %s, got %s)", size(), buffers.size()));
@ -652,12 +664,12 @@ public class UserType extends TupleType implements SchemaElement
{ {
// Since a frozen UDT value is always written in its entirety Cassandra can't preserve a pre-existing // Since a frozen UDT value is always written in its entirety Cassandra can't preserve a pre-existing
// value by 'not setting' the new value. Reject the query. // value by 'not setting' the new value. Reject the query.
ByteBuffer buffer = buffers.get(i); T buffer = buffers.get(i);
if (buffer == null) if (buffer == null)
continue; continue;
if (!isMultiCell() && buffer == ByteBufferUtil.UNSET_BYTE_BUFFER) if (!isMultiCell() && buffer == unsetValue)
throw new MarshalException(String.format("Invalid unset value for field '%s' of user defined type %s", fieldNameAsString(i), getNameAsString())); throw new MarshalException(String.format("Invalid unset value for field '%s' of user defined type %s", fieldNameAsString(i), getNameAsString()));
type(i).validate(buffer); type(i).validate(buffer, valueAccessor);
} }
return buffers; return buffers;

View File

@ -37,6 +37,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.JsonUtils;
import org.apache.cassandra.utils.bytecomparable.ByteComparable; import org.apache.cassandra.utils.bytecomparable.ByteComparable;
@ -194,20 +195,31 @@ public final class VectorType<T> extends MultiElementType<List<T>>
@Override @Override
public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers) public List<ByteBuffer> filterSortAndValidateElements(List<ByteBuffer> buffers)
{
return filterSortAndValidateElements(buffers, ByteBufferUtil.UNSET_BYTE_BUFFER, ByteBufferAccessor.instance);
}
@Override
public List<byte[]> filterSortAndValidateElementsFromArrays(List<byte[]> buffers)
{
return filterSortAndValidateElements(buffers, ByteArrayUtil.UNSET_BYTE_ARRAY, ByteArrayAccessor.instance);
}
public <V> List<V> filterSortAndValidateElements(List<V> buffers, V unsetValue, ValueAccessor<V> valueAccessor)
{ {
// We only filter and validate for this type. // We only filter and validate for this type.
if (buffers == null) if (buffers == null)
return null; return null;
for (ByteBuffer buffer: buffers) for (V buffer : buffers)
{ {
if (buffer == null || elementType.isNull(buffer)) if (buffer == null || elementType.isNull(buffer, valueAccessor))
throw new MarshalException("null is not supported inside vectors"); throw new MarshalException("null is not supported inside vectors");
if (buffer == ByteBufferUtil.UNSET_BYTE_BUFFER ) if (buffer == unsetValue)
throw new InvalidRequestException("unset is not supported inside vectors"); throw new InvalidRequestException("unset is not supported inside vectors");
elementType.validate(buffer); elementType.validate(buffer, valueAccessor);
} }
return buffers; return buffers;
} }

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.db.rows;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.apache.cassandra.db.ExpirationDateOverflowHandling;
import org.apache.cassandra.db.marshal.ByteArrayAccessor; import org.apache.cassandra.db.marshal.ByteArrayAccessor;
import org.apache.cassandra.db.marshal.ByteType; import org.apache.cassandra.db.marshal.ByteType;
import org.apache.cassandra.db.marshal.ValueAccessor; import org.apache.cassandra.db.marshal.ValueAccessor;
@ -59,6 +60,17 @@ public class ArrayCell extends AbstractCell<byte[]>
this.path = path; this.path = path;
} }
public static ArrayCell live(ColumnMetadata column, long timestamp, byte[] value, CellPath path)
{
return new ArrayCell(column, timestamp, NO_TTL, NO_DELETION_TIME, value, path);
}
public static ArrayCell expiring(ColumnMetadata column, long timestamp, int ttl, long nowInSec, byte[] value, CellPath path)
{
assert ttl != NO_TTL;
return new ArrayCell(column, timestamp, ttl, ExpirationDateOverflowHandling.computeLocalExpirationTime(nowInSec, ttl), value, path);
}
public long timestamp() public long timestamp()
{ {
return timestamp; return timestamp;

View File

@ -120,6 +120,11 @@ public abstract class Cell<V> extends ColumnData
return accessor().toBuffer(value()); return accessor().toBuffer(value());
} }
public byte[] valueAsArray()
{
return accessor().toArray(value());
}
/** /**
* The cell timestamp. * The cell timestamp.
* <p> * <p>

View File

@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import org.apache.cassandra.db.marshal.AddressBasedNativeData; import org.apache.cassandra.db.marshal.AddressBasedNativeData;
import org.apache.cassandra.db.marshal.ByteArrayAccessor;
import org.apache.cassandra.db.marshal.NativeAccessor; import org.apache.cassandra.db.marshal.NativeAccessor;
import org.apache.cassandra.db.marshal.NativeData; import org.apache.cassandra.db.marshal.NativeData;
import org.apache.cassandra.db.marshal.ValueAccessor; import org.apache.cassandra.db.marshal.ValueAccessor;
@ -66,18 +67,37 @@ public class NativeCell extends AbstractCell<NativeData> implements NativeData
this.peer = 0; this.peer = 0;
} }
public NativeCell(AddressBasedAllocator allocator, public static NativeCell build(AddressBasedAllocator allocator,
OpOrder.Group writeOp, OpOrder.Group writeOp,
Cell<?> cell) Cell<?> cell)
{ {
this(allocator, if (cell.accessor() == ByteArrayAccessor.instance) // to avoid ByteBuffer allocation via cell.value()
writeOp, {
cell.column(), byte[] value = cell.valueAsArray();
cell.timestamp(), return new NativeCell(allocator,
cell.ttl(), writeOp,
cell.localDeletionTimeAsUnsignedInt(), cell.column(),
cell.buffer(), cell.timestamp(),
cell.path()); cell.ttl(),
cell.localDeletionTimeAsUnsignedInt(),
value,
value.length,
cell.path());
}
else
{
ByteBuffer byteBuffer = cell.buffer();
assert byteBuffer.order() == ByteOrder.BIG_ENDIAN;
return new NativeCell(allocator,
writeOp,
cell.column(),
cell.timestamp(),
cell.ttl(),
cell.localDeletionTimeAsUnsignedInt(),
byteBuffer,
byteBuffer.remaining(),
cell.path());
}
} }
// Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not // Please keep both int/long overloaded ctros public. Otherwise silent casts will mess timestamps when one is not
@ -91,7 +111,7 @@ public class NativeCell extends AbstractCell<NativeData> implements NativeData
ByteBuffer value, ByteBuffer value,
CellPath path) CellPath path)
{ {
this(allocator, writeOp, column, timestamp, ttl, deletionTimeLongToUnsignedInteger(localDeletionTime), value, path); this(allocator, writeOp, column, timestamp, ttl, deletionTimeLongToUnsignedInteger(localDeletionTime), value, value.remaining(), path);
} }
public NativeCell(AddressBasedAllocator allocator, public NativeCell(AddressBasedAllocator allocator,
@ -100,13 +120,13 @@ public class NativeCell extends AbstractCell<NativeData> implements NativeData
long timestamp, long timestamp,
int ttl, int ttl,
int localDeletionTimeUnsignedInteger, int localDeletionTimeUnsignedInteger,
ByteBuffer value, Object value,
int valueLength,
CellPath path) CellPath path)
{ {
super(column); super(column);
long size = offHeapSizeWithoutPath(value.remaining()); long size = offHeapSizeWithoutPath(valueLength);
assert value.order() == ByteOrder.BIG_ENDIAN;
assert column.isComplex() == (path != null); assert column.isComplex() == (path != null);
if (path != null) if (path != null)
{ {
@ -123,15 +143,20 @@ public class NativeCell extends AbstractCell<NativeData> implements NativeData
NativeEndianMemoryUtil.setLong(peer + TIMESTAMP, timestamp); NativeEndianMemoryUtil.setLong(peer + TIMESTAMP, timestamp);
NativeEndianMemoryUtil.setInt(peer + TTL, ttl); NativeEndianMemoryUtil.setInt(peer + TTL, ttl);
NativeEndianMemoryUtil.setInt(peer + DELETION, localDeletionTimeUnsignedInteger); NativeEndianMemoryUtil.setInt(peer + DELETION, localDeletionTimeUnsignedInteger);
NativeEndianMemoryUtil.setInt(peer + LENGTH, value.remaining()); NativeEndianMemoryUtil.setInt(peer + LENGTH, valueLength);
MemoryUtil.setBytes(peer + VALUE, value); if (value instanceof byte[])
MemoryUtil.setBytes(peer + VALUE, (byte[]) value, 0, valueLength);
else if (value instanceof ByteBuffer)
MemoryUtil.setBytes(peer + VALUE, (ByteBuffer) value);
else
throw new IllegalArgumentException();
if (path != null) if (path != null)
{ {
ByteBuffer pathbuffer = path.get(0); ByteBuffer pathbuffer = path.get(0);
assert pathbuffer.order() == ByteOrder.BIG_ENDIAN; assert pathbuffer.order() == ByteOrder.BIG_ENDIAN;
long offset = peer + VALUE + value.remaining(); long offset = peer + VALUE + valueLength;
NativeEndianMemoryUtil.setInt(offset, pathbuffer.remaining()); NativeEndianMemoryUtil.setInt(offset, pathbuffer.remaining());
MemoryUtil.setBytes(offset + 4, pathbuffer); MemoryUtil.setBytes(offset + 4, pathbuffer);
} }

View File

@ -17,7 +17,6 @@
*/ */
package org.apache.cassandra.fql; package org.apache.cassandra.fql;
import java.nio.ByteBuffer;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -268,7 +267,7 @@ public class FullQueryLogger implements QueryEvents.Listener
public void batchSuccess(BatchStatement.Type type, public void batchSuccess(BatchStatement.Type type,
List<? extends CQLStatement> statements, List<? extends CQLStatement> statements,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions queryOptions, QueryOptions queryOptions,
QueryState queryState, QueryState queryState,
long batchTimeMillis, long batchTimeMillis,
@ -383,11 +382,11 @@ public class FullQueryLogger implements QueryEvents.Listener
private final int weight; private final int weight;
private final BatchStatement.Type batchType; private final BatchStatement.Type batchType;
private final List<String> queries; private final List<String> queries;
private final List<List<ByteBuffer>> values; private final List<byte[][]> values;
public Batch(BatchStatement.Type batchType, public Batch(BatchStatement.Type batchType,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions queryOptions, QueryOptions queryOptions,
QueryState queryState, QueryState queryState,
long batchTimeMillis) long batchTimeMillis)
@ -406,11 +405,11 @@ public class FullQueryLogger implements QueryEvents.Listener
queriesSize += ObjectSizes.sizeOf(checkNotNull(query)); queriesSize += ObjectSizes.sizeOf(checkNotNull(query));
long valuesSize = EMPTY_LIST_SIZE + ObjectSizes.sizeOfReferenceArray(values.size()); long valuesSize = EMPTY_LIST_SIZE + ObjectSizes.sizeOfReferenceArray(values.size());
for (List<ByteBuffer> subValues : values) for (byte[][] subValues : values)
{ {
valuesSize += EMPTY_LIST_SIZE + ObjectSizes.sizeOfReferenceArray(subValues.size()); valuesSize += EMPTY_LIST_SIZE + ObjectSizes.sizeOfReferenceArray(subValues.length);
for (ByteBuffer subValue : subValues) for (byte[] subValue : subValues)
valuesSize += ObjectSizes.sizeOnHeapOf(subValue); valuesSize += ObjectSizes.sizeOfArray(subValue);
} }
// No need to add the batch type which is an enum. // No need to add the batch type which is an enum.
@ -450,10 +449,10 @@ public class FullQueryLogger implements QueryEvents.Listener
} }
valueOut = wire.write(VALUES); valueOut = wire.write(VALUES);
valueOut.int32(values.size()); valueOut.int32(values.size());
for (List<ByteBuffer> subValues : values) for (byte[][] subValues : values)
{ {
valueOut.int32(subValues.size()); valueOut.int32(subValues.length);
for (ByteBuffer value : subValues) for (byte[] value : subValues)
valueOut.bytes(value == null ? null : BytesStore.wrap(value)); valueOut.bytes(value == null ? null : BytesStore.wrap(value));
} }
} }

View File

@ -42,6 +42,7 @@ import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.LazyToString; import org.apache.cassandra.utils.LazyToString;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -568,6 +569,35 @@ public abstract class CBUtil
return l; return l;
} }
public static byte[][] readValueListAsByteArrays(ByteBuf cb, ProtocolVersion protocolVersion)
{
int size = cb.readUnsignedShort();
if (size == 0)
return ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS;
byte[][] l = new byte[size][];
for (int i = 0; i < size; i++)
l[i] = readBoundValueAsByteArray(cb, protocolVersion);
return l;
}
public static byte[] readBoundValueAsByteArray(ByteBuf cb, ProtocolVersion protocolVersion)
{
int length = cb.readInt();
if (length < 0)
{
if (protocolVersion.isSmallerThan(ProtocolVersion.V4)) // backward compatibility for pre-version 4
return null;
if (length == -1)
return null;
else if (length == -2)
return ByteArrayUtil.UNSET_BYTE_ARRAY;
else
throw new ProtocolException("Invalid ByteBuf length " + length);
}
return readRawBytes(cb, length);
}
public static void writeValueList(List<ByteBuffer> values, ByteBuf cb) public static void writeValueList(List<ByteBuffer> values, ByteBuf cb)
{ {
cb.writeShort(values.size()); cb.writeShort(values.size());
@ -575,6 +605,13 @@ public abstract class CBUtil
CBUtil.writeValue(value, cb); CBUtil.writeValue(value, cb);
} }
public static void writeValueListOfByteArrays(byte[][] values, ByteBuf cb)
{
cb.writeShort(values.length);
for (byte[] value : values)
CBUtil.writeValue(value, cb);
}
public static int sizeOfValueList(List<ByteBuffer> values) public static int sizeOfValueList(List<ByteBuffer> values)
{ {
int size = 2; int size = 2;
@ -583,6 +620,14 @@ public abstract class CBUtil
return size; return size;
} }
public static int sizeOfValueListOfByteArrays(byte[][] values)
{
int size = 2;
for (byte[] value : values)
size += CBUtil.sizeOfValue(value);
return size;
}
public static Pair<List<String>, List<ByteBuffer>> readNameAndValueList(ByteBuf cb, ProtocolVersion protocolVersion) public static Pair<List<String>, List<ByteBuffer>> readNameAndValueList(ByteBuf cb, ProtocolVersion protocolVersion)
{ {
int size = cb.readUnsignedShort(); int size = cb.readUnsignedShort();
@ -599,6 +644,22 @@ public abstract class CBUtil
return Pair.create(s, l); return Pair.create(s, l);
} }
public static Pair<List<String>, byte[][]> readNameAndValueListAsByteArrays(ByteBuf cb, ProtocolVersion protocolVersion)
{
int size = cb.readUnsignedShort();
if (size == 0)
return Pair.create(Collections.<String>emptyList(), ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS);
List<String> s = new ArrayList<>(size);
byte[][] l = new byte[size][];
for (int i = 0; i < size; i++)
{
s.add(readString(cb));
l[i] = readBoundValueAsByteArray(cb, protocolVersion);
}
return Pair.create(s, l);
}
public static InetSocketAddress readInet(ByteBuf cb) public static InetSocketAddress readInet(ByteBuf cb)
{ {
int addrSize = cb.readByte() & 0xFF; int addrSize = cb.readByte() & 0xFF;

View File

@ -17,7 +17,6 @@
*/ */
package org.apache.cassandra.transport.messages; package org.apache.cassandra.transport.messages;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -58,7 +57,7 @@ public class BatchMessage extends Message.Request
byte type = body.readByte(); byte type = body.readByte();
int n = body.readUnsignedShort(); int n = body.readUnsignedShort();
List<Object> queryOrIds = new ArrayList<>(n); List<Object> queryOrIds = new ArrayList<>(n);
List<List<ByteBuffer>> variables = new ArrayList<>(n); List<byte[][]> variables = new ArrayList<>(n);
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
{ {
byte kind = body.readByte(); byte kind = body.readByte();
@ -68,7 +67,7 @@ public class BatchMessage extends Message.Request
queryOrIds.add(MD5Digest.wrap(CBUtil.readBytes(body))); queryOrIds.add(MD5Digest.wrap(CBUtil.readBytes(body)));
else else
throw new ProtocolException("Invalid query kind in BATCH messages. Must be 0 or 1 but got " + kind); throw new ProtocolException("Invalid query kind in BATCH messages. Must be 0 or 1 but got " + kind);
variables.add(CBUtil.readValueList(body, version)); variables.add(CBUtil.readValueListAsByteArrays(body, version));
} }
QueryOptions options = QueryOptions.codec.decode(body, version); QueryOptions options = QueryOptions.codec.decode(body, version);
@ -91,7 +90,7 @@ public class BatchMessage extends Message.Request
else else
CBUtil.writeBytes(((MD5Digest)q).bytes, dest); CBUtil.writeBytes(((MD5Digest)q).bytes, dest);
CBUtil.writeValueList(msg.values.get(i), dest); CBUtil.writeValueListOfByteArrays(msg.values.get(i), dest);
} }
if (version.isSmallerThan(ProtocolVersion.V3)) if (version.isSmallerThan(ProtocolVersion.V3))
@ -110,7 +109,7 @@ public class BatchMessage extends Message.Request
? CBUtil.sizeOfLongString((String)q) ? CBUtil.sizeOfLongString((String)q)
: CBUtil.sizeOfBytes(((MD5Digest)q).bytes)); : CBUtil.sizeOfBytes(((MD5Digest)q).bytes));
size += CBUtil.sizeOfValueList(msg.values.get(i)); size += CBUtil.sizeOfValueListOfByteArrays(msg.values.get(i));
} }
size += version.isSmallerThan(ProtocolVersion.V3) size += version.isSmallerThan(ProtocolVersion.V3)
? CBUtil.sizeOfConsistencyLevel(msg.options.getConsistency()) ? CBUtil.sizeOfConsistencyLevel(msg.options.getConsistency())
@ -145,10 +144,10 @@ public class BatchMessage extends Message.Request
public final BatchStatement.Type batchType; public final BatchStatement.Type batchType;
public final List<Object> queryOrIdList; public final List<Object> queryOrIdList;
public final List<List<ByteBuffer>> values; public final List<byte[][]> values;
public final QueryOptions options; public final QueryOptions options;
public BatchMessage(BatchStatement.Type type, List<Object> queryOrIdList, List<List<ByteBuffer>> values, QueryOptions options) public BatchMessage(BatchStatement.Type type, List<Object> queryOrIdList, List<byte[][]> values, QueryOptions options)
{ {
super(Message.Type.BATCH); super(Message.Type.BATCH);
this.batchType = type; this.batchType = type;
@ -197,11 +196,11 @@ public class BatchMessage extends Message.Request
throw new PreparedQueryNotFoundException((MD5Digest)query); throw new PreparedQueryNotFoundException((MD5Digest)query);
} }
List<ByteBuffer> queryValues = values.get(i); byte[][] queryValues = values.get(i);
if (queryValues.size() != p.statement.getBindVariables().size()) if (queryValues.length != p.statement.getBindVariables().size())
throw new InvalidRequestException(String.format("There were %d markers(?) in CQL but %d bound variables", throw new InvalidRequestException(String.format("There were %d markers(?) in CQL but %d bound variables",
p.statement.getBindVariables().size(), p.statement.getBindVariables().size(),
queryValues.size())); queryValues.length));
prepared.add(p); prepared.add(p);
} }
@ -260,7 +259,7 @@ public class BatchMessage extends Message.Request
for (int i = 0; i < queryOrIdList.size(); i++) for (int i = 0; i < queryOrIdList.size(); i++)
{ {
if (i > 0) sb.append(", "); if (i > 0) sb.append(", ");
sb.append(queryOrIdList.get(i)).append(" with ").append(values.get(i).size()).append(" values"); sb.append(queryOrIdList.get(i)).append(" with ").append(values.get(i).length).append(" values");
} }
sb.append("] at consistency ").append(options.getConsistency()); sb.append("] at consistency ").append(options.getConsistency());
return sb.toString(); return sb.toString();

View File

@ -33,6 +33,8 @@ import org.apache.cassandra.io.util.DataOutputPlus;
public class ByteArrayUtil public class ByteArrayUtil
{ {
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static final byte[][] EMPTY_ARRAY_OF_BYTE_ARRAYS = new byte[0][];
public static final byte[] UNSET_BYTE_ARRAY = new byte[0];
public static int compareUnsigned(byte[] o1, byte[] o2) public static int compareUnsigned(byte[] o1, byte[] o2)
{ {
@ -277,4 +279,16 @@ public class ByteArrayUtil
{ {
FastByteOperations.copy(src, srcPos, dst, dstPos, length); FastByteOperations.copy(src, srcPos, dst, dstPos, length);
} }
public static ByteBuffer convertToByteBufferValue(byte[] arrayValue)
{
if (arrayValue == null)
return null;
if (arrayValue == ByteArrayUtil.UNSET_BYTE_ARRAY)
return ByteBufferUtil.UNSET_BYTE_BUFFER; // it is important for correctness to preserve unset value
if (arrayValue == ByteArrayUtil.EMPTY_BYTE_ARRAY)
return ByteBufferUtil.EMPTY_BYTE_BUFFER;
return ByteBuffer.wrap(arrayValue);
}
} }

View File

@ -87,7 +87,7 @@ public class NativeAllocator extends MemtableAllocator implements AddressBasedAl
@Override @Override
public void addCell(Cell<?> cell) public void addCell(Cell<?> cell)
{ {
super.addCell(new NativeCell(allocator, writeOp, cell)); super.addCell(NativeCell.build(allocator, writeOp, cell));
} }
} }
@ -153,7 +153,7 @@ public class NativeAllocator extends MemtableAllocator implements AddressBasedAl
@Override @Override
public Cell<?> clone(Cell<?> cell) public Cell<?> clone(Cell<?> cell)
{ {
return new NativeCell(this, opGroup, cell); return NativeCell.build(this, opGroup, cell);
} }
@Override @Override

View File

@ -18,14 +18,11 @@
package org.apache.cassandra.test.microbench; package org.apache.cassandra.test.microbench;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.google.common.collect.Lists;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.Attributes; import org.apache.cassandra.cql3.Attributes;
import org.apache.cassandra.cql3.BatchQueryOptions; import org.apache.cassandra.cql3.BatchQueryOptions;
@ -44,6 +41,7 @@ import org.apache.cassandra.schema.SchemaTestUtil;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.BenchmarkMode;
@ -64,8 +62,6 @@ import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.OptionsBuilder;
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
@BenchmarkMode(Mode.Throughput) @BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS) @OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@ -108,14 +104,14 @@ public class BatchStatementBench
SchemaTestUtil.addOrUpdateKeyspace(ksm.withSwapped(ksm.tables.with(metadata)), false); SchemaTestUtil.addOrUpdateKeyspace(ksm.withSwapped(ksm.tables.with(metadata)), false);
List<ModificationStatement> modifications = new ArrayList<>(batchSize); List<ModificationStatement> modifications = new ArrayList<>(batchSize);
List<List<ByteBuffer>> parameters = new ArrayList<>(batchSize); List<byte[][]> parameters = new ArrayList<>(batchSize);
List<Object> queryOrIdList = new ArrayList<>(batchSize); List<Object> queryOrIdList = new ArrayList<>(batchSize);
QueryHandler.Prepared prepared = QueryProcessor.prepareInternal(String.format("INSERT INTO %s.%s (id, ck, v) VALUES (?,?,?)", keyspace, table)); QueryHandler.Prepared prepared = QueryProcessor.prepareInternal(String.format("INSERT INTO %s.%s (id, ck, v) VALUES (?,?,?)", keyspace, table));
for (int i = 0; i < batchSize; i++) for (int i = 0; i < batchSize; i++)
{ {
modifications.add((ModificationStatement) prepared.statement); modifications.add((ModificationStatement) prepared.statement);
parameters.add(Lists.newArrayList(bytes(uniquePartition ? i : 1), bytes(i), bytes(i))); parameters.add(new byte[][] {ByteArrayUtil.bytes(uniquePartition ? i : 1), ByteArrayUtil.bytes(i), ByteArrayUtil.bytes(i)});
queryOrIdList.add(prepared.rawCQLStatement); queryOrIdList.add(prepared.rawCQLStatement);
} }
bs = new BatchStatement(BatchStatement.Type.UNLOGGED, VariableSpecifications.empty(), modifications, Attributes.none()); bs = new BatchStatement(BatchStatement.Type.UNLOGGED, VariableSpecifications.empty(), modifications, Attributes.none());

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.cql3; package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -39,8 +38,9 @@ import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.Message;
import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteArrayUtil;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@ -144,9 +144,9 @@ public class QueryEventsTest extends CQLTester
assertEquals(3, listener.queries.size()); assertEquals(3, listener.queries.size());
assertEquals(BatchStatement.Type.UNLOGGED, listener.batchType); assertEquals(BatchStatement.Type.UNLOGGED, listener.batchType);
assertEquals(newArrayList(q1, q1, q2), listener.queries); assertEquals(newArrayList(q1, q1, q2), listener.queries);
assertEquals(newArrayList(newArrayList(ByteBufferUtil.bytes(1), ByteBufferUtil.bytes(1)), assertListOfArraysEquals(newArrayList(new byte[][] {ByteArrayUtil.bytes(1), ByteArrayUtil.bytes(1)},
newArrayList(ByteBufferUtil.bytes(2), ByteBufferUtil.bytes(2)), new byte[][] {ByteArrayUtil.bytes(2), ByteArrayUtil.bytes(2)},
newArrayList(newArrayList())), listener.values); new byte[][] {}), listener.values);
batch.add(new SimpleStatement("insert into abc.def (id, v) values (1,1)")); batch.add(new SimpleStatement("insert into abc.def (id, v) values (1,1)"));
try try
@ -162,10 +162,17 @@ public class QueryEventsTest extends CQLTester
assertEquals(3, listener.queries.size()); assertEquals(3, listener.queries.size());
assertEquals(BatchStatement.Type.UNLOGGED, listener.batchType); assertEquals(BatchStatement.Type.UNLOGGED, listener.batchType);
assertEquals(newArrayList(q1, q1, q2), listener.queries); assertEquals(newArrayList(q1, q1, q2), listener.queries);
assertEquals(newArrayList(newArrayList(ByteBufferUtil.bytes(1), ByteBufferUtil.bytes(1)), assertListOfArraysEquals(newArrayList(new byte[][] { ByteArrayUtil.bytes(1), ByteArrayUtil.bytes(1)},
newArrayList(ByteBufferUtil.bytes(2), ByteBufferUtil.bytes(2)), new byte[][] {ByteArrayUtil.bytes(2), ByteArrayUtil.bytes(2)},
newArrayList(newArrayList()), new byte[][] {},
newArrayList(newArrayList())), listener.values); new byte[][] {}), listener.values);
}
private static void assertListOfArraysEquals(List<byte[][]> expected, List<byte[][]> actual)
{
assertEquals(expected.size(), actual.size());
for (int i = 0; i < expected.size(); i++)
assertArrayEquals(expected.get(i), actual.get(i));
} }
@Test @Test
@ -323,7 +330,7 @@ public class QueryEventsTest extends CQLTester
private static class BatchMockListener extends MockListener private static class BatchMockListener extends MockListener
{ {
private List<String> queries; private List<String> queries;
private List<List<ByteBuffer>> values; private List<byte[][]> values;
private BatchStatement.Type batchType; private BatchStatement.Type batchType;
BatchMockListener(ColumnFamilyStore currentColumnFamilyStore) BatchMockListener(ColumnFamilyStore currentColumnFamilyStore)
@ -331,7 +338,7 @@ public class QueryEventsTest extends CQLTester
super(currentColumnFamilyStore); super(currentColumnFamilyStore);
} }
public void batchSuccess(BatchStatement.Type batchType, List<? extends CQLStatement> statements, List<String> queries, List<List<ByteBuffer>> values, QueryOptions options, QueryState state, long queryTime, Message.Response response) public void batchSuccess(BatchStatement.Type batchType, List<? extends CQLStatement> statements, List<String> queries, List<byte[][]> values, QueryOptions options, QueryState state, long queryTime, Message.Response response)
{ {
inc("batchSuccess"); inc("batchSuccess");
this.queries = queries; this.queries = queries;
@ -340,7 +347,7 @@ public class QueryEventsTest extends CQLTester
this.queryTime = queryTime; this.queryTime = queryTime;
} }
public void batchFailure(BatchStatement.Type batchType, List<? extends CQLStatement> statements, List<String> queries, List<List<ByteBuffer>> values, QueryOptions options, QueryState state, Exception cause) public void batchFailure(BatchStatement.Type batchType, List<? extends CQLStatement> statements, List<String> queries, List<byte[][]> values, QueryOptions options, QueryState state, Exception cause)
{ {
inc("batchFailure"); inc("batchFailure");
this.queries = queries; this.queries = queries;

View File

@ -58,6 +58,7 @@ import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ObjectSizes; import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.binlog.BinLogTest; import org.apache.cassandra.utils.binlog.BinLogTest;
@ -462,9 +463,9 @@ public class FullQueryLoggerTest extends CQLTester
configureFQL(); configureFQL();
logBatch(Type.UNLOGGED, logBatch(Type.UNLOGGED,
Arrays.asList("foo1", "foo2"), Arrays.asList("foo1", "foo2"),
Arrays.asList(Arrays.asList(ByteBuffer.allocate(1), Arrays.asList(new byte[][] {new byte[1],
ByteBuffer.allocateDirect(2)), new byte[2]},
Collections.emptyList()), ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS),
QueryOptions.DEFAULT, QueryOptions.DEFAULT,
queryState("abcdefgh"), queryState("abcdefgh"),
1); 1);
@ -486,9 +487,9 @@ public class FullQueryLoggerTest extends CQLTester
configureFQL(); configureFQL();
logBatch(Type.UNLOGGED, logBatch(Type.UNLOGGED,
Arrays.asList("foo1", "foo2"), Arrays.asList("foo1", "foo2"),
Arrays.asList(Arrays.asList(ByteBuffer.allocate(1), Arrays.asList(new byte[][] {new byte[1],
ByteBuffer.allocateDirect(2)), new byte[2]},
Collections.emptyList()), ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS),
QueryOptions.DEFAULT, QueryOptions.DEFAULT,
queryState(), queryState(),
1); 1);
@ -599,12 +600,10 @@ public class FullQueryLoggerTest extends CQLTester
bigList = null; bigList = null;
//The size of the list of values should be reflected //The size of the list of values should be reflected
List<List<ByteBuffer>> bigValues = new ArrayList<>(100000); List<byte[][]> bigValues = new ArrayList<>(100000);
for (int ii = 0; ii < 100000; ii++) for (int ii = 0; ii < 100000; ii++)
{ bigValues.add(ii == 0 ? new byte[][] {new byte[1024 * 1024 * 5]} : new byte[][]{});
bigValues.add(new ArrayList<>(0));
}
bigValues.get(0).add(ByteBuffer.allocate(1024 * 1024 * 5));
batch = new Batch(Type.UNLOGGED, new ArrayList<>(), bigValues, QueryOptions.DEFAULT, queryState(), 1); batch = new Batch(Type.UNLOGGED, new ArrayList<>(), bigValues, QueryOptions.DEFAULT, queryState(), 1);
assertTrue(batch.weight() > ObjectSizes.measureDeep(bigValues)); assertTrue(batch.weight() > ObjectSizes.measureDeep(bigValues));
@ -642,7 +641,7 @@ public class FullQueryLoggerTest extends CQLTester
@Test(expected = NullPointerException.class) @Test(expected = NullPointerException.class)
public void testLogBatchNullValuesValue() throws Exception public void testLogBatchNullValuesValue() throws Exception
{ {
logBatch(Type.UNLOGGED, new ArrayList<>(), Arrays.asList((List<ByteBuffer>)null), null, queryState(), 1); logBatch(Type.UNLOGGED, new ArrayList<>(), Collections.singletonList(null), null, queryState(), 1);
} }
@Test(expected = NullPointerException.class) @Test(expected = NullPointerException.class)
@ -737,7 +736,7 @@ public class FullQueryLoggerTest extends CQLTester
private void logBatch(BatchStatement.Type type, private void logBatch(BatchStatement.Type type,
List<String> queries, List<String> queries,
List<List<ByteBuffer>> values, List<byte[][]> values,
QueryOptions options, QueryOptions options,
QueryState queryState, QueryState queryState,
long time) long time)

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.metrics; package org.apache.cassandra.metrics;
import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -37,6 +36,7 @@ import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.transport.SimpleClient; import org.apache.cassandra.transport.SimpleClient;
import org.apache.cassandra.transport.messages.BatchMessage; import org.apache.cassandra.transport.messages.BatchMessage;
import org.apache.cassandra.transport.messages.QueryMessage; import org.apache.cassandra.transport.messages.QueryMessage;
import org.apache.cassandra.utils.ByteArrayUtil;
import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRIC_SCOPE_UNDEFINED; import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRIC_SCOPE_UNDEFINED;
import static org.apache.cassandra.transport.ProtocolVersion.CURRENT; import static org.apache.cassandra.transport.ProtocolVersion.CURRENT;
@ -261,7 +261,7 @@ public class ClientRequestRowAndColumnMetricsTest extends CQLTester
String first = String.format("INSERT INTO %s.%s (pk, v1, v2) VALUES (1, 10, 100)", KEYSPACE, currentTable()); String first = String.format("INSERT INTO %s.%s (pk, v1, v2) VALUES (1, 10, 100)", KEYSPACE, currentTable());
String second = String.format("INSERT INTO %s.%s (pk, v1, v2) VALUES (2, 20, 200)", KEYSPACE, currentTable()); String second = String.format("INSERT INTO %s.%s (pk, v1, v2) VALUES (2, 20, 200)", KEYSPACE, currentTable());
List<List<ByteBuffer>> values = ImmutableList.of(Collections.emptyList(), Collections.emptyList()); List<byte[][]> values = ImmutableList.of(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS);
BatchMessage batch = new BatchMessage(BatchStatement.Type.LOGGED, ImmutableList.of(first, second), values, QueryOptions.DEFAULT); BatchMessage batch = new BatchMessage(BatchStatement.Type.LOGGED, ImmutableList.of(first, second), values, QueryOptions.DEFAULT);
client.execute(batch); client.execute(batch);
@ -316,7 +316,7 @@ public class ClientRequestRowAndColumnMetricsTest extends CQLTester
String first = String.format("INSERT INTO %s.%s (pk, ck, v0, v1, v2) VALUES (0, 1, 2, 3, 4)", KEYSPACE, currentTable()); String first = String.format("INSERT INTO %s.%s (pk, ck, v0, v1, v2) VALUES (0, 1, 2, 3, 4)", KEYSPACE, currentTable());
String second = String.format("INSERT INTO %s.%s (pk, ck, v0, v1, v2) VALUES (0, 2, 3, 5, 6)", KEYSPACE, currentTable()); String second = String.format("INSERT INTO %s.%s (pk, ck, v0, v1, v2) VALUES (0, 2, 3, 5, 6)", KEYSPACE, currentTable());
List<List<ByteBuffer>> values = ImmutableList.of(Collections.emptyList(), Collections.emptyList()); List<byte[][]> values = ImmutableList.of(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS);
BatchMessage batch = new BatchMessage(BatchStatement.Type.LOGGED, ImmutableList.of(first, second), values, QueryOptions.DEFAULT); BatchMessage batch = new BatchMessage(BatchStatement.Type.LOGGED, ImmutableList.of(first, second), values, QueryOptions.DEFAULT);
client.execute(batch); client.execute(batch);
@ -377,7 +377,7 @@ public class ClientRequestRowAndColumnMetricsTest extends CQLTester
String first = String.format("INSERT INTO %s.%s (pk, ck, v1, v2) VALUES (1, 2, 3, 4)", KEYSPACE, currentTable()); String first = String.format("INSERT INTO %s.%s (pk, ck, v1, v2) VALUES (1, 2, 3, 4)", KEYSPACE, currentTable());
String second = String.format("DELETE FROM %s.%s WHERE pk = 1 AND ck > 1", KEYSPACE, currentTable()); String second = String.format("DELETE FROM %s.%s WHERE pk = 1 AND ck > 1", KEYSPACE, currentTable());
List<List<ByteBuffer>> values = ImmutableList.of(Collections.emptyList(), Collections.emptyList()); List<byte[][]> values = ImmutableList.of(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS);
BatchMessage batch = new BatchMessage(BatchStatement.Type.LOGGED, ImmutableList.of(first, second), values, QueryOptions.DEFAULT); BatchMessage batch = new BatchMessage(BatchStatement.Type.LOGGED, ImmutableList.of(first, second), values, QueryOptions.DEFAULT);
client.execute(batch); client.execute(batch);

View File

@ -46,6 +46,7 @@ import org.apache.cassandra.transport.messages.ExecuteMessage;
import org.apache.cassandra.transport.messages.PrepareMessage; import org.apache.cassandra.transport.messages.PrepareMessage;
import org.apache.cassandra.transport.messages.QueryMessage; import org.apache.cassandra.transport.messages.QueryMessage;
import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.MD5Digest; import org.apache.cassandra.utils.MD5Digest;
import org.apache.cassandra.utils.ReflectionUtils; import org.apache.cassandra.utils.ReflectionUtils;
@ -172,7 +173,7 @@ public class MessagePayloadTest extends CQLTester
BatchMessage batchMessage = new BatchMessage(BatchStatement.Type.UNLOGGED, BatchMessage batchMessage = new BatchMessage(BatchStatement.Type.UNLOGGED,
Collections.<Object>singletonList("INSERT INTO " + KEYSPACE + ".atable (pk,v) VALUES (1, 'foo')"), Collections.<Object>singletonList("INSERT INTO " + KEYSPACE + ".atable (pk,v) VALUES (1, 'foo')"),
Collections.singletonList(Collections.<ByteBuffer>emptyList()), Collections.singletonList(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS),
queryOptions); queryOptions);
reqMap = Collections.singletonMap("foo", bytes(45)); reqMap = Collections.singletonMap("foo", bytes(45));
responsePayload = respMap = Collections.singletonMap("bar", bytes(45)); responsePayload = respMap = Collections.singletonMap("bar", bytes(45));
@ -241,7 +242,7 @@ public class MessagePayloadTest extends CQLTester
BatchMessage batchMessage = new BatchMessage(BatchStatement.Type.UNLOGGED, BatchMessage batchMessage = new BatchMessage(BatchStatement.Type.UNLOGGED,
Collections.<Object>singletonList("INSERT INTO " + KEYSPACE + ".atable (pk,v) VALUES (1, 'foo')"), Collections.<Object>singletonList("INSERT INTO " + KEYSPACE + ".atable (pk,v) VALUES (1, 'foo')"),
Collections.singletonList(Collections.<ByteBuffer>emptyList()), Collections.singletonList(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS),
QueryOptions.DEFAULT); QueryOptions.DEFAULT);
reqMap = Collections.singletonMap("foo", bytes(45)); reqMap = Collections.singletonMap("foo", bytes(45));
responsePayload = respMap = Collections.singletonMap("bar", bytes(45)); responsePayload = respMap = Collections.singletonMap("bar", bytes(45));
@ -342,7 +343,7 @@ public class MessagePayloadTest extends CQLTester
BatchMessage batchMessage = new BatchMessage(BatchStatement.Type.UNLOGGED, BatchMessage batchMessage = new BatchMessage(BatchStatement.Type.UNLOGGED,
Collections.<Object>singletonList("INSERT INTO " + KEYSPACE + ".atable (pk,v) VALUES (1, 'foo')"), Collections.<Object>singletonList("INSERT INTO " + KEYSPACE + ".atable (pk,v) VALUES (1, 'foo')"),
Collections.singletonList(Collections.<ByteBuffer>emptyList()), Collections.singletonList(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS),
QueryOptions.DEFAULT); QueryOptions.DEFAULT);
reqMap = Collections.singletonMap("foo", bytes(45)); reqMap = Collections.singletonMap("foo", bytes(45));
responsePayload = Collections.singletonMap("bar", bytes(45)); responsePayload = Collections.singletonMap("bar", bytes(45));

View File

@ -244,11 +244,15 @@ public abstract class FQLQuery implements Comparable<FQLQuery>
public BinLog.ReleaseableWriteMarshallable toMarshallable() public BinLog.ReleaseableWriteMarshallable toMarshallable()
{ {
List<String> queryStrings = new ArrayList<>(); List<String> queryStrings = new ArrayList<>();
List<List<ByteBuffer>> values = new ArrayList<>(); List<byte[][]> values = new ArrayList<>();
for (Single q : queries) for (Single q : queries)
{ {
queryStrings.add(q.query); queryStrings.add(q.query);
values.add(q.values); byte[][] valuesAsArray = new byte[q.values.size()][];
int i = 0;
for (ByteBuffer value : q.values)
valuesAsArray[i++] = value.array();
values.add(valuesAsArray);
} }
return new FullQueryLogger.Batch(org.apache.cassandra.cql3.statements.BatchStatement.Type.valueOf(batchType.name()), queryStrings, values, queryOptions, queryState, queryStartTime); return new FullQueryLogger.Batch(org.apache.cassandra.cql3.statements.BatchStatement.Type.valueOf(batchType.name()), queryStrings, values, queryOptions, queryState, queryStartTime);
} }

View File

@ -50,6 +50,7 @@ import org.apache.cassandra.fqltool.commands.Replay;
import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.tools.Util; import org.apache.cassandra.tools.Util;
import org.apache.cassandra.utils.ByteArrayUtil;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.MergeIterator; import org.apache.cassandra.utils.MergeIterator;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -705,11 +706,11 @@ public class FQLReplayTest
{ {
int batchSize = random ? r.nextInt(99) + 1 : i + 1; int batchSize = random ? r.nextInt(99) + 1 : i + 1;
List<String> queries = new ArrayList<>(batchSize); List<String> queries = new ArrayList<>(batchSize);
List<List<ByteBuffer>> values = new ArrayList<>(batchSize); List<byte[][]> values = new ArrayList<>(batchSize);
for (int jj = 0; jj < (random ? r.nextInt(batchSize) : 10); jj++) for (int jj = 0; jj < (random ? r.nextInt(batchSize) : 10); jj++)
{ {
queries.add("aaaaaa batch "+i+":"+jj); queries.add("aaaaaa batch "+i+":"+jj);
values.add(Collections.emptyList()); values.add(ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS);
} }
FullQueryLogger.Batch batch = new FullQueryLogger.Batch(BatchStatement.Type.UNLOGGED, FullQueryLogger.Batch batch = new FullQueryLogger.Batch(BatchStatement.Type.UNLOGGED,
queries, queries,