@@ -591,17 +591,36 @@ class RocksDBTimestampEncoderOperationsSuite extends SharedSparkSession
591591 Some (keyAndTimestampToRow(" key1" , 1 , 0L )),
592592 Some (keyAndTimestampToRow(" key1" , 1 , 1001L )))
593593 val boundedResults = boundedIter.map { pair =>
594- (pair.key.getLong(2 ), pair.value.getInt(0 ))
594+ (pair.key.getString( 0 ), pair.key. getLong(2 ), pair.value.getInt(0 ))
595595 }.toList
596596 boundedIter.close()
597597
598598 val expectedTimestamps = diverseTimestamps.filter(ts => ts >= 0 && ts <= 1000 ).sorted
599- assert(boundedResults.map(_._1 ).distinct === expectedTimestamps)
599+ assert(boundedResults.map(_._2 ).distinct === expectedTimestamps)
600600 val expectedValues = diverseTimestamps.zipWithIndex
601601 .filter { case (ts, _) => ts >= 0 && ts <= 1000 }
602602 .sortBy(_._1)
603603 .flatMap { case (_, idx) => Seq (idx * 10 , idx * 10 + 1 ) }
604- assert(boundedResults.map(_._2) === expectedValues)
604+ assert(boundedResults.map(_._3) === expectedValues)
605+ assert(boundedResults.forall(_._1 == " key1" ))
606+
607+ // Exact bound: startKey is inclusive, endKey is exclusive.
608+ // 9 exists in diverseTimestamps, 90 exists in diverseTimestamps.
609+ val exactIter = store.scanWithMultiValues(
610+ Some (keyAndTimestampToRow(" key1" , 1 , 9L )),
611+ Some (keyAndTimestampToRow(" key1" , 1 , 90L )))
612+ val exactResults = exactIter.map(_.key.getLong(2 )).toList
613+ exactIter.close()
614+ val exactResultsDistinct = exactResults.distinct
615+ assert(exactResultsDistinct === diverseTimestamps
616+ .filter(ts => ts >= 9 && ts < 90 ).sorted)
617+ assert(exactResultsDistinct.contains(9L ))
618+ assert(! exactResultsDistinct.contains(90L ))
619+
620+ // Postfix timestamp encoder places the timestamp after the key prefix.
621+ // With different key prefixes, None in startKey or endKey would scan across
622+ // key boundaries, which is not meaningful for postfix encoding. Hence we only
623+ // test bounded ranges with explicit keys here.
605624
606625 // Full range [MinValue, MaxValue)
607626 val fullIter = store.scanWithMultiValues(
@@ -612,6 +631,15 @@ class RocksDBTimestampEncoderOperationsSuite extends SharedSparkSession
612631
613632 assert(fullResults.distinct === diverseTimestamps.sorted)
614633
634+ // Bounded negative range [-300, 0)
635+ val negIter = store.scanWithMultiValues(
636+ Some (keyAndTimestampToRow(" key1" , 1 , - 300L )),
637+ Some (keyAndTimestampToRow(" key1" , 1 , 0L )))
638+ val negResults = negIter.map(_.key.getLong(2 )).toList
639+ negIter.close()
640+ assert(negResults.distinct === diverseTimestamps
641+ .filter(ts => ts >= - 300 && ts < 0 ).sorted)
642+
615643 // Empty range [10, 31) - no diverseTimestamps entries between 9 and 32
616644 val emptyIter = store.scanWithMultiValues(
617645 Some (keyAndTimestampToRow(" key1" , 1 , 10L )),
@@ -624,6 +652,9 @@ class RocksDBTimestampEncoderOperationsSuite extends SharedSparkSession
624652 }
625653 }
626654
655+ // Sanity test for prefix encoder scan. Full scan coverage is in RocksDBStateStoreSuite's
656+ // "rocksdb range scan - scan" and "rocksdb range scan - scanWithMultiValues" tests.
657+ // This test verifies the timestamp prefix encoder integration works correctly.
627658 test(s " scan with prefix encoder (encoding = $encoding) " ) {
628659 tryWithProviderResource(
629660 newStoreProviderWithTimestampEncoder(
@@ -643,10 +674,13 @@ class RocksDBTimestampEncoderOperationsSuite extends SharedSparkSession
643674 // None startKey scans from beginning up to 301 (exclusive)
644675 val iter1 = store.scanWithMultiValues(None ,
645676 Some (keyAndTimestampToRow(" key1" , 1 , 301L )))
646- val results1 = iter1.map(_.key.getLong(2 )).toList
677+ val results1 = iter1.map { pair =>
678+ (pair.key.getString(0 ), pair.key.getLong(2 ))
679+ }.toList
647680 iter1.close()
648681
649- assert(results1 === Seq (100L , 150L , 200L , 300L ))
682+ assert(results1 === Seq (
683+ (" key1" , 100L ), (" key2" , 150L ), (" key1" , 200L ), (" key1" , 300L )))
650684
651685 // Boundary safety: endKey at 201, includes everything up to 200
652686 // regardless of join key
@@ -657,54 +691,14 @@ class RocksDBTimestampEncoderOperationsSuite extends SharedSparkSession
657691 }.toList
658692 iter2.close()
659693
660- assert(results2.map(_._2) === Seq (100L , 150L , 200L ))
694+ assert(results2 === Seq (
695+ (" key1" , 100L ), (" key2" , 150L ), (" key1" , 200L )))
661696 } finally {
662697 store.abort()
663698 }
664699 }
665700 }
666701
667- test(s " scan single-value variant (encoding = $encoding) " ) {
668- tryWithProviderResource(
669- newStoreProviderWithTimestampEncoder(
670- encoderType = " postfix" ,
671- useMultipleValuesPerKey = false ,
672- dataEncoding = encoding)
673- ) { provider =>
674- val store = provider.getStore(0 )
675-
676- try {
677- diverseTimestamps.foreach { ts =>
678- store.put(keyAndTimestampToRow(" key1" , 1 , ts), valueToRow(ts.toInt))
679- }
680-
681- // Bounded positive range [0, 100)
682- val posIter = store.scan(
683- Some (keyAndTimestampToRow(" key1" , 1 , 0L )),
684- Some (keyAndTimestampToRow(" key1" , 1 , 100L )))
685- val posResults = posIter.map { pair =>
686- (pair.key.getLong(2 ), pair.value.getInt(0 ))
687- }.toList
688- posIter.close()
689-
690- val expectedPosTs = diverseTimestamps.filter(ts => ts >= 0 && ts < 100 ).sorted
691- assert(posResults.map(_._1) === expectedPosTs)
692- assert(posResults.map(_._2) === expectedPosTs.map(_.toInt))
693-
694- // Bounded negative range [-300, 0)
695- val negIter = store.scan(
696- Some (keyAndTimestampToRow(" key1" , 1 , - 300L )),
697- Some (keyAndTimestampToRow(" key1" , 1 , 0L )))
698- val negResults = negIter.map(_.key.getLong(2 )).toList
699- negIter.close()
700-
701- val expectedNegTs = diverseTimestamps.filter(ts => ts >= - 300 && ts < 0 ).sorted
702- assert(negResults === expectedNegTs)
703- } finally {
704- store.abort()
705- }
706- }
707- }
708702 }
709703
710704 // Helper methods to create test data
0 commit comments