/*
* 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;
import java.util.List;
import org.apache.cassandra.utils.MD5Digest;
import org.apache.cassandra.transport.messages.ResultMessage;
import com.datastax.driver.core.exceptions.DriverInternalError;
/**
* Represents a prepared statement, a query with bound variables that has been
* prepared (pre-parsed) by the database.
* <p>
* A prepared statement can be executed once concrete values has been provided
* for the bound variables. The pair of a prepared statement and values for its
* bound variables is a BoundStatement and can be executed (by
* {@link Session#execute}).
*/
public class PreparedStatement {
final ColumnDefinitions metadata;
final MD5Digest id;
volatile ByteBuffer routingKey;
final int[] routingKeyIndexes;
volatile ConsistencyLevel consistency;
private PreparedStatement(ColumnDefinitions metadata, MD5Digest id, int[] routingKeyIndexes) {
this.metadata = metadata;
this.id = id;
this.routingKeyIndexes = routingKeyIndexes;
}
static PreparedStatement fromMessage(ResultMessage.Prepared msg, Metadata clusterMetadata) {
switch (msg.kind) {
case PREPARED:
ResultMessage.Prepared pmsg = (ResultMessage.Prepared)msg;
ColumnDefinitions.Definition[] defs = new ColumnDefinitions.Definition[pmsg.metadata.names.size()];
if (defs.length == 0)
return new PreparedStatement(new ColumnDefinitions(defs), pmsg.statementId, null);
List<ColumnMetadata> partitionKeyColumns = null;
int[] pkIndexes = null;
KeyspaceMetadata km = clusterMetadata.getKeyspace(pmsg.metadata.names.get(0).ksName);
if (km != null) {
TableMetadata tm = km.getTable(pmsg.metadata.names.get(0).cfName);
if (tm != null) {
partitionKeyColumns = tm.getPartitionKey();
pkIndexes = new int[partitionKeyColumns.size()];
for (int i = 0; i < pkIndexes.length; ++i)
pkIndexes[i] = -1;
}
}
// Note: we rely on the fact CQL queries cannot span multiple tables. If that change, we'll have to get smarter.
for (int i = 0; i < defs.length; i++) {
defs[i] = ColumnDefinitions.Definition.fromTransportSpecification(pmsg.metadata.names.get(i));
maybeGetIndex(defs[i].getName(), i, partitionKeyColumns, pkIndexes);
}
return new PreparedStatement(new ColumnDefinitions(defs), pmsg.statementId, allSet(pkIndexes) ? pkIndexes : null);
default:
throw new DriverInternalError(String.format("%s response received when prepared statement received was expected", msg.kind));
}
}
private static void maybeGetIndex(String name, int j, List<ColumnMetadata> pkColumns, int[] pkIndexes) {
if (pkColumns == null)
return;
for (int i = 0; i < pkColumns.size(); ++i) {
if (name.equals(pkColumns.get(i).getName())) {
// We may have the same column prepared multiple times, but only pick the first value
pkIndexes[i] = j;
return;
}
}
}
private static boolean allSet(int[] pkColumns) {
if (pkColumns == null)
return false;
for (int i = 0; i < pkColumns.length; ++i)
if (pkColumns[i] < 0)
return false;
return true;
}
/**
* Returns metadata on the bounded variables of this prepared statement.
*
* @return the variables bounded in this prepared statement.
*/
public ColumnDefinitions getVariables() {
return metadata;
}
/**
* Creates a new BoundStatement object and bind its variables to the
* provided values.
* <p>
* This method is a shortcut for {@code new BoundStatement(this).bind(...)}.
* <p>
* Note that while no more {@code values} than bound variables can be
* provided, it is allowed to provide less {@code values} that there is
* variables. In that case, the remaining variables will have to be bound
* to values by another mean because the resulting {@code BoundStatement}
* being executable.
*
* @param values the values to bind to the variables of the newly created
* BoundStatement.
* @return the newly created {@code BoundStatement} with its variables
* bound to {@code values}.
*
* @throws IllegalArgumentException if more {@code values} are provided
* than there is of bound variables in this statement.
* @throws InvalidTypeException if any of the provided value is not of
* correct type to be bound to the corresponding bind variable.
*
* @see BoundStatement#bind
*/
public BoundStatement bind(Object... values) {
BoundStatement bs = new BoundStatement(this);
return bs.bind(values);
}
/**
* Set the routing key for this prepared statement.
* <p>
* This method allows to manually provide a fixed routing key for all
* executions of this prepared statement. It is never mandatory to provide
* a routing key through this method and this method should only be used
* if the partition key of the prepared query is not part of the prepared
* variables (i.e. if the partition key is fixed).
* <p>
* Note that if the partition key is part of the prepared variables, the
* routing key will be automatically computed once those variables are bound.
*
* @param routingKey the raw (binary) value to use as routing key.
* @return this {@code PreparedStatement} object.
*
* @see Query#getRoutingKey
*/
public PreparedStatement setRoutingKey(ByteBuffer routingKey) {
this.routingKey = routingKey;
return this;
}
/**
* Set the routing key for this query.
* <p>
* 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 PreparedStatement} object.
*
* @see Query#getRoutingKey
*/
public PreparedStatement setRoutingKey(ByteBuffer... routingKeyComponents) {
this.routingKey = SimpleStatement.compose(routingKeyComponents);
return this;
}
/**
* Sets a default consistency level for all {@code BoundStatement} created
* from this object.
* <p>
* If no consistency level is set through this method, the BoundStatement
* created from this object will use the default consistency level (ONE).
* <p>
* Changing the default consistency level is not retroactive, it only
* applies to BoundStatement created after the change.
*
* @param consistency the default consistency level to set.
* @return this {@code PreparedStatement} object.
*/
public PreparedStatement setConsistencyLevel(ConsistencyLevel consistency) {
this.consistency = consistency;
return this;
}
/**
* The default consistency level set through {@link #setConsistencyLevel}.
*
* @return the default consistency level. Returns {@code null} if no
* consistency level has been set through this object {@code setConsistencyLevel}
* method.
*/
public ConsistencyLevel getConsistencyLevel() {
return consistency;
}
}