com.datastax.driver.core.Statement Maven / Gradle / Ivy
Show all versions of dse-java-driver-core Show documentation
/*
* 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.NoHostAvailableException;
import com.datastax.driver.core.exceptions.PagingStateException;
import com.datastax.driver.core.exceptions.UnsupportedProtocolVersionException;
import com.datastax.driver.core.policies.LoadBalancingPolicy;
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;
private volatile Host host;
// 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 this setting.
*
*
Note that unlike other configuration, when this statement is prepared {@link
* BoundStatement}s created off of {@link PreparedStatement} do not inherit this configuration.
*
*
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).
*
* Note that unlike other configuration, when this statement is prepared {@link
* BoundStatement}s created off of {@link PreparedStatement} do not inherit this configuration.
*
* @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).
*
*
Note that unlike other configuration, when this statement is prepared {@link
* BoundStatement}s created off of {@link PreparedStatement} do not inherit this configuration.
*
* @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();
}
/**
* @return The host configured on this statement, or null if none is configured.
* @see #setHost(Host)
*/
public Host getHost() {
return host;
}
/**
* Sets the {@link Host} that should handle this query.
*
* In the general case, use of this method is heavily discouraged and should only be
* used in the following cases:
*
*
* - Querying node-local tables, such as tables in the {@code system} and {@code system_views}
* keyspaces.
*
- Applying a series of schema changes, where it may be advantageous to execute schema
* changes in sequence on the same node.
*
*
* Configuring a specific host causes the configured {@link LoadBalancingPolicy} to be
* completely bypassed. However, if the load balancing policy dictates that the host is at
* distance {@link HostDistance#IGNORED} or there is no active connectivity to the host, the
* request will fail with a {@link NoHostAvailableException}.
*
*
Note that unlike other configuration, when this statement is prepared {@link
* BoundStatement}s created off of {@link PreparedStatement} do not inherit this configuration.
*
* @param host The host that should be used to handle executions of this statement or null to
* delegate to the configured load balancing policy.
* @return this {@code Statement} object.
*/
public Statement setHost(Host host) {
this.host = host;
return this;
}
}