/*
* 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.mapping;
import com.datastax.driver.core.*;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import com.datastax.driver.core.exceptions.UnavailableException;
import com.datastax.driver.core.utils.CassandraVersion;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
import org.testng.SkipException;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import static com.datastax.driver.core.ConsistencyLevel.*;
import static com.datastax.driver.core.ProtocolVersion.V1;
import static com.datastax.driver.mapping.Mapper.Option;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
@SuppressWarnings("unused")
public class MapperOptionTest extends CCMTestsSupport {
ProtocolVersion protocolVersion;
Mapper<User> mapper;
@Override
public void onTestContextInitialized() {
execute("CREATE TABLE user (key int primary key, v text)");
}
@BeforeMethod(groups = "short")
public void setup() {
mapper = new MappingManager(session()).mapper(User.class);
protocolVersion = cluster().getConfiguration().getProtocolOptions().getProtocolVersion();
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_save_options() throws Exception {
Long tsValue = futureTimestamp();
mapper.save(new User(42, "helloworld"), Option.timestamp(tsValue), Option.tracing(true));
assertThat(mapper.get(42).getV()).isEqualTo("helloworld");
Long tsReturned = session().execute("SELECT writetime(v) FROM user WHERE key=" + 42).one().getLong(0);
assertThat(tsReturned).isEqualTo(tsValue);
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_delete_options() {
User todelete = new User(45, "todelete");
mapper.save(todelete);
Option opt = Option.timestamp(35);
BoundStatement bs = (BoundStatement) mapper.deleteQuery(45, opt, Option.consistencyLevel(QUORUM));
assertThat(bs.preparedStatement().getQueryString()).contains("USING TIMESTAMP");
assertThat(bs.getConsistencyLevel()).isEqualTo(QUORUM);
}
@Test(groups = "short", expectedExceptions = {IllegalArgumentException.class})
@CassandraVersion("2.0.0")
void should_use_get_options() {
User user = new User(45, "toget");
mapper.save(user);
BoundStatement bs = (BoundStatement) mapper.getQuery(45, Option.tracing(true), Option.consistencyLevel(ALL));
assertThat(bs.isTracing()).isTrue();
assertThat(bs.getConsistencyLevel()).isEqualTo(ALL);
ResultSet rs = session().execute(bs);
assertThat(rs.getExecutionInfo().getQueryTrace()).isNotNull();
User us = mapper.map(rs).one();
assertThat(us.getV()).isEqualTo("toget");
mapper.getQuery(45, Option.timestamp(1337));
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_options_only_once() {
Long tsValue = futureTimestamp();
mapper.save(new User(43, "helloworld"), Option.timestamp(tsValue));
mapper.save(new User(44, "test"));
Long tsReturned = session().execute("SELECT writetime(v) FROM user WHERE key=" + 44).one().getLong(0);
// Assuming we cannot go back in time (yet) and execute the write at ts=1
assertThat(tsReturned).isNotEqualTo(tsValue);
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_default_options() {
mapper.setDefaultSaveOptions(Option.timestamp(644746L), Option.ttl(76324));
BoundStatement bs = (BoundStatement) mapper.saveQuery(new User(46, "rjhrgce"));
assertThat(bs.preparedStatement().getQueryString()).contains("TIMESTAMP").contains("TTL");
mapper.resetDefaultSaveOptions();
bs = (BoundStatement) mapper.saveQuery(new User(47, "rjhrgce"));
assertThat(bs.preparedStatement().getQueryString()).doesNotContain("TIMESTAMP").doesNotContain("TTL");
mapper.setDefaultDeleteOptions(Option.timestamp(3245L), Option.tracing(true));
bs = (BoundStatement) mapper.deleteQuery(47);
assertThat(bs.preparedStatement().getQueryString()).contains("TIMESTAMP");
assertThat(bs.isTracing()).isTrue();
mapper.resetDefaultDeleteOptions();
bs = (BoundStatement) mapper.deleteQuery(47);
assertThat(bs.preparedStatement().getQueryString()).doesNotContain("TIMESTAMP");
assertThat(bs.isTracing()).isFalse();
bs = (BoundStatement) mapper.saveQuery(new User(46, "rjhrgce"), Option.timestamp(23), Option.consistencyLevel(ConsistencyLevel.ANY));
assertThat(bs.getConsistencyLevel()).isEqualTo(ConsistencyLevel.ANY);
mapper.setDefaultGetOptions(Option.tracing(true));
bs = (BoundStatement) mapper.getQuery(46);
assertThat(bs.isTracing()).isTrue();
mapper.resetDefaultGetOptions();
bs = (BoundStatement) mapper.getQuery(46);
assertThat(bs.isTracing()).isFalse();
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_prioritize_option_over_model_consistency() {
// Generate Write Query and ensure User model writeConsistency is used.
User user = new User(1859, "Steve");
Statement saveDefault = mapper.saveQuery(user);
assertThat(saveDefault.getConsistencyLevel()).isEqualTo(ONE);
// Generate Write Query and ensure provided Option for consistencyLevel is used.
Statement saveProvidedCL = mapper.saveQuery(user, Option.consistencyLevel(QUORUM));
assertThat(saveProvidedCL.getConsistencyLevel()).isEqualTo(QUORUM);
// Generate Read Query and ensure User model readConsistency is used.
Statement readDefault = mapper.getQuery(1859);
assertThat(readDefault.getConsistencyLevel()).isEqualTo(LOCAL_ONE);
// Generate Ready Query and ensure provided Option for consistencyLevel is used.
Statement readProvidedCL = mapper.getQuery(1859, Option.consistencyLevel(LOCAL_QUORUM));
assertThat(readProvidedCL.getConsistencyLevel()).isEqualTo(LOCAL_QUORUM);
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_if_not_exists_option() {
Pattern ifNotExistsPattern = Pattern.compile(".*\\sIF\\s+NOT\\s+EXISTS(\\s+)?;?(\\s+)?$", Pattern.CASE_INSENSITIVE);
User user = new User(42, "Cin Ali");
// test default
BoundStatement saveDefault = (BoundStatement) mapper.saveQuery(user);
DefaultPreparedStatement stmt = (DefaultPreparedStatement) saveDefault.preparedStatement();
assertThat(stmt.getQueryString()).doesNotMatch(ifNotExistsPattern);
// test disabled
saveDefault = (BoundStatement) mapper.saveQuery(user, Option.ifNotExists(false));
stmt = (DefaultPreparedStatement) saveDefault.preparedStatement();
assertThat(stmt.getQueryString()).doesNotMatch(ifNotExistsPattern);
// test enabled
saveDefault = (BoundStatement) mapper.saveQuery(user, Option.ifNotExists(true));
stmt = (DefaultPreparedStatement) saveDefault.preparedStatement();
assertThat(stmt.getQueryString()).matches(ifNotExistsPattern);
// test default enabled
mapper.setDefaultSaveOptions(Option.ifNotExists(true));
saveDefault = (BoundStatement) mapper.saveQuery(user);
stmt = (DefaultPreparedStatement) saveDefault.preparedStatement();
assertThat(stmt.getQueryString()).matches(ifNotExistsPattern);
// test default disabled
mapper.setDefaultSaveOptions(Option.ifNotExists(false));
saveDefault = (BoundStatement) mapper.saveQuery(user);
stmt = (DefaultPreparedStatement) saveDefault.preparedStatement();
assertThat(stmt.getQueryString()).doesNotMatch(ifNotExistsPattern);
}
@Test(groups = "short", expectedExceptions = {IllegalArgumentException.class})
void should_fail_when_using_if_not_exists_on_get_query() {
mapper.get(new User(42, "Cin Ali"), Option.ifNotExists(true));
}
@Test(groups = "short", expectedExceptions = {IllegalArgumentException.class})
void should_fail_when_using_if_not_exists_on_delete_query() {
mapper.delete(new User(42, "Cin Ali"), Option.ifNotExists(true));
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_explicit_options_over_default_options() {
long defaultTimestamp = futureTimestamp();
long explicitTimestamp = futureTimestamp();
mapper.setDefaultSaveOptions(Option.timestamp(defaultTimestamp));
mapper.save(new User(42, "helloworld"), Option.timestamp(explicitTimestamp));
Long savedTimestamp = session().execute("SELECT writetime(v) FROM user WHERE key=" + 42).one().getLong(0);
assertThat(savedTimestamp).isEqualTo(explicitTimestamp);
}
/**
* Cover all versions of save() to check that methods that call each other properly propagate the options
*/
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_save_options_for_all_variants() throws ExecutionException, InterruptedException {
Long timestamp = futureTimestamp();
User user = new User(42, "helloworld");
mapper.save(user, Option.timestamp(timestamp));
assertThat(getWriteTime(user.getKey())).isEqualTo(timestamp);
mapper.delete(user.getKey());
mapper.saveAsync(user, Option.timestamp(timestamp)).get();
assertThat(getWriteTime(user.getKey())).isEqualTo(timestamp);
mapper.delete(user.getKey());
session().execute(mapper.saveQuery(user, Option.timestamp(timestamp)));
assertThat(getWriteTime(user.getKey())).isEqualTo(timestamp);
mapper.delete(user.getKey());
// Set as default and try optionless methods
mapper.setDefaultSaveOptions(Option.timestamp(timestamp));
mapper.save(user);
assertThat(getWriteTime(user.getKey())).isEqualTo(timestamp);
mapper.delete(user.getKey());
mapper.saveAsync(user).get();
assertThat(getWriteTime(user.getKey())).isEqualTo(timestamp);
mapper.delete(user.getKey());
session().execute(mapper.saveQuery(user));
assertThat(getWriteTime(user.getKey())).isEqualTo(timestamp);
mapper.delete(user.getKey());
}
private Long getWriteTime(int key) {
Row row = session().execute("SELECT writetime(v) FROM user WHERE key=" + key).one();
return row.getLong(0);
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_get_options_for_all_variants() throws InterruptedException {
try {
mapper.get(42, Option.consistencyLevel(TWO));
fail("Expected a NoHostAvailableException");
} catch (NoHostAvailableException e) {
assertFirstNodeUnavailable(e);
}
try {
mapper.getAsync(42, Option.consistencyLevel(TWO)).get();
fail("Expected an ExecutionException");
} catch (ExecutionException e) {
assertFirstNodeUnavailable(e);
}
Statement statement = mapper.getQuery(42, Option.consistencyLevel(TWO));
assertThat(statement.getConsistencyLevel()).isEqualTo(TWO);
// Set as default and try optionless methods
mapper.setDefaultGetOptions(Option.consistencyLevel(TWO));
try {
mapper.get(42);
fail("Expected a NoHostAvailableException");
} catch (NoHostAvailableException e) {
assertFirstNodeUnavailable(e);
}
try {
mapper.getAsync(42).get();
fail("Expected an ExecutionException");
} catch (ExecutionException e) {
assertFirstNodeUnavailable(e);
}
statement = mapper.getQuery(42);
assertThat(statement.getConsistencyLevel()).isEqualTo(TWO);
}
@Test(groups = "short")
@CassandraVersion("2.0.0")
void should_use_delete_options_for_all_variants() throws InterruptedException {
User user = new User(42, "helloworld");
try {
mapper.delete(user, Option.consistencyLevel(TWO));
fail("Expected a NoHostAvailableException");
} catch (NoHostAvailableException e) {
assertFirstNodeUnavailable(e);
}
try {
mapper.deleteAsync(user, Option.consistencyLevel(TWO)).get();
fail("Expected an ExecutionException");
} catch (ExecutionException e) {
assertFirstNodeUnavailable(e);
}
Statement statement = mapper.deleteQuery(user, Option.consistencyLevel(TWO));
assertThat(statement.getConsistencyLevel()).isEqualTo(TWO);
try {
mapper.delete(user.getKey(), Option.consistencyLevel(TWO));
fail("Expected a NoHostAvailableException");
} catch (NoHostAvailableException e) {
assertFirstNodeUnavailable(e);
}
try {
mapper.deleteAsync(user.getKey(), Option.consistencyLevel(TWO)).get();
fail("Expected an ExecutionException");
} catch (ExecutionException e) {
assertFirstNodeUnavailable(e);
}
statement = mapper.deleteQuery(user.getKey(), Option.consistencyLevel(TWO));
assertThat(statement.getConsistencyLevel()).isEqualTo(TWO);
// Set as default and try optionless methods
mapper.setDefaultDeleteOptions(Option.consistencyLevel(TWO));
try {
mapper.delete(user);
fail("Expected a NoHostAvailableException");
} catch (NoHostAvailableException e) {
assertFirstNodeUnavailable(e);
}
try {
mapper.deleteAsync(user).get();
fail("Expected an ExecutionException");
} catch (ExecutionException e) {
assertFirstNodeUnavailable(e);
}
statement = mapper.deleteQuery(user);
assertThat(statement.getConsistencyLevel()).isEqualTo(TWO);
try {
mapper.delete(user.getKey());
fail("Expected a NoHostAvailableException");
} catch (NoHostAvailableException e) {
assertFirstNodeUnavailable(e);
}
try {
mapper.deleteAsync(user.getKey()).get();
fail("Expected an ExecutionException");
} catch (ExecutionException e) {
assertFirstNodeUnavailable(e);
}
statement = mapper.deleteQuery(user.getKey());
assertThat(statement.getConsistencyLevel()).isEqualTo(TWO);
}
@Test(groups = "short", expectedExceptions = IllegalArgumentException.class)
void should_fail_if_option_does_not_apply_to_query() {
mapper.get(42, Option.ttl(1));
}
@Test(groups = "short", expectedExceptions = IllegalArgumentException.class)
void should_fail_when_using_ttl_with_protocol_v1() {
if (protocolVersion.compareTo(V1) > 0)
throw new SkipException("Skipped when protocol version > V1");
mapper.saveQuery(new User(42, "helloworld"), Option.ttl(15));
}
@Test(groups = "short", expectedExceptions = IllegalArgumentException.class)
void should_fail_when_using_timestamp_with_protocol_v1() {
if (protocolVersion.compareTo(V1) > 0)
throw new SkipException("Skipped when protocol version > V1");
mapper.saveQuery(new User(42, "helloworld"), Option.timestamp(15));
}
private static void assertFirstNodeUnavailable(NoHostAvailableException e) {
Throwable node1Error = e.getErrors().values().iterator().next();
assertThat(node1Error).isInstanceOf(UnavailableException.class);
}
private static void assertFirstNodeUnavailable(ExecutionException e) {
Throwable cause = e.getCause();
assertThat(cause).isInstanceOf(NoHostAvailableException.class);
assertFirstNodeUnavailable((NoHostAvailableException) cause);
}
private static long futureTimestamp() {
return (System.currentTimeMillis() + 1000) * 1000;
}
@Table(name = "user", readConsistency = "LOCAL_ONE", writeConsistency = "ONE")
public static class User {
@PartitionKey
private int key;
private String v;
public User() {
}
public User(int k, String val) {
this.key = k;
this.v = val;
}
public int getKey() {
return this.key;
}
public void setKey(int pk) {
this.key = pk;
}
public String getV() {
return this.v;
}
public void setV(String val) {
this.v = val;
}
}
}