@@ -734,8 +734,9 @@ unsafe fn dispatch_array_iterator_method_inner(
734734 // Field 0: backing array pointer (NaN-boxed).
735735 let backing_field = js_object_get_field ( iter_obj ( ) , 0 ) ;
736736 let backing_f64 = f64:: from_bits ( backing_field. bits ( ) ) ;
737- // Array iterators clear their backing array at exhaustion. SQLite's
738- // statement iterator restarts a completed execution on the next call.
737+ // Iterators clear their backing array at exhaustion. A completed
738+ // SQLite statement iterator is also permanently closed; calling
739+ // `StatementSync::iterate()` again creates a separate iterator.
739740 if JSValue :: from_bits ( backing_f64. to_bits ( ) ) . is_undefined ( ) {
740741 return crate :: iter_result:: emit_iter_result_cached (
741742 & scope,
@@ -763,12 +764,7 @@ unsafe fn dispatch_array_iterator_method_inner(
763764 } ;
764765
765766 if idx >= len {
766- if kind == KIND_VALUES_NULL_DONE {
767- // SQLite's statement iterator restarts on the next call.
768- js_object_set_field ( iter_obj ( ) , 1 , JSValue :: number ( 0.0 ) ) ;
769- } else {
770- js_object_set_field ( iter_obj ( ) , 0 , JSValue :: undefined ( ) ) ;
771- }
767+ js_object_set_field ( iter_obj ( ) , 0 , JSValue :: undefined ( ) ) ;
772768 return crate :: iter_result:: emit_iter_result_cached (
773769 & scope,
774770 & iter_h,
@@ -851,3 +847,55 @@ unsafe fn dispatch_array_iterator_method_inner(
851847 _ => f64:: from_bits ( TAG_UNDEFINED ) ,
852848 }
853849}
850+
851+ #[ cfg( test) ]
852+ mod sqlite_iterator_tests {
853+ use super :: * ;
854+ use std:: sync:: atomic:: AtomicU64 ;
855+
856+ unsafe fn result_fields ( result : f64 ) -> ( u64 , u64 ) {
857+ let result = js_nanbox_get_pointer ( result) as * mut ObjectHeader ;
858+ (
859+ js_object_get_field ( result, 0 ) . bits ( ) ,
860+ js_object_get_field ( result, 1 ) . bits ( ) ,
861+ )
862+ }
863+
864+ #[ test]
865+ fn sqlite_iterator_stays_exhausted_after_fused_for_of_drain ( ) {
866+ let _serialized = crate :: array:: test_serialize ( ) ;
867+ let epoch = AtomicU64 :: new ( 0 ) ;
868+ let rows = crate :: array:: js_array_push_f64 ( crate :: array:: js_array_alloc ( 1 ) , 7.0 ) ;
869+ let scope = crate :: gc:: RuntimeHandleScope :: new ( ) ;
870+ let iter_h = scope. root_nanbox_f64 ( array_values_iter_null_done (
871+ js_nanbox_pointer ( rows as i64 ) ,
872+ & epoch,
873+ 0 ,
874+ ) ) ;
875+ let iter = || js_nanbox_get_pointer ( iter_h. get_nanbox_f64 ( ) ) as * mut ObjectHeader ;
876+
877+ unsafe {
878+ // Model the optimized `for...of` driver: one yielded row followed
879+ // by its terminal advance.
880+ let first = dispatch_array_iterator_method_emit ( iter ( ) , "next" , true , true ) ;
881+ assert_eq ! (
882+ result_fields( first) ,
883+ ( crate :: value:: TAG_FALSE , 7.0f64 . to_bits( ) )
884+ ) ;
885+ let done = dispatch_array_iterator_method_emit ( iter ( ) , "next" , true , true ) ;
886+ assert_eq ! (
887+ result_fields( done) ,
888+ ( crate :: value:: TAG_TRUE , crate :: value:: TAG_NULL )
889+ ) ;
890+
891+ // The same iterator must remain closed when user code calls
892+ // `.next()` after the loop. Resetting its cursor used to return
893+ // the first row again here.
894+ let after = dispatch_array_iterator_method ( iter ( ) , "next" ) ;
895+ assert_eq ! (
896+ result_fields( after) ,
897+ ( crate :: value:: TAG_TRUE , crate :: value:: TAG_NULL )
898+ ) ;
899+ }
900+ }
901+ }
0 commit comments