/*
* Copyright (C) 2012-2015 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 com.datastax.driver.core.exceptions.InvalidQueryException;
import com.datastax.driver.core.exceptions.UnsupportedFeatureException;
import com.datastax.driver.core.policies.FallthroughRetryPolicy;
import com.datastax.driver.core.utils.Bytes;
import com.datastax.driver.core.utils.CassandraVersion;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Uninterruptibles;
import org.testng.annotations.Test;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.datastax.driver.core.ProtocolVersion.V4;
import static com.datastax.driver.core.TestUtils.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.*;
/**
* Prepared statement tests.
* <p/>
* Note: this class also happens to test all the get methods from Row.
*/
@CCMConfig(clusterProvider = "createClusterBuilderNoDebouncing")
public class PreparedStatementTest extends CCMTestsSupport {
private static final String ALL_NATIVE_TABLE = "all_native";
private static final String ALL_LIST_TABLE = "all_list";
private static final String ALL_SET_TABLE = "all_set";
private static final String ALL_MAP_TABLE = "all_map";
private static final String SIMPLE_TABLE = "test";
private static final String SIMPLE_TABLE2 = "test2";
private ProtocolVersion protocolVersion;
private Collection<DataType> primitiveTypes;
private boolean exclude(DataType t) {
// duration is not supported in collections
return t.getName() == DataType.Name.COUNTER || t.getName() == DataType.Name.DURATION;
}
@Override
public void onTestContextInitialized() {
protocolVersion = ccm().getProtocolVersion();
primitiveTypes = TestUtils.allPrimitiveTypes(protocolVersion);
execute(createTestFixtures());
}
private List<String> createTestFixtures() {
List<String> defs = new ArrayList<String>(4);
StringBuilder sb = new StringBuilder();
sb.append("CREATE TABLE ").append(ALL_NATIVE_TABLE).append(" (k text PRIMARY KEY");
for (DataType type : primitiveTypes) {
if (exclude(type))
continue;
sb.append(", c_").append(type).append(' ').append(type);
}
sb.append(')');
defs.add(sb.toString());
sb = new StringBuilder();
sb.append("CREATE TABLE ").append(ALL_LIST_TABLE).append(" (k text PRIMARY KEY");
for (DataType type : primitiveTypes) {
if (exclude(type))
continue;
sb.append(", c_list_").append(type).append(" list<").append(type).append('>');
}
sb.append(')');
defs.add(sb.toString());
sb = new StringBuilder();
sb.append("CREATE TABLE ").append(ALL_SET_TABLE).append(" (k text PRIMARY KEY");
for (DataType type : primitiveTypes) {
// This must be handled separately
if (exclude(type))
continue;
sb.append(", c_set_").append(type).append(" set<").append(type).append('>');
}
sb.append(')');
defs.add(sb.toString());
sb = new StringBuilder();
sb.append("CREATE TABLE ").append(ALL_MAP_TABLE).append(" (k text PRIMARY KEY");
for (DataType keyType : primitiveTypes) {
// This must be handled separately
if (exclude(keyType))
continue;
for (DataType valueType : primitiveTypes) {
// This must be handled separately
if (exclude(valueType))
continue;
sb.append(", c_map_").append(keyType).append('_').append(valueType).append(" map<").append(keyType).append(',').append(valueType).append('>');
}
}
sb.append(')');
defs.add(sb.toString());
defs.add(String.format("CREATE TABLE %s (k text PRIMARY KEY, i int)", SIMPLE_TABLE));
defs.add(String.format("CREATE TABLE %s (k text PRIMARY KEY, v text)", SIMPLE_TABLE2));
return defs;
}
@Test(groups = "short")
public void preparedNativeTest() {
// Test preparing/bounding for all native types
for (DataType type : primitiveTypes) {
// This must be handled separately
if (exclude(type))
continue;
String name = "c_" + type;
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_native', ?)", ALL_NATIVE_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, getFixedValue(type));
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_native'", name, ALL_NATIVE_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), getFixedValue(type), "For type " + type);
}
}
/**
* Almost the same as preparedNativeTest, but it uses getFixedValue2() instead.
*/
@Test(groups = "short")
public void preparedNativeTest2() {
// Test preparing/bounding for all native types
for (DataType type : primitiveTypes) {
// This must be handled separately
if (exclude(type))
continue;
String name = "c_" + type;
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_native', ?)", ALL_NATIVE_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, getFixedValue2(type));
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_native'", name, ALL_NATIVE_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), getFixedValue2(type), "For type " + type);
}
}
@Test(groups = "short")
@SuppressWarnings("unchecked")
public void prepareListTest() {
// Test preparing/bounding for all possible list types
for (DataType rawType : primitiveTypes) {
// This must be handled separately
if (exclude(rawType))
continue;
String name = "c_list_" + rawType;
DataType type = DataType.list(rawType);
List<Object> value = (List<Object>) getFixedValue(type);
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_list', ?)", ALL_LIST_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, value);
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_list'", name, ALL_LIST_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), value, "For type " + type);
}
}
/**
* Almost the same as prepareListTest, but it uses getFixedValue2() instead.
*/
@Test(groups = "short")
@SuppressWarnings("unchecked")
public void prepareListTest2() {
// Test preparing/bounding for all possible list types
for (DataType rawType : primitiveTypes) {
// This must be handled separately
if (exclude(rawType))
continue;
String name = "c_list_" + rawType;
DataType type = DataType.list(rawType);
List<Object> value = (List<Object>) getFixedValue2(type);
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_list', ?)", ALL_LIST_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, value);
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_list'", name, ALL_LIST_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), value, "For type " + type);
}
}
@Test(groups = "short")
@SuppressWarnings("unchecked")
public void prepareSetTest() {
// Test preparing/bounding for all possible set types
for (DataType rawType : primitiveTypes) {
// This must be handled separately
if (exclude(rawType))
continue;
String name = "c_set_" + rawType;
DataType type = DataType.set(rawType);
Set<Object> value = (Set<Object>) getFixedValue(type);
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_set', ?)", ALL_SET_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, value);
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_set'", name, ALL_SET_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), value, "For type " + type);
}
}
/**
* Almost the same as prepareSetTest, but it uses getFixedValue2() instead.
*/
@Test(groups = "short")
@SuppressWarnings("unchecked")
public void prepareSetTest2() {
// Test preparing/bounding for all possible set types
for (DataType rawType : primitiveTypes) {
// This must be handled separately
if (exclude(rawType))
continue;
String name = "c_set_" + rawType;
DataType type = DataType.set(rawType);
Set<Object> value = (Set<Object>) getFixedValue2(type);
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_set', ?)", ALL_SET_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, value);
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_set'", name, ALL_SET_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), value, "For type " + type);
}
}
@Test(groups = "short")
@SuppressWarnings("unchecked")
public void prepareMapTest() {
// Test preparing/bounding for all possible map types
for (DataType rawKeyType : primitiveTypes) {
// This must be handled separately
if (exclude(rawKeyType))
continue;
for (DataType rawValueType : primitiveTypes) {
// This must be handled separately
if (exclude(rawValueType))
continue;
String name = "c_map_" + rawKeyType + '_' + rawValueType;
DataType type = DataType.map(rawKeyType, rawValueType);
Map<Object, Object> value = (Map<Object, Object>) getFixedValue(type);
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_map', ?)", ALL_MAP_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, value);
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_map'", name, ALL_MAP_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), value, "For type " + type);
}
}
}
/**
* Almost the same as prepareMapTest, but it uses getFixedValue2() instead.
*/
@Test(groups = "short")
@SuppressWarnings("unchecked")
public void prepareMapTest2() {
// Test preparing/bounding for all possible map types
for (DataType rawKeyType : primitiveTypes) {
// This must be handled separately
if (exclude(rawKeyType))
continue;
for (DataType rawValueType : primitiveTypes) {
// This must be handled separately
if (exclude(rawValueType))
continue;
String name = "c_map_" + rawKeyType + '_' + rawValueType;
DataType type = DataType.map(rawKeyType, rawValueType);
Map<Object, Object> value = (Map<Object, Object>) getFixedValue2(type);
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s(k, %s) VALUES ('prepared_map', ?)", ALL_MAP_TABLE, name));
BoundStatement bs = ps.bind();
setValue(bs, name, type, value);
session().execute(bs);
Row row = session().execute(String.format("SELECT %s FROM %s WHERE k='prepared_map'", name, ALL_MAP_TABLE)).one();
assertEquals(getValue(row, name, type, cluster().getConfiguration().getCodecRegistry()), value, "For type " + type);
}
}
}
@Test(groups = "short")
public void prepareWithNullValuesTest() throws Exception {
PreparedStatement ps = session().prepare("INSERT INTO " + SIMPLE_TABLE2 + "(k, v) VALUES (?, ?)");
session().execute(ps.bind("prepWithNull1", null));
BoundStatement bs = ps.bind();
bs.setString("k", "prepWithNull2");
bs.setString("v", null);
session().execute(bs);
ResultSet rs = session().execute("SELECT * FROM " + SIMPLE_TABLE2 + " WHERE k IN ('prepWithNull1', 'prepWithNull2')");
Row r1 = rs.one();
Row r2 = rs.one();
assertTrue(rs.isExhausted());
assertEquals(r1.getString("k"), "prepWithNull1");
assertEquals(r1.getString("v"), null);
assertEquals(r2.getString("k"), "prepWithNull2");
assertEquals(r2.getString("v"), null);
}
@Test(groups = "short")
public void prepareStatementInheritPropertiesTest() {
RegularStatement toPrepare = new SimpleStatement("SELECT * FROM test WHERE k=?");
toPrepare.setConsistencyLevel(ConsistencyLevel.QUORUM);
toPrepare.setSerialConsistencyLevel(ConsistencyLevel.LOCAL_SERIAL);
toPrepare.setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
if (protocolVersion.compareTo(V4) >= 0)
toPrepare.setOutgoingPayload(ImmutableMap.of("foo", Bytes.fromHexString("0xcafebabe")));
toPrepare.setIdempotent(true);
toPrepare.enableTracing();
PreparedStatement prepared = session().prepare(toPrepare);
assertThat(prepared.getConsistencyLevel()).isEqualTo(ConsistencyLevel.QUORUM);
assertThat(prepared.getSerialConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_SERIAL);
assertThat(prepared.getRetryPolicy()).isEqualTo(FallthroughRetryPolicy.INSTANCE);
if (protocolVersion.compareTo(V4) >= 0)
assertThat(prepared.getOutgoingPayload()).isEqualTo(ImmutableMap.of("foo", Bytes.fromHexString("0xcafebabe")));
assertThat(prepared.isIdempotent()).isTrue();
assertThat(prepared.isTracing()).isTrue();
BoundStatement bs = prepared.bind("someValue");
assertThat(bs.getConsistencyLevel()).isEqualTo(ConsistencyLevel.QUORUM);
assertThat(bs.getSerialConsistencyLevel()).isEqualTo(ConsistencyLevel.LOCAL_SERIAL);
assertThat(bs.getRetryPolicy()).isEqualTo(FallthroughRetryPolicy.INSTANCE);
if (protocolVersion.compareTo(V4) >= 0)
assertThat(bs.getOutgoingPayload()).isEqualTo(ImmutableMap.of("foo", Bytes.fromHexString("0xcafebabe")));
assertThat(bs.isIdempotent()).isTrue();
assertThat(bs.isTracing()).isTrue();
}
/**
* Prints the table definitions that will be used in testing
* (for exporting purposes)
*/
@Test(groups = {"docs"})
public void printTableDefinitions() {
for (String definition : createTestFixtures()) {
System.out.println(definition);
}
}
@Test(groups = "short")
public void batchTest() throws Exception {
try {
PreparedStatement ps1 = session().prepare("INSERT INTO " + SIMPLE_TABLE2 + "(k, v) VALUES (?, ?)");
PreparedStatement ps2 = session().prepare("INSERT INTO " + SIMPLE_TABLE2 + "(k, v) VALUES (?, 'bar')");
BatchStatement bs = new BatchStatement();
bs.add(ps1.bind("one", "foo"));
bs.add(ps2.bind("two"));
bs.add(new SimpleStatement("INSERT INTO " + SIMPLE_TABLE2 + " (k, v) VALUES ('three', 'foobar')"));
session().execute(bs);
List<Row> all = session().execute("SELECT * FROM " + SIMPLE_TABLE2).all();
assertEquals("three", all.get(0).getString("k"));
assertEquals("foobar", all.get(0).getString("v"));
assertEquals("one", all.get(1).getString("k"));
assertEquals("foo", all.get(1).getString("v"));
assertEquals("two", all.get(2).getString("k"));
assertEquals("bar", all.get(2).getString("v"));
} catch (UnsupportedFeatureException e) {
// This is expected when testing the protocol v1
if (cluster().getConfiguration().getProtocolOptions().getProtocolVersion() != ProtocolVersion.V1)
throw e;
}
}
@Test(groups = "short")
public void should_set_routing_key_on_case_insensitive_keyspace_and_table() {
session().execute(String.format("CREATE TABLE %s.foo (i int PRIMARY KEY)", keyspace));
PreparedStatement ps = session().prepare(String.format("INSERT INTO %s.foo (i) VALUES (?)", keyspace));
BoundStatement bs = ps.bind(1);
assertThat(bs.getRoutingKey(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE)).isNotNull();
}
@Test(groups = "short")
public void should_set_routing_key_on_case_sensitive_keyspace_and_table() {
session().execute("CREATE KEYSPACE \"Test\" WITH replication = { "
+ " 'class': 'SimpleStrategy',"
+ " 'replication_factor': '1'"
+ "}");
session().execute("CREATE TABLE \"Test\".\"Foo\" (i int PRIMARY KEY)");
PreparedStatement ps = session().prepare("INSERT INTO \"Test\".\"Foo\" (i) VALUES (?)");
BoundStatement bs = ps.bind(1);
assertThat(bs.getRoutingKey(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE)).isNotNull();
}
@Test(groups = "short", expectedExceptions = InvalidQueryException.class)
public void should_fail_when_prepared_on_another_cluster() throws Exception {
Cluster otherCluster = Cluster.builder()
.addContactPoints(getContactPoints())
.withPort(ccm().getBinaryPort())
.build();
try {
PreparedStatement pst = otherCluster.connect().prepare("select * from system.peers where inet = ?");
BoundStatement bs = pst.bind().setInet(0, InetAddress.getByName("localhost"));
// We expect that the error gets detected without a roundtrip to the server, so use executeAsync
session().executeAsync(bs);
} finally {
otherCluster.close();
}
}
/**
* Tests that, under protocol versions lesser than V4,
* it is NOT possible to execute a prepared statement with unbound values.
* Note that we have to force protocol version to less than V4 because
* higher protocol versions would allow such unbound values to be sent.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-777
* @since 2.2.0
*/
@Test(groups = "short")
public void should_not_allow_unbound_value_on_bound_statement_when_protocol_lesser_than_v4() {
Cluster cluster = register(Cluster.builder()
.addContactPoints(getContactPoints())
.withPort(ccm().getBinaryPort())
.withProtocolVersion(ccm().getProtocolVersion(ProtocolVersion.V3))
.build());
Session session = cluster.connect();
try {
PreparedStatement ps = session.prepare("INSERT INTO " + keyspace + "." + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BoundStatement bs = ps.bind("foo");
assertFalse(bs.isSet("i"));
session.execute(bs);
fail("Should not have executed statement with UNSET values in protocol V3");
} catch (IllegalStateException e) {
assertThat(e.getMessage()).contains("Unset value at index 1");
}
}
/**
* Tests that, under protocol versions lesser that V4,
* it is NOT possible to execute a prepared statement with unbound values.
* Note that we have to force protocol version to less than V4 because
* higher protocol versions would allow such unbound values to be sent.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-777
* @since 2.2.0
*/
@Test(groups = "short")
@CassandraVersion("2.0.0")
public void should_not_allow_unbound_value_on_batch_statement_when_protocol_lesser_than_v4() {
Cluster cluster = register(Cluster.builder()
.addContactPoints(getContactPoints())
.withPort(ccm().getBinaryPort())
.withProtocolVersion(ccm().getProtocolVersion(ProtocolVersion.V3))
.build());
Session session = cluster.connect();
try {
PreparedStatement ps = session.prepare("INSERT INTO " + keyspace + "." + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BatchStatement batch = new BatchStatement();
batch.add(ps.bind("foo"));
// i is UNSET
session.execute(batch);
fail("Should not have executed statement with UNSET values in protocol V3");
} catch (IllegalStateException e) {
assertThat(e.getMessage()).contains("Unset value at index 1");
}
}
/**
* Tests that a tombstone is NOT created when a column in a prepared statement
* is not bound (UNSET flag).
* This only works from protocol V4 onwards.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-777
* @since 2.2.0
*/
@Test(groups = "short")
@CassandraVersion("2.2.0")
public void should_not_create_tombstone_when_unbound_value_on_bound_statement_and_protocol_v4() {
PreparedStatement prepared = session().prepare("INSERT INTO " + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BoundStatement st1 = prepared.bind();
st1.setString(0, "foo");
st1.setInt(1, 1234);
session().execute(st1);
BoundStatement st2 = prepared.bind();
st2.setString(0, "foo");
// i is UNSET
session().execute(st2);
Statement st3 = new SimpleStatement("SELECT i from " + SIMPLE_TABLE + " where k = 'foo'");
st3.enableTracing();
ResultSet rows = session().execute(st3);
assertThat(rows.one().getInt("i")).isEqualTo(1234);
// sleep 10 seconds to make sure the trace will be complete
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
QueryTrace queryTrace = rows.getExecutionInfo().getQueryTrace();
assertEventsContain(queryTrace, "0 tombstone");
}
/**
* Tests that a value that was previously set on a bound statement can be unset by index.
* This only works from protocol V4 onwards.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-930
* @since 2.2.0
*/
@Test(groups = "short")
@CassandraVersion("2.2.0")
public void should_unset_value_by_index() {
PreparedStatement prepared = session().prepare("INSERT INTO " + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BoundStatement bound = prepared.bind();
bound.setString(0, "foo");
bound.setInt(1, 1234);
bound.unset(1);
assertThat(bound.isSet(1)).isFalse();
session().execute(bound);
ResultSet rows = session().execute(
new SimpleStatement("SELECT i from " + SIMPLE_TABLE + " where k = 'foo'")
.enableTracing());
assertThat(rows.one().isNull("i"));
// sleep 10 seconds to make sure the trace will be complete
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
QueryTrace queryTrace = rows.getExecutionInfo().getQueryTrace();
assertEventsContain(queryTrace, "0 tombstone");
}
/**
* Tests that a value that was previously set on a bound statement can be unset by name.
* This only works from protocol V4 onwards.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-930
* @since 2.2.0
*/
@Test(groups = "short")
@CassandraVersion("2.2.0")
public void should_unset_value_by_name() {
PreparedStatement prepared = session().prepare("INSERT INTO " + SIMPLE_TABLE + " (k, i) VALUES (:k, :i)");
BoundStatement bound = prepared.bind();
bound.setString("k", "foo");
bound.setInt("i", 1234);
bound.unset("i");
assertThat(bound.isSet("i")).isFalse();
session().execute(bound);
ResultSet rows = session().execute(
new SimpleStatement("SELECT i from " + SIMPLE_TABLE + " where k = 'foo'")
.enableTracing());
assertThat(rows.one().isNull("i"));
// sleep 10 seconds to make sure the trace will be complete
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
QueryTrace queryTrace = rows.getExecutionInfo().getQueryTrace();
assertEventsContain(queryTrace, "0 tombstone");
}
/**
* Tests that a tombstone is NOT created when a column in a prepared statement
* is not bound (UNSET flag).
* This only works from protocol V4 onwards.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-777
* @since 2.2.0
*/
@Test(groups = "short")
@CassandraVersion("2.2.0")
public void should_not_create_tombstone_when_unbound_value_on_batch_statement_and_protocol_v4() {
PreparedStatement prepared = session().prepare("INSERT INTO " + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BoundStatement st1 = prepared.bind();
st1.setString(0, "foo");
st1.setInt(1, 1234);
session().execute(new BatchStatement().add(st1));
BoundStatement st2 = prepared.bind();
st2.setString(0, "foo");
// i is UNSET
session().execute(new BatchStatement().add(st2));
Statement st3 = new SimpleStatement("SELECT i from " + SIMPLE_TABLE + " where k = 'foo'");
st3.enableTracing();
ResultSet rows = session().execute(st3);
assertThat(rows.one().getInt("i")).isEqualTo(1234);
// sleep 10 seconds to make sure the trace will be complete
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
QueryTrace queryTrace = rows.getExecutionInfo().getQueryTrace();
assertEventsContain(queryTrace, "0 tombstone");
}
/**
* Tests that a tombstone is created when binding a null value to a column in a prepared statement.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-777
* @since 2.2.0
*/
@Test(groups = "long")
public void should_create_tombstone_when_null_value_on_bound_statement() {
PreparedStatement prepared = session().prepare("INSERT INTO " + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BoundStatement st1 = prepared.bind();
st1.setString(0, "foo");
st1.setToNull(1);
session().execute(st1);
Statement st2 = new SimpleStatement("SELECT i from " + SIMPLE_TABLE + " where k = 'foo'");
st2.enableTracing();
ResultSet rows = session().execute(st2);
assertThat(rows.one().isNull(0)).isTrue();
// sleep 10 seconds to make sure the trace will be complete
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
QueryTrace queryTrace = rows.getExecutionInfo().getQueryTrace();
assertEventsContain(queryTrace, "1 tombstone");
}
/**
* Tests that a tombstone is created when binding a null value to a column in a batch statement.
*
* @test_category prepared_statements:binding
* @jira_ticket JAVA-777
* @since 2.2.0
*/
@Test(groups = "short")
@CassandraVersion("2.0.0")
public void should_create_tombstone_when_null_value_on_batch_statement() {
PreparedStatement prepared = session().prepare("INSERT INTO " + SIMPLE_TABLE + " (k, i) VALUES (?, ?)");
BoundStatement st1 = prepared.bind();
st1.setString(0, "foo");
st1.setToNull(1);
session().execute(new BatchStatement().add(st1));
Statement st2 = new SimpleStatement("SELECT i from " + SIMPLE_TABLE + " where k = 'foo'");
st2.enableTracing();
ResultSet rows = session().execute(st2);
assertThat(rows.one().isNull(0)).isTrue();
// sleep 10 seconds to make sure the trace will be complete
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
QueryTrace queryTrace = rows.getExecutionInfo().getQueryTrace();
assertEventsContain(queryTrace, "1 tombstone");
}
private void assertEventsContain(QueryTrace queryTrace, String toFind) {
for (QueryTrace.Event event : queryTrace.getEvents()) {
if (event.getDescription().contains(toFind))
return;
}
fail("Did not find '" + toFind + "' in trace");
}
@Test(groups = "short")
public void should_propagate_idempotence_in_statements() {
session().execute(String.format("CREATE TABLE %s.idempotencetest (i int PRIMARY KEY)", keyspace));
SimpleStatement statement;
PreparedStatement prepared;
BoundStatement bound;
statement = new SimpleStatement(String.format("SELECT * FROM %s.idempotencetest WHERE i = ?", keyspace));
prepared = session().prepare(statement);
bound = prepared.bind(1);
assertThat(prepared.isIdempotent()).isNull();
assertThat(bound.isIdempotent()).isNull();
statement.setIdempotent(true);
prepared = session().prepare(statement);
bound = prepared.bind(1);
assertThat(prepared.isIdempotent()).isTrue();
assertThat(bound.isIdempotent()).isTrue();
statement.setIdempotent(false);
prepared = session().prepare(statement);
bound = prepared.bind(1);
assertThat(prepared.isIdempotent()).isFalse();
assertThat(bound.isIdempotent()).isFalse();
prepared.setIdempotent(true);
bound = prepared.bind(1);
assertThat(bound.isIdempotent()).isTrue();
}
}