@@ -263,6 +263,7 @@ mod tests {
263263 use vortex_array:: { IntoArray , assert_arrays_eq} ;
264264 use vortex_buffer:: { Buffer , BufferMut , buffer} ;
265265 use vortex_dtype:: Nullability ;
266+ use vortex_vector:: { VectorMutOps , VectorOps } ;
266267
267268 use super :: * ;
268269 use crate :: BitPackedVTable ;
@@ -452,4 +453,268 @@ mod tests {
452453 // Verify all values were correctly unpacked including patches.
453454 assert_arrays_eq ! ( result, PrimitiveArray :: from_iter( values) ) ;
454455 }
456+
457+ /// Test basic unpacking to primitive vector for multiple types and sizes.
458+ #[ test]
459+ fn test_unpack_to_primitive_vector_basic ( ) {
460+ // Test with u8 values.
461+ let u8_values = PrimitiveArray :: from_iter ( [ 5u8 , 10 , 15 , 20 , 25 ] ) ;
462+ let u8_bitpacked = bitpack_encode ( & u8_values, 5 , None ) . unwrap ( ) ;
463+ let u8_vector = unpack_to_primitive_vector ( & u8_bitpacked) ;
464+ // Compare with existing unpack method.
465+ let expected = unpack_array ( & u8_bitpacked) ;
466+ assert_eq ! ( u8_vector. len( ) , expected. len( ) ) ;
467+ // Verify the vector matches expected values by checking specific elements.
468+ let _u8_frozen = u8_vector. freeze ( ) ;
469+ // We know both produce the same primitive values, just in different forms.
470+
471+ // Test with u32 values - empty array.
472+ let u32_empty: PrimitiveArray = PrimitiveArray :: from_iter ( Vec :: < u32 > :: new ( ) ) ;
473+ let u32_empty_bp = bitpack_encode ( & u32_empty, 0 , None ) . unwrap ( ) ;
474+ let u32_empty_vec = unpack_to_primitive_vector ( & u32_empty_bp) ;
475+ assert_eq ! ( u32_empty_vec. len( ) , 0 ) ;
476+
477+ // Test with u16 values - exactly one chunk (1024 elements).
478+ let u16_values = PrimitiveArray :: from_iter ( 0u16 ..1024 ) ;
479+ let u16_bitpacked = bitpack_encode ( & u16_values, 10 , None ) . unwrap ( ) ;
480+ let u16_vector = unpack_to_primitive_vector ( & u16_bitpacked) ;
481+ assert_eq ! ( u16_vector. len( ) , 1024 ) ;
482+
483+ // Test with i32 values - partial chunk (1025 elements).
484+ let i32_values = PrimitiveArray :: from_iter ( ( 0i32 ..1025 ) . map ( |x| x % 512 ) ) ;
485+ let i32_bitpacked = bitpack_encode ( & i32_values, 9 , None ) . unwrap ( ) ;
486+ let i32_vector = unpack_to_primitive_vector ( & i32_bitpacked) ;
487+ assert_eq ! ( i32_vector. len( ) , 1025 ) ;
488+
489+ // Verify consistency: unpack_to_primitive_vector and unpack_array should produce same values.
490+ let i32_array = unpack_array ( & i32_bitpacked) ;
491+ assert_eq ! ( i32_vector. len( ) , i32_array. len( ) ) ;
492+ }
493+
494+ /// Test unpacking with patches at various positions.
495+ #[ test]
496+ fn test_unpack_to_primitive_vector_with_patches ( ) {
497+ // Create an array where patches are needed at start, middle, and end.
498+ let values: Vec < u32 > = vec ! [
499+ 2000 , // Patch at start
500+ 5 , 10 , 15 , 20 , 25 , 30 , 3000 , // Patch in middle
501+ 35 , 40 , 45 , 50 , 55 , 4000 , // Patch at end
502+ ] ;
503+ let array = PrimitiveArray :: from_iter ( values. clone ( ) ) ;
504+
505+ // Bitpack with a small bit width to force patches.
506+ let bitpacked = bitpack_encode ( & array, 6 , None ) . unwrap ( ) ;
507+ assert ! ( bitpacked. patches( ) . is_some( ) , "Should have patches" ) ;
508+
509+ // Unpack to vector.
510+ let vector = unpack_to_primitive_vector ( & bitpacked) ;
511+
512+ // Verify length and that patches were applied.
513+ assert_eq ! ( vector. len( ) , values. len( ) ) ;
514+ // The vector should have the patched values, which unpack_array also produces.
515+ let expected = unpack_array ( & bitpacked) ;
516+ assert_eq ! ( vector. len( ) , expected. len( ) ) ;
517+
518+ // Test with a larger array with multiple patches across chunks.
519+ let large_values: Vec < u16 > = ( 0 ..3072 )
520+ . map ( |i| {
521+ if i % 500 == 0 {
522+ 2000 + i as u16 // Values that need patches
523+ } else {
524+ ( i % 256 ) as u16 // Values that fit in 8 bits
525+ }
526+ } )
527+ . collect ( ) ;
528+ let large_array = PrimitiveArray :: from_iter ( large_values) ;
529+ let large_bitpacked = bitpack_encode ( & large_array, 8 , None ) . unwrap ( ) ;
530+ assert ! ( large_bitpacked. patches( ) . is_some( ) ) ;
531+
532+ let large_vector = unpack_to_primitive_vector ( & large_bitpacked) ;
533+ assert_eq ! ( large_vector. len( ) , 3072 ) ;
534+ }
535+
536+ /// Test unpacking with nullability and validity masks.
537+ #[ test]
538+ fn test_unpack_to_primitive_vector_nullability ( ) {
539+ // Test with null values at various positions.
540+ let values = Buffer :: from_iter ( [ 100u32 , 0 , 200 , 0 , 300 , 0 , 400 ] ) ;
541+ let validity = Validity :: from_iter ( [ true , false , true , false , true , false , true ] ) ;
542+ let array = PrimitiveArray :: new ( values, validity) ;
543+
544+ let bitpacked = bitpack_encode ( & array, 9 , None ) . unwrap ( ) ;
545+ let vector = unpack_to_primitive_vector ( & bitpacked) ;
546+
547+ // Verify length.
548+ assert_eq ! ( vector. len( ) , 7 ) ;
549+ // Validity should be preserved when unpacking.
550+
551+ // Test combining patches with nullability.
552+ let patch_values = Buffer :: from_iter ( [ 10u16 , 0 , 2000 , 0 , 30 , 3000 , 0 ] ) ;
553+ let patch_validity = Validity :: from_iter ( [ true , false , true , false , true , true , false ] ) ;
554+ let patch_array = PrimitiveArray :: new ( patch_values, patch_validity) ;
555+
556+ let patch_bitpacked = bitpack_encode ( & patch_array, 5 , None ) . unwrap ( ) ;
557+ assert ! ( patch_bitpacked. patches( ) . is_some( ) ) ;
558+
559+ let patch_vector = unpack_to_primitive_vector ( & patch_bitpacked) ;
560+ assert_eq ! ( patch_vector. len( ) , 7 ) ;
561+
562+ // Test all nulls edge case.
563+ let all_nulls = PrimitiveArray :: new (
564+ Buffer :: from_iter ( [ 0u32 , 0 , 0 , 0 ] ) ,
565+ Validity :: from_iter ( [ false , false , false , false ] ) ,
566+ ) ;
567+ let all_nulls_bp = bitpack_encode ( & all_nulls, 0 , None ) . unwrap ( ) ;
568+ let all_nulls_vec = unpack_to_primitive_vector ( & all_nulls_bp) ;
569+ assert_eq ! ( all_nulls_vec. len( ) , 4 ) ;
570+ }
571+
572+ /// Test that the execute method produces consistent results with other unpacking methods.
573+ #[ test]
574+ fn test_execute_method_consistency ( ) {
575+ use vortex_vector:: Vector ;
576+
577+ // Test that execute(), unpack_to_primitive_vector(), and unpack_array() all produce consistent results.
578+ let test_consistency = |array : & PrimitiveArray , bit_width : u8 | {
579+ let bitpacked = bitpack_encode ( array, bit_width, None ) . unwrap ( ) ;
580+
581+ // Method 1: Using the new unpack_to_primitive_vector.
582+ let vector_result = unpack_to_primitive_vector ( & bitpacked) ;
583+
584+ // Method 2: Using the old unpack_array.
585+ let unpacked_array = unpack_array ( & bitpacked) ;
586+
587+ // Method 3: Using the execute() method (this is what would be used in production).
588+ let executed = bitpacked. into_array ( ) . execute ( ) . unwrap ( ) ;
589+
590+ // All three should produce the same length.
591+ assert_eq ! ( vector_result. len( ) , array. len( ) , "vector length mismatch" ) ;
592+ assert_eq ! (
593+ unpacked_array. len( ) ,
594+ array. len( ) ,
595+ "unpacked array length mismatch"
596+ ) ;
597+
598+ // The executed vector should also have the correct length.
599+ match & executed {
600+ Vector :: Primitive ( pv) => {
601+ assert_eq ! ( pv. len( ) , array. len( ) , "executed vector length mismatch" ) ;
602+ }
603+ _ => panic ! ( "Expected primitive vector from execute" ) ,
604+ }
605+
606+ // Verify that the execute() method works correctly by comparing with unpack_array.
607+ // We convert unpack_array result to a vector using execute() to compare.
608+ let unpacked_executed = unpacked_array. into_array ( ) . execute ( ) . unwrap ( ) ;
609+ match ( & executed, & unpacked_executed) {
610+ ( Vector :: Primitive ( exec_pv) , Vector :: Primitive ( unpack_pv) ) => {
611+ assert_eq ! (
612+ exec_pv. len( ) ,
613+ unpack_pv. len( ) ,
614+ "execute() and unpack_array().execute() produced different lengths"
615+ ) ;
616+ // Both should produce identical vectors since they represent the same data.
617+ }
618+ _ => panic ! ( "Expected both to be primitive vectors" ) ,
619+ }
620+ } ;
621+
622+ // Test various scenarios without patches.
623+ test_consistency ( & PrimitiveArray :: from_iter ( 0u16 ..100 ) , 7 ) ;
624+ test_consistency ( & PrimitiveArray :: from_iter ( 0u32 ..1024 ) , 10 ) ;
625+
626+ // Test with values that will create patches.
627+ test_consistency ( & PrimitiveArray :: from_iter ( ( 0i16 ..2048 ) . map ( |x| x % 128 ) ) , 7 ) ;
628+
629+ // Test with an array that definitely has patches.
630+ let patch_values: Vec < u32 > = ( 0 ..100 )
631+ . map ( |i| if i % 20 == 0 { 1000 + i } else { i % 16 } )
632+ . collect ( ) ;
633+ let patch_array = PrimitiveArray :: from_iter ( patch_values) ;
634+ test_consistency ( & patch_array, 4 ) ;
635+
636+ // Test with sliced array (offset > 0).
637+ let values = PrimitiveArray :: from_iter ( 0u32 ..2048 ) ;
638+ let bitpacked = bitpack_encode ( & values, 11 , None ) . unwrap ( ) ;
639+ let sliced = bitpacked. slice ( 500 ..1500 ) ;
640+
641+ // Test all three methods on the sliced array.
642+ let sliced_bp = sliced. as_ :: < BitPackedVTable > ( ) ;
643+ let vector_result = unpack_to_primitive_vector ( sliced_bp) ;
644+ let unpacked_array = unpack_array ( sliced_bp) ;
645+ let executed = sliced. execute ( ) . unwrap ( ) ;
646+
647+ assert_eq ! (
648+ vector_result. len( ) ,
649+ 1000 ,
650+ "sliced vector length should be 1000"
651+ ) ;
652+ assert_eq ! (
653+ unpacked_array. len( ) ,
654+ 1000 ,
655+ "sliced unpacked array length should be 1000"
656+ ) ;
657+
658+ match executed {
659+ Vector :: Primitive ( pv) => {
660+ assert_eq ! (
661+ pv. len( ) ,
662+ 1000 ,
663+ "sliced executed vector length should be 1000"
664+ ) ;
665+ }
666+ _ => panic ! ( "Expected primitive vector from execute on sliced array" ) ,
667+ }
668+ }
669+
670+ /// Test edge cases for unpacking.
671+ #[ test]
672+ fn test_unpack_edge_cases ( ) {
673+ // Empty array.
674+ let empty: PrimitiveArray = PrimitiveArray :: from_iter ( Vec :: < u64 > :: new ( ) ) ;
675+ let empty_bp = bitpack_encode ( & empty, 0 , None ) . unwrap ( ) ;
676+ let empty_vec = unpack_to_primitive_vector ( & empty_bp) ;
677+ assert_eq ! ( empty_vec. len( ) , 0 ) ;
678+
679+ // All zeros (bit_width = 0).
680+ let zeros = PrimitiveArray :: from_iter ( [ 0u32 ; 100 ] ) ;
681+ let zeros_bp = bitpack_encode ( & zeros, 0 , None ) . unwrap ( ) ;
682+ let zeros_vec = unpack_to_primitive_vector ( & zeros_bp) ;
683+ assert_eq ! ( zeros_vec. len( ) , 100 ) ;
684+ // Verify consistency with unpack_array.
685+ let zeros_array = unpack_array ( & zeros_bp) ;
686+ assert_eq ! ( zeros_vec. len( ) , zeros_array. len( ) ) ;
687+
688+ // Maximum bit width for u16 (15 bits, since bitpacking requires bit_width < type bit width).
689+ let max_values = PrimitiveArray :: from_iter ( [ 32767u16 ; 50 ] ) ; // 2^15 - 1
690+ let max_bp = bitpack_encode ( & max_values, 15 , None ) . unwrap ( ) ;
691+ let max_vec = unpack_to_primitive_vector ( & max_bp) ;
692+ assert_eq ! ( max_vec. len( ) , 50 ) ;
693+
694+ // Exactly 3072 elements with patches across chunks.
695+ let boundary_values: Vec < u32 > = ( 0 ..3072 )
696+ . map ( |i| {
697+ if i == 1023 || i == 1024 || i == 2047 || i == 2048 {
698+ 50000 // Force patches at chunk boundaries
699+ } else {
700+ ( i % 128 ) as u32
701+ }
702+ } )
703+ . collect ( ) ;
704+ let boundary_array = PrimitiveArray :: from_iter ( boundary_values) ;
705+ let boundary_bp = bitpack_encode ( & boundary_array, 7 , None ) . unwrap ( ) ;
706+ assert ! ( boundary_bp. patches( ) . is_some( ) ) ;
707+
708+ let boundary_vec = unpack_to_primitive_vector ( & boundary_bp) ;
709+ assert_eq ! ( boundary_vec. len( ) , 3072 ) ;
710+ // Verify consistency.
711+ let boundary_unpacked = unpack_array ( & boundary_bp) ;
712+ assert_eq ! ( boundary_vec. len( ) , boundary_unpacked. len( ) ) ;
713+
714+ // Single element.
715+ let single = PrimitiveArray :: from_iter ( [ 42u8 ] ) ;
716+ let single_bp = bitpack_encode ( & single, 6 , None ) . unwrap ( ) ;
717+ let single_vec = unpack_to_primitive_vector ( & single_bp) ;
718+ assert_eq ! ( single_vec. len( ) , 1 ) ;
719+ }
455720}
0 commit comments