com.datastax.driver.core.Statement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dse-java-driver-core Show documentation
Show all versions of dse-java-driver-core Show documentation
A driver for DataStax Enterprise (DSE)
and Apache Cassandra 1.2+ clusters that works exclusively with the
Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol,
supporting DSE-specific features such as geospatial types, DSE Graph and DSE authentication.
/*
* Copyright DataStax, Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.core;
import com.datastax.driver.core.exceptions.PagingStateException;
import com.datastax.driver.core.exceptions.UnsupportedProtocolVersionException;
import com.datastax.driver.core.policies.RetryPolicy;
import com.datastax.driver.core.querybuilder.BuiltStatement;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
/**
* An executable query.
*
* This represents either a {@link RegularStatement}, a {@link BoundStatement} or a
* {@link BatchStatement} along with the querying options (consistency level,
* whether to trace the query, ...).
*/
public abstract class Statement {
/**
* A special ByteBuffer value that can be used with custom payloads
* to denote a null value in a payload map.
*/
public static final ByteBuffer NULL_PAYLOAD_VALUE = ByteBuffer.allocate(0);
static final String PROXY_EXECUTE = "ProxyExecute";
// An exception to the RegularStatement, BoundStatement or BatchStatement rule above. This is
// used when preparing a statement and for other internal queries. Do not expose publicly.
static final Statement DEFAULT = new Statement() {
@Override
public ByteBuffer getRoutingKey(ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
return null;
}
@Override
public String getKeyspace() {
return null;
}
@Override
public ConsistencyLevel getConsistencyLevel() {
return ConsistencyLevel.ONE;
}
};
private volatile ConsistencyLevel consistency;
private volatile ConsistencyLevel serialConsistency;
private volatile boolean traceQuery;
private volatile int fetchSize;
private volatile long defaultTimestamp = Long.MIN_VALUE;
private volatile int readTimeoutMillis = Integer.MIN_VALUE;
private volatile RetryPolicy retryPolicy;
private volatile ByteBuffer pagingState;
protected volatile Boolean idempotent;
private volatile Map outgoingPayload;
private String authorizationId;
// We don't want to expose the constructor, because the code relies on this being only sub-classed by RegularStatement, BoundStatement and BatchStatement
Statement() {
}
/**
* Sets the consistency level for the query.
*
* @param consistency the consistency level to set.
* @return this {@code Statement} object.
*/
public Statement setConsistencyLevel(ConsistencyLevel consistency) {
this.consistency = consistency;
return this;
}
/**
* The consistency level for this query.
*
* @return the consistency level for this query, or {@code null} if no
* consistency level has been specified (through {@code setConsistencyLevel}).
* In the latter case, the default consistency level will be used.
*/
public ConsistencyLevel getConsistencyLevel() {
return consistency;
}
/**
* Sets the serial consistency level for the query.
*
* The serial consistency level is only used by conditional updates ({@code INSERT}, {@code UPDATE}
* or {@code DELETE} statements with an {@code IF} condition).
* For those, the serial consistency level defines
* the consistency level of the serial phase (or "paxos" phase) while the
* normal consistency level defines the consistency for the "learn" phase, i.e. what
* type of reads will be guaranteed to see the update right away. For instance, if
* a conditional write has a regular consistency of QUORUM (and is successful), then a
* QUORUM read is guaranteed to see that write. But if the regular consistency of that
* write is ANY, then only a read with a consistency of SERIAL is guaranteed to see it
* (even a read with consistency ALL is not guaranteed to be enough).
*
* The serial consistency can only be one of {@code ConsistencyLevel.SERIAL} or
* {@code ConsistencyLevel.LOCAL_SERIAL}. While {@code ConsistencyLevel.SERIAL} guarantees full
* linearizability (with other SERIAL updates), {@code ConsistencyLevel.LOCAL_SERIAL} only
* guarantees it in the local data center.
*
* The serial consistency level is ignored for any query that is not a conditional
* update (serial reads should use the regular consistency level for instance).
*
* @param serialConsistency the serial consistency level to set.
* @return this {@code Statement} object.
* @throws IllegalArgumentException if {@code serialConsistency} is not one of
* {@code ConsistencyLevel.SERIAL} or {@code ConsistencyLevel.LOCAL_SERIAL}.
*/
public Statement setSerialConsistencyLevel(ConsistencyLevel serialConsistency) {
if (!serialConsistency.isSerial())
throw new IllegalArgumentException("Supplied consistency level is not serial: " + serialConsistency);
this.serialConsistency = serialConsistency;
return this;
}
/**
* The serial consistency level for this query.
*
* See {@link #setSerialConsistencyLevel(ConsistencyLevel)} for more detail on the serial consistency level.
*
* @return the serial consistency level for this query, or {@code null} if no serial
* consistency level has been specified (through {@link #setSerialConsistencyLevel(ConsistencyLevel)}).
* In the latter case, the default serial consistency level will be used.
*/
public ConsistencyLevel getSerialConsistencyLevel() {
return serialConsistency;
}
/**
* Enables tracing for this query.
*
* By default (that is unless you call this method), tracing is not enabled.
*
* @return this {@code Statement} object.
*/
public Statement enableTracing() {
this.traceQuery = true;
return this;
}
/**
* Disables tracing for this query.
*
* @return this {@code Statement} object.
*/
public Statement disableTracing() {
this.traceQuery = false;
return this;
}
/**
* Returns whether tracing is enabled for this query or not.
*
* @return {@code true} if this query has tracing enabled, {@code false}
* otherwise.
*/
public boolean isTracing() {
return traceQuery;
}
/**
* Returns the routing key (in binary raw form) to use for token aware
* routing of this query.
*
* The routing key is optional in that implementers are free to
* return {@code null}. The routing key is an hint used for token-aware routing (see
* {@link com.datastax.driver.core.policies.TokenAwarePolicy}), and
* if provided should correspond to the binary value for the query
* partition key. However, not providing a routing key never causes a query
* to fail and if the load balancing policy used is not token aware, then
* the routing key can be safely ignored.
*
* @param protocolVersion the protocol version that will be used if the actual
* implementation needs to serialize something to compute
* the key.
* @param codecRegistry the codec registry that will be used if the actual
* implementation needs to serialize something to compute
* this key.
* @return the routing key for this query or {@code null}.
*/
public abstract ByteBuffer getRoutingKey(ProtocolVersion protocolVersion, CodecRegistry codecRegistry);
/**
* Returns the routing token to use for token aware routing of this query.
*
* This is similar to {@link #getRoutingKey(ProtocolVersion, CodecRegistry)}, except that the key is already hashed.
* A typical use case would be analytics clients wishing to optimize a query by splitting it into chunks, each
* targeted to a direct replica. Such clients could call {@link Metadata#getTokenRanges()} to get a description of
* the ring, and then issue a separate query with the end token of each range. Unlike the routing key, the routing
* token is never computed automatically.
*
* If both the routing key and token are defined, the token is used in priority.
*
* @return an optional routing token for this statement or {@code null}.
*/
public Token getRoutingToken() {
return null; // by default, subclasses that support it will override this method
}
/**
* Returns the keyspace this query operates on.
*
* Note that not all query specify on which keyspace they operate on, and
* so this method can always return {@code null}. Firstly, some queries do
* not operate inside a keyspace: keyspace creation, {@code USE} queries,
* user creation, etc. Secondly, even query that operate within a keyspace
* do not have to specify said keyspace directly, in which case the
* currently logged in keyspace (the one set through a {@code USE} query
* (or through the use of {@link Cluster#connect(String)})). Lastly, as
* for the routing key, this keyspace information is only a hint for
* token-aware routing (since replica placement depend on the replication
* strategy in use which is a per-keyspace property) and having this method
* return {@code null} (or even a bogus keyspace name) will never cause the
* query to fail.
*
* Note: This returns the internal representation of the keyspace.
* In the typical case, the internal representation is equivalent to
* the CQL representation. However, if you are using a keyspace that
* requires the use of quotes in CQL (a quoted identifier), i.e.: "MyKS",
* this method will return the unquoted representation instead, i.e. MyKS.
*
* @return the keyspace this query operate on if relevant or {@code null}.
*/
public abstract String getKeyspace();
/**
* Sets the retry policy to use for this query.
*
* The default retry policy, if this method is not called, is the one returned by
* {@link com.datastax.driver.core.policies.Policies#getRetryPolicy} in the
* cluster configuration. This method is thus only useful in case you want
* to punctually override the default policy for this request.
*
* @param policy the retry policy to use for this query.
* @return this {@code Statement} object.
*/
public Statement setRetryPolicy(RetryPolicy policy) {
this.retryPolicy = policy;
return this;
}
/**
* Returns the retry policy sets for this query, if any.
*
* @return the retry policy sets specifically for this query or {@code null} if no query specific
* retry policy has been set through {@link #setRetryPolicy} (in which case
* the Cluster retry policy will apply if necessary).
*/
public RetryPolicy getRetryPolicy() {
return retryPolicy;
}
/**
* Sets the query fetch size.
*
* The fetch size controls how much resulting rows will be retrieved
* simultaneously (the goal being to avoid loading too much results
* in memory for queries yielding large results). Please note that
* while value as low as 1 can be used, it is *highly* discouraged to
* use such a low value in practice as it will yield very poor
* performance. If in doubt, leaving the default is probably a good
* idea.
*
* Only {@code SELECT} queries only ever make use of that setting.
*
* Note: Paging is not supported with the native protocol version 1. If
* you call this method with {@code fetchSize > 0} and
* {@code fetchSize != Integer.MAX_VALUE} and the protocol version is in
* use (i.e. if you've force version 1 through {@link Cluster.Builder#withProtocolVersion}
* or you use Cassandra 1.2), you will get {@link UnsupportedProtocolVersionException}
* when submitting this statement for execution.
*
* @param fetchSize the fetch size to use. If {@code fetchSize <e; 0},
* the default fetch size will be used. To disable paging of the
* result set, use {@code fetchSize == Integer.MAX_VALUE}.
* @return this {@code Statement} object.
*/
public Statement setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
return this;
}
/**
* The fetch size for this query.
*
* @return the fetch size for this query. If that value is less or equal
* to 0 (the default unless {@link #setFetchSize} is used), the default
* fetch size will be used.
*/
public int getFetchSize() {
return fetchSize;
}
/**
* Sets the default timestamp for this query (in microseconds since the epoch).
*
* This feature is only available when version {@link ProtocolVersion#V3 V3} or
* higher of the native protocol is in use. With earlier versions, calling this
* method has no effect.
*
* The actual timestamp that will be used for this query is, in order of
* preference:
*
* - the timestamp specified directly in the CQL query string (using the
* {@code USING TIMESTAMP} syntax);
* - the timestamp specified through this method, if different from
* {@link Long#MIN_VALUE};
* - the timestamp returned by the {@link TimestampGenerator} currently in use,
* if different from {@link Long#MIN_VALUE}.
*
* If none of these apply, no timestamp will be sent with the query and Cassandra
* will generate a server-side one (similar to the pre-V3 behavior).
*
* @param defaultTimestamp the default timestamp for this query (must be strictly
* positive).
* @return this {@code Statement} object.
* @see Cluster.Builder#withTimestampGenerator(TimestampGenerator)
*/
public Statement setDefaultTimestamp(long defaultTimestamp) {
this.defaultTimestamp = defaultTimestamp;
return this;
}
/**
* The default timestamp for this query.
*
* @return the default timestamp (in microseconds since the epoch).
*/
public long getDefaultTimestamp() {
return defaultTimestamp;
}
/**
* Overrides the default per-host read timeout ({@link SocketOptions#getReadTimeoutMillis()})
* for this statement.
*
* You should override this only for statements for which the coordinator may allow a longer server-side
* timeout (for example aggregation queries).
*
* @param readTimeoutMillis the timeout to set. Negative values are not allowed. If it is 0, the read timeout will
* be disabled for this statement.
* @return this {@code Statement} object.
*/
public Statement setReadTimeoutMillis(int readTimeoutMillis) {
Preconditions.checkArgument(readTimeoutMillis >= 0, "read timeout must be >= 0");
this.readTimeoutMillis = readTimeoutMillis;
return this;
}
/**
* Return the per-host read timeout that was set for this statement.
*
* @return the timeout. Note that a negative value means that the default
* {@link SocketOptions#getReadTimeoutMillis()} will be used.
*/
public int getReadTimeoutMillis() {
return readTimeoutMillis;
}
/**
* Sets the paging state.
*
* This will cause the next execution of this statement to fetch results from a given
* page, rather than restarting from the beginning.
*
* You get the paging state from a previous execution of the statement (see
* {@link ExecutionInfo#getPagingState()}.
* This is typically used to iterate in a "stateless" manner (e.g. across HTTP requests):
*
* {@code
* Statement st = new SimpleStatement("your query");
* ResultSet rs = session.execute(st.setFetchSize(20));
* int available = rs.getAvailableWithoutFetching();
* for (int i = 0; i < available; i++) {
* Row row = rs.one();
* // Do something with row (e.g. display it to the user...)
* }
* // Get state and serialize as string or byte[] to store it for the next execution
* // (e.g. pass it as a parameter in the "next page" URI)
* PagingState pagingState = rs.getExecutionInfo().getPagingState();
* String savedState = pagingState.toString();
*
* // Next execution:
* // Get serialized state back (e.g. get URI parameter)
* String savedState = ...
* Statement st = new SimpleStatement("your query");
* st.setPagingState(PagingState.fromString(savedState));
* ResultSet rs = session.execute(st.setFetchSize(20));
* int available = rs.getAvailableWithoutFetching();
* for (int i = 0; i < available; i++) {
* ...
* }
* }
*
*
* The paging state can only be reused between perfectly identical statements
* (same query string, same bound parameters). Altering the contents of the paging state
* or trying to set it on a different statement will cause this method to fail.
*
* Note that, due to internal implementation details, the paging state is not portable
* across native protocol versions (see the
* online documentation
* for more explanations about the native protocol).
* This means that {@code PagingState} instances generated with an old version won't work
* with a higher version. If that is a problem for you, consider using the "unsafe" API (see
* {@link #setPagingStateUnsafe(byte[])}).
*
* @param pagingState the paging state to set, or {@code null} to remove any state that was
* previously set on this statement.
* @param codecRegistry the codec registry that will be used if this method needs to serialize the
* statement's values in order to check that the paging state matches.
* @return this {@code Statement} object.
* @throws PagingStateException if the paging state does not match this statement.
* @see #setPagingState(PagingState)
*/
public Statement setPagingState(PagingState pagingState, CodecRegistry codecRegistry) {
if (this instanceof BatchStatement) {
throw new UnsupportedOperationException("Cannot set the paging state on a batch statement");
} else {
if (pagingState == null) {
this.pagingState = null;
} else if (pagingState.matches(this, codecRegistry)) {
this.pagingState = pagingState.getRawState();
} else {
throw new PagingStateException("Paging state mismatch, "
+ "this means that either the paging state contents were altered, "
+ "or you're trying to apply it to a different statement");
}
}
return this;
}
/**
* Sets the paging state.
*
* This method calls {@link #setPagingState(PagingState, CodecRegistry)} with {@link CodecRegistry#DEFAULT_INSTANCE}.
* Whether you should use this or the other variant depends on the type of statement this is
* called on:
*
* - for a {@link BoundStatement}, the codec registry isn't actually needed, so it's always safe to
* use this method;
* - for a {@link SimpleStatement} or {@link BuiltStatement}, you can use this method if you use no
* custom codecs, or if your custom codecs are registered with the default registry. Otherwise, use
* the other method and provide the registry that contains your codecs.
*
*
* @param pagingState the paging state to set, or {@code null} to remove any state that was
* previously set on this statement.
*/
public Statement setPagingState(PagingState pagingState) {
return setPagingState(pagingState, CodecRegistry.DEFAULT_INSTANCE);
}
/**
* Sets the paging state.
*
* Contrary to {@link #setPagingState(PagingState)}, this method takes the "raw" form of the
* paging state (previously extracted with {@link ExecutionInfo#getPagingStateUnsafe()}.
* It won't validate that this statement matches the one that the paging state was extracted from.
* If the paging state was altered in any way, you will get unpredictable behavior from
* Cassandra (ranging from wrong results to a query failure). If you decide to use this variant,
* it is strongly recommended to add your own validation (for example, signing the raw state with
* a private key).
*
* @param pagingState the paging state to set, or {@code null} to remove any state that was
* previously set on this statement.
* @return this {@code Statement} object.
*/
public Statement setPagingStateUnsafe(byte[] pagingState) {
if (pagingState == null) {
this.pagingState = null;
} else {
this.pagingState = ByteBuffer.wrap(pagingState);
}
return this;
}
ByteBuffer getPagingState() {
return pagingState;
}
/**
* Sets whether this statement is idempotent.
*
* See {@link #isIdempotent()} for more explanations about this property.
*
* @param idempotent the new value.
* @return this {@code Statement} object.
*/
public Statement setIdempotent(boolean idempotent) {
this.idempotent = idempotent;
return this;
}
/**
* Whether this statement is idempotent, i.e. whether it can be applied multiple times
* without changing the result beyond the initial application.
*
* If a statement is not idempotent, the driver will ensure that it never gets executed more than once,
* which means:
*
* - avoiding {@link RetryPolicy retries} on write timeouts or request errors;
* - never scheduling {@link com.datastax.driver.core.policies.SpeculativeExecutionPolicy speculative executions}.
*
*
* (this behavior is implemented in the driver internals, the corresponding policies will not even be invoked).
*
* Note that this method can return {@code null}, in which case the driver will default to
* {@link QueryOptions#getDefaultIdempotence()}.
*
* By default, this method returns {@code null} for all statements, except for
*
* - {@link BuiltStatement} - value will be inferred from the query: if it updates counters,
* prepends/appends to a list, or uses a function call or
* {@link com.datastax.driver.core.querybuilder.QueryBuilder#raw(String)} anywhere in an inserted value,
* the result will be {@code false}; otherwise it will be {@code true}.
*
* -
* {@link com.datastax.driver.core.querybuilder.Batch} and {@link BatchStatement}:
*
* - If any statement in batch has isIdempotent() false - return false
* - If no statements with isIdempotent() false, but some have isIdempotent() null - return null
* - Otherwise - return true
*
*
*
* In all cases, calling {@link #setIdempotent(boolean)} forces a value that overrides calculated value.
*
* Note that when a statement is prepared ({@link Session#prepare(String)}), its idempotence flag will be propagated
* to all {@link PreparedStatement}s created from it.
*
* @return whether this statement is idempotent, or {@code null} to use
* {@link QueryOptions#getDefaultIdempotence()}.
*/
public Boolean isIdempotent() {
return idempotent;
}
boolean isIdempotentWithDefault(QueryOptions queryOptions) {
Boolean myValue = this.isIdempotent();
if (myValue != null)
return myValue;
else
return queryOptions.getDefaultIdempotence();
}
/**
* Returns this statement's outgoing payload.
* Each time this statement is executed, this payload will be included in the query request.
*
* This method returns {@code null} if no payload has been set, otherwise
* it always returns immutable maps.
*
* This feature is only available with {@link ProtocolVersion#V4} or above.
* Trying to include custom payloads in requests sent by the driver
* under lower protocol versions will result in an
* {@link com.datastax.driver.core.exceptions.UnsupportedFeatureException}
* (wrapped in a {@link com.datastax.driver.core.exceptions.NoHostAvailableException}).
*
* @return the outgoing payload to include with this statement,
* or {@code null} if no payload has been set.
* @since 2.2
*/
public Map getOutgoingPayload() {
return outgoingPayload;
}
/**
* Set the given outgoing payload on this statement.
* Each time this statement is executed, this payload will be included in the query request.
*
* This method makes a defensive copy of the given map, but its values
* remain inherently mutable. Care should be taken not to modify the original map
* once it is passed to this method.
*
* This feature is only available with {@link ProtocolVersion#V4} or above.
* Trying to include custom payloads in requests sent by the driver
* under lower protocol versions will result in an
* {@link com.datastax.driver.core.exceptions.UnsupportedFeatureException}
* (wrapped in a {@link com.datastax.driver.core.exceptions.NoHostAvailableException}).
*
* @param payload the outgoing payload to include with this statement,
* or {@code null} to clear any previously entered payload.
* @return this {@link Statement} object.
* @since 2.2
*/
public Statement setOutgoingPayload(Map payload) {
this.outgoingPayload = payload == null ? null : ImmutableMap.copyOf(payload);
rebuildOutgoingPayload();
return this;
}
/**
* Returns the number of bytes required to encode this statement.
*
* The calculated size may be overestimated by a few bytes, but is never underestimated.
* If the size cannot be calculated, this method returns -1.
*
* Note that the returned value is not cached, but instead recomputed at every method call.
*
* @return the number of bytes required to encode this statement.
*/
public int requestSizeInBytes(ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
return -1;
}
protected static Boolean isBatchIdempotent(Collection extends Statement> statements) {
boolean hasNullIdempotentStatements = false;
for (Statement statement : statements) {
Boolean innerIdempotent = statement.isIdempotent();
if (innerIdempotent == null) {
hasNullIdempotentStatements = true;
} else if (!innerIdempotent) {
return false;
}
}
return (hasNullIdempotentStatements) ? null : true;
}
/**
* Allows this statement to be executed as a different user/role
* than the one currently authenticated (a.k.a. proxy execution).
*
* This feature is only available in DSE 5.1+.
*
* @param userOrRole The user or role name to act as when executing this statement.
*/
public Statement executingAs(String userOrRole) {
this.authorizationId = userOrRole;
rebuildOutgoingPayload();
return this;
}
private void rebuildOutgoingPayload() {
if (authorizationId == null)
return;
ByteBuffer proxyExecute = ByteBuffer.wrap(authorizationId.getBytes(Charsets.UTF_8));
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put(PROXY_EXECUTE, proxyExecute).build();
if (outgoingPayload != null)
builder.putAll(outgoingPayload);
outgoingPayload = builder.build();
}
}