@@ -15,6 +15,7 @@ use diesel::{
1515use serde:: { Deserialize , Deserializer , Serialize , Serializer , ser} ;
1616
1717use super :: error:: SyncstorageDbError ;
18+ use crate :: { Sorting , params} ;
1819
1920/// Get the time since the UNIX epoch in milliseconds
2021fn ms_since_epoch ( ) -> i64 {
@@ -215,6 +216,39 @@ pub fn to_rfc3339(val: i64) -> Result<String, SyncstorageDbError> {
215216 ) ) )
216217}
217218
219+ /// Encode a timestamp and the number of rows to skip for BSO query pagination.
220+ pub fn encode_next_offset (
221+ sort : Sorting ,
222+ prev_offset : u64 ,
223+ prev_timestamp : Option < i64 > ,
224+ modified_timestamps : & [ i64 ] ,
225+ ) -> String {
226+ if let Sorting :: Index = sort {
227+ return ( prev_offset + modified_timestamps. len ( ) as u64 ) . to_string ( ) ;
228+ }
229+ if modified_timestamps. is_empty ( ) {
230+ return prev_offset. to_string ( ) ;
231+ }
232+
233+ let bound = * modified_timestamps. last ( ) . unwrap ( ) ;
234+ let mut skip = 1usize ;
235+
236+ skip += modified_timestamps[ ..modified_timestamps. len ( ) - 1 ]
237+ . iter ( )
238+ . rev ( )
239+ . take_while ( |& & m| m == bound)
240+ . count ( ) ;
241+ if skip == modified_timestamps. len ( ) && prev_timestamp == Some ( bound) {
242+ skip += prev_offset as usize ;
243+ }
244+
245+ params:: Offset {
246+ timestamp : Some ( SyncTimestamp :: from_milliseconds ( bound as u64 ) ) ,
247+ offset : skip as u64 ,
248+ }
249+ . to_string ( )
250+ }
251+
218252#[ cfg( test) ]
219253mod tests {
220254 use std:: error:: Error ;
@@ -262,4 +296,51 @@ mod tests {
262296 assert_eq ! ( zero, SyncTimestamp :: from_i64( 0 ) . unwrap( ) ) ;
263297 assert_eq ! ( zero, SyncTimestamp :: from_seconds( 0.00 ) ) ;
264298 }
299+
300+ mod encode_next_offset_tests {
301+ use crate :: Sorting ;
302+ use crate :: util:: encode_next_offset;
303+
304+ #[ test]
305+ fn index_sort_returns_numeric_offset ( ) {
306+ let result = encode_next_offset ( Sorting :: Index , 50 , None , & [ 19 , 83 , 747 ] ) ;
307+ assert_eq ! ( result, "53" ) ;
308+ }
309+
310+ #[ test]
311+ fn empty_modified_timestamps_returns_prev_offset ( ) {
312+ let result = encode_next_offset ( Sorting :: Newest , 42 , None , & [ ] ) ;
313+ assert_eq ! ( result, "42" ) ;
314+ }
315+
316+ #[ test]
317+ fn unique_last_timestamp_skip_is_one ( ) {
318+ let result = encode_next_offset ( Sorting :: Newest , 0 , None , & [ 5555 , 4242 , 3838 ] ) ;
319+ assert_eq ! ( result, "3838:1" ) ;
320+ }
321+
322+ #[ test]
323+ fn skip_counts_identical_tail_timestamps ( ) {
324+ let result = encode_next_offset ( Sorting :: Newest , 0 , None , & [ 5000 , 3838 , 3838 ] ) ;
325+ assert_eq ! ( result, "3838:2" ) ;
326+ }
327+
328+ #[ test]
329+ fn identical_timestamps_no_prev_bound_match ( ) {
330+ let result = encode_next_offset ( Sorting :: Newest , 0 , Some ( 2048 ) , & [ 3838 , 3838 , 3838 ] ) ;
331+ assert_eq ! ( result, "3838:3" ) ;
332+ }
333+
334+ #[ test]
335+ fn identical_timestamps_with_matching_prev_bound_sums ( ) {
336+ let result = encode_next_offset ( Sorting :: Newest , 2 , Some ( 9001 ) , & [ 9001 , 9001 , 9001 ] ) ;
337+ assert_eq ! ( result, "9001:5" ) ;
338+ }
339+
340+ #[ test]
341+ fn oldest_sort_works ( ) {
342+ let result = encode_next_offset ( Sorting :: Oldest , 0 , None , & [ 8999 , 9000 , 9001 ] ) ;
343+ assert_eq ! ( result, "9001:1" ) ;
344+ }
345+ }
265346}
0 commit comments