/* * 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.PagingStateException; import com.datastax.driver.core.utils.CassandraVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import java.util.Iterator; import static org.assertj.core.api.Assertions.assertThat; @CassandraVersion("2.0.0") public class PagingStateTest extends CCMTestsSupport { private static final Logger logger = LoggerFactory.getLogger(PagingStateTest.class); public static final String KEY = "paging_test"; @Override public void onTestContextInitialized() { execute("CREATE TABLE test (k text, v int, PRIMARY KEY (k, v))"); for (int i = 0; i < 100; i++) { execute(String.format("INSERT INTO test (k, v) VALUES ('%s', %d)", KEY, i)); } } /** * Validates that {@link PagingState} can be reused with the same Statement. * * @test_category paging * @expected_result {@link ResultSet} from the query with the provided {@link PagingState} starts from the * subsequent row from the first query. */ @Test(groups = "short") public void should_complete_when_using_paging_state() { SimpleStatement st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); ResultSet result = session().execute(st.setFetchSize(20)); int pageSize = result.getAvailableWithoutFetching(); String savedPagingStateString = result.getExecutionInfo().getPagingState().toString(); st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); result = session().execute(st.setFetchSize(20).setPagingState(PagingState.fromString(savedPagingStateString))); //We have the result starting from the next page we stopped assertThat(result.one().getInt("v")).isEqualTo(pageSize); } /** * <p/> * Validates that if the {@link PagingState} is altered in any way that it may not be reused. * The paging state is altered in the following ways: * <p/> * <ol> * <li>Altering a byte in the paging state raw bytes.</li> * <li>Setting the {@link PagingState} on a different Statement. (should fail hash validation)</li> * </ol> * * @test_category paging * @expected_result {@link PagingState} refused to be reused if it is changed or used on a different statement. */ @Test(groups = "short") public void should_fail_if_paging_state_altered() { boolean setWithFalseContent = false; boolean setWithWrongStatement = false; SimpleStatement st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); ResultSet result = session().execute(st.setFetchSize(20)); PagingState savedPagingState = result.getExecutionInfo().getPagingState(); byte[] savedPagingStateBuffer = savedPagingState.toBytes(); String savedPagingStateString = savedPagingState.toString(); // corrupting the paging state savedPagingStateBuffer[6] = (byte) 42; try { st.setFetchSize(20).setPagingState(PagingState.fromBytes(savedPagingStateBuffer)); } catch (PagingStateException e) { setWithFalseContent = true; logger.debug(e.getMessage()); } finally { assertThat(setWithFalseContent).isTrue(); assertThat(st.getPagingState()).isNull(); } // Changing the statement st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", "paging")); try { st.setFetchSize(20).setPagingState(PagingState.fromString(savedPagingStateString)); } catch (PagingStateException e) { setWithWrongStatement = true; logger.debug(e.getMessage()); } finally { assertThat(setWithWrongStatement).isTrue(); assertThat(st.getPagingState()).isNull(); } } /** * Validates that {@link PagingState} can be reused with a wrapped Statement. * * @test_category paging * @expected_result {@link ResultSet} from the query with the provided {@link PagingState} starts from the * subsequent row from the first query. */ @Test(groups = "short") public void should_use_state_with_wrapped_statement() { Statement st = new TestWrapper(new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY))); ResultSet result = session().execute(st.setFetchSize(20)); int pageSize = result.getAvailableWithoutFetching(); String savedPagingStateString = result.getExecutionInfo().getPagingState().toString(); st = new TestWrapper(new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY))); result = session().execute(st.setFetchSize(20).setPagingState(PagingState.fromString(savedPagingStateString))); //We have the result starting from the next page we stopped assertThat(result.one().getInt("v")).isEqualTo(pageSize); } /** * Validates that {@link PagingState} can be reused with the same {@link BoundStatement}. * * @test_category paging * @expected_result {@link ResultSet} from the query with the provided paging state starts from the subsequent row * from the first query. */ @Test(groups = "short") @CassandraVersion("2.0.0") public void should_be_able_to_use_state_with_bound_statement() { PreparedStatement prepared = session().prepare("SELECT v from test where k=?"); BoundStatement bs = prepared.bind(KEY); ResultSet result = session().execute(bs.setFetchSize(20)); int pageSize = result.getAvailableWithoutFetching(); PagingState pagingState = result.getExecutionInfo().getPagingState(); result = session().execute(bs.setFetchSize(20).setPagingState(pagingState)); //We have the result starting from the next page we stopped assertThat(result.one().getInt("v")).isEqualTo(pageSize); } /** * Validates that {@link PagingState} cannot be reused with a different {@link BoundStatement} than the original, * even if its source {@link PreparedStatement} was the same. * * @test_category paging * @expected_result A failure is thrown when setting paging state on a different {@link BoundStatement}. */ @Test(groups = "short", expectedExceptions = {PagingStateException.class}) @CassandraVersion("2.0.0") public void should_not_be_able_to_use_state_with_different_bound_statement() { PreparedStatement prepared = session().prepare("SELECT v from test where k=?"); BoundStatement bs0 = prepared.bind(KEY); ResultSet result = session().execute(bs0.setFetchSize(20)); PagingState pagingState = result.getExecutionInfo().getPagingState(); BoundStatement bs1 = prepared.bind("different_key"); session().execute(bs1.setFetchSize(20).setPagingState(pagingState)); } /** * Validates if all results of a query are paged in through a queries result set that the {@link PagingState} it * returns will return an empty set when queried with. * * @test_category paging * @expected_result Query with the {@link PagingState} returns 0 rows. */ @Test(groups = "short") public void should_return_no_rows_when_paged_to_end() { SimpleStatement st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); ResultSet result = session().execute(st.setFetchSize(20)); // Consume enough of the iterator to cause all the results to be paged in. Iterator<Row> rowIt = result.iterator(); for (int i = 0; i < 83; i++) { rowIt.next().getInt("v"); } String savedPagingStateString = result.getExecutionInfo().getPagingState().toString(); st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); result = session().execute(st.setFetchSize(20).setPagingState(PagingState.fromString(savedPagingStateString))); assertThat(result.one()).isNull(); } @Test(groups = "unit", expectedExceptions = {PagingStateException.class}) public void should_fail_when_given_invalid_string() { // An invalid string cannot be parsed and a PagingStateException is thrown. PagingState.fromString("0101"); } @Test(groups = "unit", expectedExceptions = {PagingStateException.class}) public void should_fail_when_given_invalid_byte_array() { // Given an expected page state of size 1 and hash of size 1, we should expect 6 bytes, but only receive 5. byte[] complete = {0x00, 0x01, 0x00, 0x01, 0x00}; PagingState.fromBytes(complete); } @Test(groups = "unit", expectedExceptions = {UnsupportedOperationException.class}) public void should_fail_when_setting_paging_state_on_batch_statement() { // Should not be able to set paging state on a batch statement. PagingState emptyStatement = PagingState.fromString("00000000"); BatchStatement batch = new BatchStatement(); batch.setPagingState(emptyStatement); } /** * Validates that the "unsafe" paging state can be reused with the same Statement. * * @test_category paging * @expected_result {@link ResultSet} from the query with the provided raw paging state starts from the * subsequent row from the first query. */ @Test(groups = "short") public void should_complete_when_using_unsafe_paging_state() { SimpleStatement st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); ResultSet result = session().execute(st.setFetchSize(20)); int pageSize = result.getAvailableWithoutFetching(); byte[] savedPagingState = result.getExecutionInfo().getPagingStateUnsafe(); st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY)); result = session().execute(st.setFetchSize(20).setPagingStateUnsafe(savedPagingState)); //We have the result starting from the next page we stopped assertThat(result.one().getInt("v")).isEqualTo(pageSize); } static class TestWrapper extends StatementWrapper { TestWrapper(Statement wrapped) { super(wrapped); } } }