@@ -91,6 +91,9 @@ impl Iterator for TableIndexPointIter<'_> {
9191///
9292/// See module docs for info about specialization.
9393enum TypedIndexRangeIter < ' a > {
94+ /// The range itself provided was empty.
95+ RangeEmpty ,
96+
9497 // All the non-unique btree index iterators.
9598 BtreeBool ( BtreeIndexRangeIter < ' a , bool > ) ,
9699 BtreeU8 ( BtreeIndexRangeIter < ' a , u8 > ) ,
@@ -137,6 +140,8 @@ impl Iterator for TypedIndexRangeIter<'_> {
137140 type Item = RowPointer ;
138141 fn next ( & mut self ) -> Option < Self :: Item > {
139142 match self {
143+ Self :: RangeEmpty => None ,
144+
140145 Self :: BtreeBool ( this) => this. next ( ) . copied ( ) ,
141146 Self :: BtreeU8 ( this) => this. next ( ) . copied ( ) ,
142147 Self :: BtreeI8 ( this) => this. next ( ) . copied ( ) ,
@@ -827,6 +832,24 @@ impl TypedIndex {
827832 }
828833
829834 fn seek_range ( & self , range : & impl RangeBounds < AlgebraicValue > ) -> TypedIndexRangeIter < ' _ > {
835+ // Copied from `RangeBounds::is_empty` as it's unstable.
836+ // TODO(centril): replace once stable.
837+ fn is_empty < T : PartialOrd > ( bounds : & impl RangeBounds < T > ) -> bool {
838+ use core:: ops:: Bound :: * ;
839+ !match ( bounds. start_bound ( ) , bounds. end_bound ( ) ) {
840+ ( Unbounded , _) | ( _, Unbounded ) => true ,
841+ ( Included ( start) , Excluded ( end) )
842+ | ( Excluded ( start) , Included ( end) )
843+ | ( Excluded ( start) , Excluded ( end) ) => start < end,
844+ ( Included ( start) , Included ( end) ) => start <= end,
845+ }
846+ }
847+
848+ // Ensure we don't panic inside `BTreeMap::seek_range`.
849+ if is_empty ( range) {
850+ return RangeEmpty ;
851+ }
852+
830853 fn mm_iter_at_type < ' a , T : Ord > (
831854 this : & ' a BtreeIndex < T > ,
832855 range : & impl RangeBounds < AlgebraicValue > ,
@@ -1355,10 +1378,12 @@ mod test {
13551378 use crate :: page_pool:: PagePool ;
13561379 use crate :: { blob_store:: HashMapBlobStore , table:: test:: table} ;
13571380 use core:: ops:: Bound :: * ;
1381+ use decorum:: Total ;
13581382 use proptest:: prelude:: * ;
13591383 use proptest:: { collection:: vec, test_runner:: TestCaseResult } ;
13601384 use spacetimedb_data_structures:: map:: HashMap ;
13611385 use spacetimedb_primitives:: ColId ;
1386+ use spacetimedb_sats:: proptest:: { generate_algebraic_value, generate_primitive_algebraic_type} ;
13621387 use spacetimedb_sats:: {
13631388 product,
13641389 proptest:: { generate_product_value, generate_row_type} ,
@@ -1406,6 +1431,34 @@ mod test {
14061431 index. is_unique ( ) . then ( || index. seek_point ( row) )
14071432 }
14081433
1434+ fn successor_of_primitive ( av : & AlgebraicValue ) -> Option < AlgebraicValue > {
1435+ use AlgebraicValue :: * ;
1436+ match av {
1437+ Min | Max | Sum ( _) | Product ( _) | Array ( _) | String ( _) => unimplemented ! ( ) ,
1438+
1439+ Bool ( false ) => Some ( Bool ( true ) ) ,
1440+ Bool ( true ) => None ,
1441+ I8 ( x) => x. checked_add ( 1 ) . map ( I8 ) ,
1442+ U8 ( x) => x. checked_add ( 1 ) . map ( U8 ) ,
1443+ I16 ( x) => x. checked_add ( 1 ) . map ( I16 ) ,
1444+ U16 ( x) => x. checked_add ( 1 ) . map ( U16 ) ,
1445+ I32 ( x) => x. checked_add ( 1 ) . map ( I32 ) ,
1446+ U32 ( x) => x. checked_add ( 1 ) . map ( U32 ) ,
1447+ I64 ( x) => x. checked_add ( 1 ) . map ( I64 ) ,
1448+ U64 ( x) => x. checked_add ( 1 ) . map ( U64 ) ,
1449+ I128 ( x) => x. 0 . checked_add ( 1 ) . map ( Packed ) . map ( I128 ) ,
1450+ U128 ( x) => x. 0 . checked_add ( 1 ) . map ( Packed ) . map ( U128 ) ,
1451+ I256 ( x) => x. checked_add ( 1 . into ( ) ) . map ( Box :: new) . map ( I256 ) ,
1452+ U256 ( x) => x. checked_add ( 1u8 . into ( ) ) . map ( Box :: new) . map ( U256 ) ,
1453+ F32 ( x) => Some ( F32 ( Total :: from_inner ( x. into_inner ( ) . next_up ( ) ) ) ) ,
1454+ F64 ( x) => Some ( F64 ( Total :: from_inner ( x. into_inner ( ) . next_up ( ) ) ) ) ,
1455+ }
1456+ }
1457+
1458+ fn gen_primitive_ty_and_val ( ) -> impl Strategy < Value = ( AlgebraicType , AlgebraicValue ) > {
1459+ generate_primitive_algebraic_type ( ) . prop_flat_map ( |ty| ( Just ( ty. clone ( ) ) , generate_algebraic_value ( ty) ) )
1460+ }
1461+
14091462 proptest ! {
14101463 #![ proptest_config( ProptestConfig { max_shrink_iters: 0x10000000 , ..Default :: default ( ) } ) ]
14111464 #[ test]
@@ -1560,5 +1613,38 @@ mod test {
15601613 test_seek( & index, & val_to_ptr, ( Excluded ( V ( prev) ) , Excluded ( V ( needle) ) ) , [ ] ) ?;
15611614 test_seek( & index, & val_to_ptr, ( Excluded ( V ( prev) ) , Excluded ( V ( next) ) ) , [ needle] ) ?;
15621615 }
1616+
1617+ #[ test]
1618+ fn empty_range_scans_dont_panic( ( ( ty, val) , is_unique) in ( gen_primitive_ty_and_val( ) , any:: <bool >( ) ) ) {
1619+ let succ = successor_of_primitive( & val) ;
1620+ prop_assume!( succ. is_some( ) ) ;
1621+ let succ = succ. unwrap( ) ;
1622+
1623+ // Construct the index.
1624+ let row_ty = ProductType :: from( [ ty. clone( ) ] ) ;
1625+ let mut index = new_index( & row_ty, & [ 0 ] . into( ) , is_unique) ;
1626+
1627+ // Construct the table and add `val` as a row.
1628+ let mut table = table( row_ty) ;
1629+ let pool = PagePool :: new_for_test( ) ;
1630+ let mut blob_store = HashMapBlobStore :: default ( ) ;
1631+ let pv = product![ val. clone( ) ] ;
1632+ let row_ref = table. insert( & pool, & mut blob_store, & pv) . unwrap( ) . 1 ;
1633+
1634+ // Add the row to the index.
1635+ unsafe { index. check_and_insert( row_ref) . unwrap( ) ; }
1636+
1637+ // Seek the empty ranges.
1638+ let rows = index. seek_range( & ( & succ..& val) ) . collect:: <Vec <_>>( ) ;
1639+ assert_eq!( rows, [ ] ) ;
1640+ let rows = index. seek_range( & ( & succ..=& val) ) . collect:: <Vec <_>>( ) ;
1641+ assert_eq!( rows, [ ] ) ;
1642+ let rows = index. seek_range( & ( Excluded ( & succ) , Included ( & val) ) ) . collect:: <Vec <_>>( ) ;
1643+ assert_eq!( rows, [ ] ) ;
1644+ let rows = index. seek_range( & ( Excluded ( & succ) , Excluded ( & val) ) ) . collect:: <Vec <_>>( ) ;
1645+ assert_eq!( rows, [ ] ) ;
1646+ let rows = index. seek_range( & ( Excluded ( & val) , Excluded ( & val) ) ) . collect:: <Vec <_>>( ) ;
1647+ assert_eq!( rows, [ ] ) ;
1648+ }
15631649 }
15641650}
0 commit comments