/* * 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; } } }