Skip to content

Commit ab4cf9a

Browse files
committed
Expose "unsafe" paging state API (JAVA-759).
1 parent 74285f8 commit ab4cf9a

5 files changed

Lines changed: 119 additions & 4 deletions

File tree

driver-core/CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
-------
66

77
- [improvement] Use Netty's pooled ByteBufAllocator by default (JAVA-756)
8+
- [improvement] Expose "unsafe" paging state API (JAVA-759)
89

910

1011
2.0.10:

driver-core/src/main/java/com/datastax/driver/core/ExecutionInfo.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.nio.ByteBuffer;
1919
import java.util.List;
2020

21+
import com.datastax.driver.core.utils.Bytes;
22+
2123
/**
2224
* Basic information on the execution of a query.
2325
*/
@@ -143,6 +145,22 @@ public PagingState getPagingState() {
143145
return new PagingState(this.pagingState, this.statement);
144146
}
145147

148+
/**
149+
* Returns the "raw" paging state of the query.
150+
*
151+
* Contrary to {@link #getPagingState()}, there will be no validation when
152+
* this is later reinjected into a statement.
153+
*
154+
* @return the paging state or null if there is no next page.
155+
*
156+
* @see Statement#setPagingStateUnsafe(byte[])
157+
*/
158+
public byte[] getPagingStateUnsafe() {
159+
if (this.pagingState == null)
160+
return null;
161+
return Bytes.getArray(this.pagingState);
162+
}
163+
146164
/**
147165
* Whether the cluster had reached schema agreement after the execution of this query.
148166
*

driver-core/src/main/java/com/datastax/driver/core/Statement.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,9 @@ public int getFetchSize() {
267267
* This will cause the next execution of this statement to fetch results from a given
268268
* page, rather than restarting from the beginning.
269269
* <p>
270-
* You get the paging state from a previous execution of the statement. This is typically
271-
* used to iterate in a "stateless" manner (e.g. across HTTP requests):
270+
* You get the paging state from a previous execution of the statement (see
271+
* {@link ExecutionInfo#getPagingState()}.
272+
* This is typically used to iterate in a "stateless" manner (e.g. across HTTP requests):
272273
* <pre>
273274
* {@code
274275
* Statement st = new SimpleStatement("your query");
@@ -296,9 +297,17 @@ public int getFetchSize() {
296297
* }
297298
* </pre>
298299
* <p>
299-
* Note that the paging state can only be reused between perfectly identical statements
300+
* The paging state can only be reused between perfectly identical statements
300301
* (same query string, same bound parameters). Altering the contents of the paging state
301302
* or trying to set it on a different statement will cause this method to fail.
303+
* <p>
304+
* Note that, due to internal implementation details, the paging state is not portable
305+
* across native protocol versions (see the
306+
* <a href="http://datastax.github.io/java-driver/features/native_protocol">online documentation</a>
307+
* for more explanations about the native protocol).
308+
* This means that {@code PagingState} instances generated with an old version won't work
309+
* with a higher version. If that is a problem for you, consider using the "unsafe" API (see
310+
* {@link #setPagingStateUnsafe(byte[])}).
302311
*
303312
* @param pagingState the paging state to set, or {@code null} to remove any state that was
304313
* previously set on this statement.
@@ -323,6 +332,30 @@ public Statement setPagingState(PagingState pagingState) {
323332
return this;
324333
}
325334

335+
/**
336+
* Sets the paging state.
337+
* <p>
338+
* Contrary to {@link #setPagingState(PagingState)}, this method takes the "raw" form of the
339+
* paging state (previously extracted with {@link ExecutionInfo#getPagingStateUnsafe()}.
340+
* It won't validate that this statement matches the one that the paging state was extracted from.
341+
* If the paging state was altered in any way, you will get unpredictable behavior from
342+
* Cassandra (ranging from wrong results to a query failure). If you decide to use this variant,
343+
* it is strongly recommended to add your own validation (for example, signing the raw state with
344+
* a private key).
345+
*
346+
* @param pagingState the paging state to set, or {@code null} to remove any state that was
347+
* previously set on this statement.
348+
* @return this {@code Statement} object.
349+
*/
350+
public Statement setPagingStateUnsafe(byte[] pagingState) {
351+
if (pagingState == null) {
352+
this.pagingState = null;
353+
} else {
354+
this.pagingState = ByteBuffer.wrap(pagingState);
355+
}
356+
return this;
357+
}
358+
326359
ByteBuffer getPagingState() {
327360
return pagingState;
328361
}

driver-core/src/test/java/com/datastax/driver/core/PagingStateTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,26 @@ public void should_fail_when_setting_paging_state_on_batch_statement() {
209209
batch.setPagingState(emptyStatement);
210210
}
211211

212+
/**
213+
* Validates that the "unsafe" paging state can be reused with the same Statement.
214+
*
215+
* @test_category paging
216+
* @expected_result {@link ResultSet} from the query with the provided raw paging state starts from the
217+
* subsequent row from the first query.
218+
*/
219+
@Test(groups = "short")
220+
public void should_complete_when_using_unsafe_paging_state() {
221+
SimpleStatement st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY));
222+
ResultSet result = session.execute(st.setFetchSize(20));
223+
int pageSize = result.getAvailableWithoutFetching();
224+
byte[] savedPagingState = result.getExecutionInfo().getPagingStateUnsafe();
225+
226+
st = new SimpleStatement(String.format("SELECT v FROM test WHERE k='%s'", KEY));
227+
result = session.execute(st.setFetchSize(20).setPagingStateUnsafe(savedPagingState));
228+
229+
//We have the result starting from the next page we stopped
230+
assertThat(result.one().getInt("v")).isEqualTo(pageSize);
231+
}
232+
212233
}
213234

features/paging/README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,46 @@ if (nextPage != null) {
172172
```
173173

174174
[result_set]:http://docs.datastax.com/en/drivers/java/2.0/com/datastax/driver/core/ResultSet.html
175-
[paging_state]:http://docs.datastax.com/en/drivers/java/2.0/com/datastax/driver/core/PagingState.html
175+
[paging_state]:http://docs.datastax.com/en/drivers/java/2.0/com/datastax/driver/core/PagingState.html
176+
177+
Due to internal implementation details, `PagingState` instances are not
178+
portable across [native protocol](../native_protocol/) versions. This
179+
could become a problem in the following scenario:
180+
181+
* you're using the driver 2.0.x and Cassandra 2.0.x, and therefore
182+
native protocol v2;
183+
* a user bookmarks a link to your web service that contains a serialized
184+
paging state;
185+
* you upgrade your server stack to use the driver 2.1.x and Cassandra
186+
2.1.x, so you're now using protocol v3;
187+
* the user tries to reload their bookmark, but the paging state was
188+
serialized with protocol v2, so trying to reuse it will fail.
189+
190+
If this is not acceptable for you, you might want to consider the unsafe
191+
API described in the next section.
192+
193+
#### Unsafe API
194+
195+
As an alternative to the standard API, there are two methods that
196+
manipulate a raw `byte[]` instead of a `PagingState` object:
197+
198+
* [ExecutionInfo#getPagingStateUnsafe()][gpsu]
199+
* [Statement#setPagingStateUnsafe(byte[])][spsu]
200+
201+
These low-level methods perform no validation on their arguments;
202+
therefore nothing protects you from reusing a paging state that was
203+
generated from a different statement, or altered in any way. This could
204+
result in sending a corrupt paging state to Cassandra, with
205+
unpredictable consequences (ranging from wrong results to a query
206+
failure).
207+
208+
There are two situations where you might want to use the unsafe API:
209+
210+
* you never expose the paging state to end users and you are confident
211+
that it won't get altered;
212+
* you want portability across protocol versions and/or you prefer
213+
implementing your own validation logic (for example, signing the raw
214+
state with a private key).
215+
216+
[gpsu]: http://www.datastax.com/drivers/java/2.0/com/datastax/driver/core/ExecutionInfo.html#getPagingStateUnsafe()
217+
[spsu]: http://www.datastax.com/drivers/java/2.0/com/datastax/driver/core/Statement.html#setPagingStateUnsafe(byte[])

0 commit comments

Comments
 (0)