Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.datastax.driver.core.querybuilder.BuiltStatement Maven / Gradle / Ivy
Go to download
A Shaded CQL ActivityType driver for http://nosqlbench.io/
/*
* 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.querybuilder;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.ColumnMetadata;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.exceptions.CodecNotFoundException;
import com.datastax.driver.core.policies.RetryPolicy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Common ancestor to statements generated with the {@link QueryBuilder}.
*
* The actual query string will be generated and cached the first time it is requested, which is
* either when the driver tries to execute the query, or when you call certain public methods (for
* example {@link RegularStatement#getQueryString(CodecRegistry)}, {@link #getObject(int,
* CodecRegistry)}).
*
*
Whenever possible (and unless you call {@link #setForceNoValues(boolean)}, the builder will
* try to handle values passed to its methods as standalone values bound to the query string with
* placeholders. For instance:
*
*
* select().all().from("foo").where(eq("k", "the key"));
* // Is equivalent to:
* new SimpleStatement("SELECT * FROM foo WHERE k=?", "the key");
*
*
* There are a few exceptions to this rule:
*
*
* for fixed-size number types, the builder can't guess what the actual CQL type is.
* Standalone values are sent to Cassandra in their serialized form, and number types aren't
* all serialized in the same way, so picking the wrong type could lead to a query error;
* if the value is a "special" element like a function call, it can't be serialized. This also
* applies to collections mixing special elements and regular objects.
*
*
* In these cases, the builder will inline the value in the query string:
*
*
* select().all().from("foo").where(eq("k", 1));
* // Is equivalent to:
* new SimpleStatement("SELECT * FROM foo WHERE k=1");
*
*
* One final thing to consider is {@link CodecRegistry custom codecs}. If you've registered codecs
* to handle your own Java types against the cluster, then you can pass instances of those types to
* query builder methods. But should the builder have to inline those values, it needs your codecs
* to {@link TypeCodec#format(Object) convert them to string form}. That is why some of the public
* methods of this class take a {@code CodecRegistry} as a parameter:
*
*
* BuiltStatement s = select().all().from("foo").where(eq("k", myCustomObject));
* // if we do this codecs will definitely be needed:
* s.forceNoValues(true);
* s.getQueryString(myCodecRegistry);
*
*
* For convenience, there are no-arg versions of those methods that use {@link
* CodecRegistry#DEFAULT_INSTANCE}. But you should only use them if you are sure that no custom
* values will need to be inlined while building the statement, or if you have registered your
* custom codecs with the default registry instance. Otherwise, you will get a {@link
* CodecNotFoundException}.
*/
public abstract class BuiltStatement extends RegularStatement {
private final List partitionKey;
private final List routingKeyValues;
String keyspace;
private boolean dirty;
private String cache;
private List values;
Boolean isCounterOp;
boolean hasNonIdempotentOps;
// Whether the user has inputted bind markers. If that's the case, we never generate values as
// it means the user meant for the statement to be prepared and we shouldn't add our own markers.
boolean hasBindMarkers;
private boolean forceNoValues;
BuiltStatement(
String keyspace, List partitionKey, List routingKeyValues) {
this.partitionKey = partitionKey;
this.routingKeyValues = routingKeyValues;
this.keyspace = keyspace;
}
/**
* @deprecated preserved for backward compatibility, use {@link Metadata#quoteIfNecessary(String)}
* instead.
*/
@Deprecated
protected static String escapeId(String ident) {
return Metadata.quoteIfNecessary(ident);
}
@Override
public String getQueryString(CodecRegistry codecRegistry) {
maybeRebuildCache(codecRegistry);
return cache;
}
/**
* Returns the {@code i}th value as the Java type matching its CQL type.
*
* @param i the index to retrieve.
* @param codecRegistry the codec registry that will be used if the statement must be rebuilt in
* order to determine if it has values, and Java objects must be inlined in the process (see
* {@link BuiltStatement} for more explanations on why this is so).
* @return the value of the {@code i}th value of this statement.
* @throws IllegalStateException if this statement does not have values.
* @throws IndexOutOfBoundsException if {@code i} is not a valid index for this object.
* @see #getObject(int)
*/
public Object getObject(int i, CodecRegistry codecRegistry) {
maybeRebuildCache(codecRegistry);
if (values == null || values.isEmpty())
throw new IllegalStateException("This statement does not have values");
if (i < 0 || i >= values.size()) throw new ArrayIndexOutOfBoundsException(i);
return values.get(i);
}
/**
* Returns the {@code i}th value as the Java type matching its CQL type.
*
* This method calls {@link #getObject(int, CodecRegistry)} with {@link
* CodecRegistry#DEFAULT_INSTANCE}. It's safe to use if you don't use any custom codecs, or if
* your custom codecs are in the default registry; otherwise, use the other method and provide the
* registry that contains your codecs.
*
* @param i the index to retrieve.
* @return the value of the {@code i}th value of this statement.
* @throws IllegalStateException if this statement does not have values.
* @throws IndexOutOfBoundsException if {@code i} is not a valid index for this object.
*/
public Object getObject(int i) {
return getObject(i, CodecRegistry.DEFAULT_INSTANCE);
}
private void maybeRebuildCache(CodecRegistry codecRegistry) {
if (!dirty && cache != null) return;
StringBuilder sb;
values = null;
if (hasBindMarkers || forceNoValues) {
sb = buildQueryString(null, codecRegistry);
} else {
values = new ArrayList();
sb = buildQueryString(values, codecRegistry);
if (values.size() > 65535)
throw new IllegalArgumentException(
"Too many values for built statement, the maximum allowed is 65535");
if (values.isEmpty()) values = null;
}
maybeAddSemicolon(sb);
cache = sb.toString();
dirty = false;
}
static StringBuilder maybeAddSemicolon(StringBuilder sb) {
// Use the same test that String#trim() uses to determine
// if a character is a whitespace character.
int l = sb.length();
while (l > 0 && sb.charAt(l - 1) <= ' ') l -= 1;
if (l != sb.length()) sb.setLength(l);
if (l == 0 || sb.charAt(l - 1) != ';') sb.append(';');
return sb;
}
abstract StringBuilder buildQueryString(List variables, CodecRegistry codecRegistry);
boolean isCounterOp() {
return isCounterOp == null ? false : isCounterOp;
}
void setCounterOp(boolean isCounterOp) {
this.isCounterOp = isCounterOp;
}
boolean hasNonIdempotentOps() {
return hasNonIdempotentOps;
}
void setNonIdempotentOps() {
hasNonIdempotentOps = true;
}
void setDirty() {
dirty = true;
}
void checkForBindMarkers(Object value) {
dirty = true;
if (Utils.containsBindMarker(value)) hasBindMarkers = true;
}
void checkForBindMarkers(Utils.Appendeable value) {
dirty = true;
if (value != null && value.containsBindMarker()) hasBindMarkers = true;
}
// TODO: Correctly document the InvalidTypeException
void maybeAddRoutingKey(String name, Object value) {
if (routingKeyValues == null
|| name == null
|| value == null
|| Utils.containsSpecialValue(value)) return;
for (int i = 0; i < partitionKey.size(); i++) {
if (Utils.handleId(name).equals(partitionKey.get(i).getName())) {
routingKeyValues.set(i, value);
return;
}
}
}
@Override
public ByteBuffer getRoutingKey(ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
if (routingKeyValues == null) return null;
ByteBuffer[] routingKeyParts = new ByteBuffer[partitionKey.size()];
for (int i = 0; i < partitionKey.size(); i++) {
Object value = routingKeyValues.get(i);
if (value == null) return null;
TypeCodec codec = codecRegistry.codecFor(partitionKey.get(i).getType(), value);
routingKeyParts[i] = codec.serialize(value, protocolVersion);
}
return routingKeyParts.length == 1 ? routingKeyParts[0] : Utils.compose(routingKeyParts);
}
@Override
public String getKeyspace() {
return Utils.handleId(keyspace);
}
@Override
public ByteBuffer[] getValues(ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
maybeRebuildCache(codecRegistry);
return values == null ? null : Utils.convert(values.toArray(), protocolVersion, codecRegistry);
}
@Override
public boolean hasValues(CodecRegistry codecRegistry) {
maybeRebuildCache(codecRegistry);
return values != null;
}
@Override
public Map getNamedValues(
ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
// Built statements never return named values
return null;
}
@Override
public boolean usesNamedValues() {
return false;
}
@Override
public Boolean isIdempotent() {
// If a value was forced with setIdempotent, it takes priority
if (idempotent != null) return idempotent;
// Otherwise return the computed value
return !hasNonIdempotentOps();
}
@Override
public String toString() {
try {
if (forceNoValues) return getQueryString();
// 1) try first with all values inlined (will not work if some values require custom codecs,
// or if the required codecs are registered in a different CodecRegistry instance than the
// default one)
return maybeAddSemicolon(buildQueryString(null, CodecRegistry.DEFAULT_INSTANCE)).toString();
} catch (RuntimeException e1) {
// 2) try next with bind markers for all values to avoid usage of custom codecs
try {
return maybeAddSemicolon(
buildQueryString(new ArrayList(), CodecRegistry.DEFAULT_INSTANCE))
.toString();
} catch (RuntimeException e2) {
// Ugly but we have absolutely no context to get the registry from
return String.format(
"built query (could not generate with default codec registry: %s)", e2.getMessage());
}
}
}
/**
* Allows to force this builder to not generate values (through its {@code getValues()} method).
*
* By default (and unless the protocol version 1 is in use, see below) and for performance
* reasons, the query builder will not serialize all values provided to strings. This means that
* {@link #getQueryString(CodecRegistry)} may return a query string with bind markers (where and
* when is at the discretion of the builder) and {@link #getValues} will return the binary values
* for those markers. This method allows to force the builder to not generate binary values but
* rather to inline them all in the query string. In practice, this means that if you call {@code
* setForceNoValues(true)}, you are guaranteed that {@code getValues()} will return {@code null}
* and that the string returned by {@code getQueryString()} will contain no other bind markers
* than the ones specified by the user.
*
*
If the native protocol version 1 is in use, the driver will default to not generating values
* since those are not supported by that version of the protocol. In practice, the driver will
* automatically call this method with {@code true} as argument prior to execution. Hence, calling
* this method when the protocol version 1 is in use is basically a no-op.
*
*
Note that this method is mainly useful for debugging purpose. In general, the default
* behavior should be the correct and most efficient one.
*
* @param forceNoValues whether or not this builder may generate values.
* @return this statement.
*/
public RegularStatement setForceNoValues(boolean forceNoValues) {
this.forceNoValues = forceNoValues;
this.dirty = true;
return this;
}
/**
* Sets the keyspace this query operates on.
*
*
This method allows you to manually provide a keyspace for this query. It is used for the
* following:
*
*
* To indicate to cassandra what keyspace the statement is applicable to (protocol V5+
* only). This is useful when the query does not provide an explicit keyspace and you want
* to override the session's keyspace.
* By {@link com.datastax.driver.core.policies.TokenAwarePolicy} to help identify which
* replicas are applicable to send this statement to.
*
*
* Do note that if the query does not use a fully qualified keyspace, then you do not need to set
* the keyspace through this method as the currently logged in keyspace will be used if it is set.
*
* Note: This expects 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.
*
* @param keyspace the name of the keyspace this query operates on.
* @return this {@code BuiltStatement} object.
* @see Statement#getKeyspace
*/
public BuiltStatement setKeyspace(String keyspace) {
this.keyspace = keyspace;
return this;
}
/** An utility class to create a BuiltStatement that encapsulate another one. */
abstract static class ForwardingStatement extends BuiltStatement {
T statement;
ForwardingStatement(T statement) {
super(null, null, null);
this.statement = statement;
}
@Override
public String getQueryString(CodecRegistry codecRegistry) {
return statement.getQueryString(codecRegistry);
}
@Override
StringBuilder buildQueryString(List values, CodecRegistry codecRegistry) {
return statement.buildQueryString(values, codecRegistry);
}
@Override
public ByteBuffer getRoutingKey(ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
return statement.getRoutingKey(protocolVersion, codecRegistry);
}
@Override
public String getKeyspace() {
return statement.getKeyspace();
}
public ForwardingStatement setKeyspace(String keyspace) {
statement.setKeyspace(keyspace);
return this;
}
@Override
boolean isCounterOp() {
return statement.isCounterOp();
}
@Override
boolean hasNonIdempotentOps() {
return statement.hasNonIdempotentOps();
}
@Override
public RegularStatement setForceNoValues(boolean forceNoValues) {
statement.setForceNoValues(forceNoValues);
return this;
}
@Override
public Statement setConsistencyLevel(ConsistencyLevel consistency) {
statement.setConsistencyLevel(consistency);
return this;
}
@Override
public ConsistencyLevel getConsistencyLevel() {
return statement.getConsistencyLevel();
}
@Override
public Statement enableTracing() {
statement.enableTracing();
return this;
}
@Override
public Statement disableTracing() {
statement.disableTracing();
return this;
}
@Override
public boolean isTracing() {
return statement.isTracing();
}
@Override
public Statement setRetryPolicy(RetryPolicy policy) {
statement.setRetryPolicy(policy);
return this;
}
@Override
public RetryPolicy getRetryPolicy() {
return statement.getRetryPolicy();
}
@Override
public ByteBuffer[] getValues(ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
return statement.getValues(protocolVersion, codecRegistry);
}
@Override
public boolean hasValues() {
return statement.hasValues();
}
@Override
void checkForBindMarkers(Object value) {
statement.checkForBindMarkers(value);
}
@Override
void checkForBindMarkers(Utils.Appendeable value) {
statement.checkForBindMarkers(value);
}
@Override
public String toString() {
return statement.toString();
}
}
}