com.datastax.driver.core.SimpleStatement Maven / Gradle / Ivy
Show all versions of cassandra-driver-core Show documentation
/*
* Copyright (C) 2012 DataStax Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.driver.core;
import java.nio.ByteBuffer;
/**
* A simple {@code RegularStatement} implementation built directly from a query
* string.
*/
public class SimpleStatement extends RegularStatement {
private final String query;
private final ByteBuffer[] values;
private volatile ByteBuffer routingKey;
private volatile String keyspace;
/**
* Creates a new {@code SimpleStatement} with the provided query string (and no values).
*
* @param query the query string.
*/
public SimpleStatement(String query) {
this.query = query;
this.values = null;
}
/**
* Creates a new {@code SimpleStatement} with the provided query string and values.
*
* This version of SimpleStatement is useful when you do not want to execute a
* query only once (and thus do not want to resort to prepared statement), but
* do not want to convert all column values to string (typically, if you have blob
* values, encoding them to a hexadecimal string is not very efficient). In
* that case, you can provide a query string with bind marker to this constructor
* along with the values for those bind variables. When executed, the server will
* prepare the provided, bind the provided values to that prepare statement and
* execute the resulting statement. Thus,
*
* session.execute(new SimpleStatement(query, value1, value2, value3));
*
* is functionally equivalent to
*
* PreparedStatement ps = session.prepare(query);
* session.execute(ps.bind(value1, value2, value3));
*
* except that the former version:
*
* - Requires only one round-trip to a Cassandra node.
* - Does not left any prepared statement stored in memory (neither client or
* server side) once it has been executed.
*
*
* Note that the type of the {@code values} provided to this method will
* not be validated by the driver as is done by {@link BoundStatement#bind} since
* {@code query} is not parsed (and hence the driver cannot know what those value
* should be). If too much or too little values are provided or if a value is not
* a valid one for the variable it is bound to, an
* {@link com.datastax.driver.core.exceptions.InvalidQueryException} will be thrown
* by Cassandra at execution time. An {@code IllegalArgumentException} may be
* thrown by this constructor however if one of the value does not correspond to
* any CQL3 type (for instance, if it is a custom class).
*
* @param query the query string.
* @param values values required for the execution of {@code query}.
*
* @throws IllegalArgumentException if one of {@code values} is not of a type
* corresponding to a CQL3 type, i.e. is not a Class that could be returned
* by {@link DataType#asJavaClass}.
*/
public SimpleStatement(String query, Object... values) {
this.query = query;
this.values = convert(values);
}
private static ByteBuffer[] convert(Object[] values) {
ByteBuffer[] serializedValues = new ByteBuffer[values.length];
for (int i = 0; i < values.length; i++) {
try {
serializedValues[i] = DataType.serializeValue(values[i]);
} catch (IllegalArgumentException e) {
// Catch and rethrow to provide a more helpful error message (one that include which value is bad)
throw new IllegalArgumentException(String.format("Value %d of type %s does not correspond to any CQL3 type", i, values[i].getClass()));
}
}
return serializedValues;
}
/**
* Returns the query string.
*
* @return the query string;
*/
@Override
public String getQueryString() {
return query;
}
@Override
public ByteBuffer[] getValues() {
return values;
}
/**
* Returns the routing key for the query.
*
* Unless the routing key has been explicitly set through
* {@link #setRoutingKey}, this method will return {@code null} to
* avoid having to parse the query string to retrieve the partition key.
*
* @return the routing key set through {@link #setRoutingKey} if such a key
* was set, {@code null} otherwise.
*
* @see Statement#getRoutingKey
*/
@Override
public ByteBuffer getRoutingKey() {
return routingKey;
}
/**
* Sets the routing key for this query.
*
* This method allows you to manually provide a routing key for this query. It
* is thus optional since the routing key is only an hint for token aware
* load balancing policy but is never mandatory.
*
* If the partition key for the query is composite, use the
* {@link #setRoutingKey(ByteBuffer...)} method instead to build the
* routing key.
*
* @param routingKey the raw (binary) value to use as routing key.
* @return this {@code SimpleStatement} object.
*
* @see Statement#getRoutingKey
*/
public SimpleStatement setRoutingKey(ByteBuffer routingKey) {
this.routingKey = routingKey;
return this;
}
/**
* Returns the keyspace this query operates on.
*
* Unless the keyspace has been explicitly set through {@link #setKeyspace},
* this method will return {@code null} to avoid having to parse the query
* string.
*
* @return the keyspace set through {@link #setKeyspace} if such keyspace was
* set, {@code null} otherwise.
*
* @see Statement#getKeyspace
*/
@Override
public String getKeyspace() {
return keyspace;
}
/**
* Sets the keyspace this query operates on.
*
* This method allows you to manually provide a keyspace for this query. It
* is thus optional since the value returned by this method is only an hint
* for token aware load balancing policy but is never mandatory.
*
* Do note that if the query does not use a fully qualified keyspace, then
* you do not need to set the keyspace through that method as the
* currently logged in keyspace will be used.
*
* @param keyspace the name of the keyspace this query operates on.
* @return this {@code SimpleStatement} object.
*
* @see Statement#getKeyspace
*/
public SimpleStatement setKeyspace(String keyspace) {
this.keyspace = keyspace;
return this;
}
/**
* Sets the routing key for this query.
*
* See {@link #setRoutingKey(ByteBuffer)} for more information. This
* method is a variant for when the query partition key is composite and
* thus the routing key must be built from multiple values.
*
* @param routingKeyComponents the raw (binary) values to compose to obtain
* the routing key.
* @return this {@code SimpleStatement} object.
*
* @see Statement#getRoutingKey
*/
public SimpleStatement setRoutingKey(ByteBuffer... routingKeyComponents) {
this.routingKey = compose(routingKeyComponents);
return this;
}
// TODO: we could find that a better place (but it's not expose so it doesn't matter too much)
static ByteBuffer compose(ByteBuffer... buffers) {
int totalLength = 0;
for (ByteBuffer bb : buffers)
totalLength += 2 + bb.remaining() + 1;
ByteBuffer out = ByteBuffer.allocate(totalLength);
for (ByteBuffer buffer : buffers)
{
ByteBuffer bb = buffer.duplicate();
putShortLength(out, bb.remaining());
out.put(bb);
out.put((byte) 0);
}
out.flip();
return out;
}
private static void putShortLength(ByteBuffer bb, int length) {
bb.put((byte) ((length >> 8) & 0xFF));
bb.put((byte) (length & 0xFF));
}
}