Repository: jorgecarleitao/arrow2
Pull request #246 (2021-08-03 02:30:45)

Sort improve
Related #245 

Changes:

- Add `to_vec` function to `MergeSortSlices`, this makes iterator reuseable.
- Add `buffer_from_range ` to Index trait
- Fix some bad codes in benches.
- Change `cmp(a,b).reverse()` to `cmp(b,a)`

sundy-li: Seems `#![feature(step_trait)]` is not acceptable. 

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 55eeab15e4..74ac46afd3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,7 +58,7 @@ futures = { version = "0.3", optional = true }
 # for faster hashing
 ahash = { version = "0.7", optional = true }
 
-parquet2 = { version = "0.1", optional = true }
+parquet2 = { version = "0.1", optional = true, default_features = false, features = ["stream"] }
 
 [dev-dependencies]
 rand = "0.8"
@@ -68,12 +68,33 @@ doc-comment = "0.3"
 crossbeam-channel = "0.5.1"
 
 [features]
-default = ["io_csv", "io_json", "io_ipc", "io_ipc_compression", "io_json_integration", "io_print", "io_parquet", "regex", "merge_sort", "ahash", "benchmarks", "compute"]
+default = [
+    "io_csv",
+    "io_json",
+    "io_ipc",
+    "io_ipc_compression",
+    "io_json_integration",
+    "io_print",
+    "io_parquet",
+    "io_parquet_compression",
+    "regex",
+    "merge_sort",
+    "ahash",
+    "benchmarks",
+    "compute",
+]
 merge_sort = ["itertools"]
 io_csv = ["csv", "lazy_static", "regex"]
 io_json = ["serde", "serde_derive", "serde_json", "indexmap"]
 io_ipc = ["flatbuffers"]
 io_ipc_compression = ["lz4", "zstd"]
+io_parquet_compression = [
+    "parquet2/zstd",
+    "parquet2/snappy",
+    "parquet2/gzip",
+    "parquet2/lz4",
+    "parquet2/brotli",
+]
 io_json_integration = ["io_json", "hex"]
 io_print = ["prettytable-rs"]
 # the compute kernels. Disabling this significantly reduces compile time.
@@ -88,6 +109,8 @@ skip_feature_sets = [
     ["benchmarks"],
     ["merge_sort"],
     ["io_json_integration"],
+    # this does not change the public API
+    ["io_parquet_compression"],
     ["simd"],
 ]
 
diff --git a/benches/growable.rs b/benches/growable.rs
index bedb89bd5a..e0a4d2426a 100644
--- a/benches/growable.rs
+++ b/benches/growable.rs
@@ -14,7 +14,7 @@ fn add_benchmark(c: &mut Criterion) {
     let i32_array = create_primitive_array::<i32>(1026 * 10, DataType::Int32, 0.0);
     c.bench_function("growable::primitive::non_null::non_null", |b| {
         b.iter(|| {
-            let mut a = GrowablePrimitive::new(&[&i32_array], false, 1026 * 10);
+            let mut a = GrowablePrimitive::new(vec![&i32_array], false, 1026 * 10);
             values
                 .clone()
                 .into_iter()
@@ -25,7 +25,7 @@ fn add_benchmark(c: &mut Criterion) {
     let i32_array = create_primitive_array::<i32>(1026 * 10, DataType::Int32, 0.0);
     c.bench_function("growable::primitive::non_null::null", |b| {
         b.iter(|| {
-            let mut a = GrowablePrimitive::new(&[&i32_array], true, 1026 * 10);
+            let mut a = GrowablePrimitive::new(vec![&i32_array], true, 1026 * 10);
             values.clone().into_iter().for_each(|start| {
                 if start % 2 == 0 {
                     a.extend_validity(10);
@@ -41,7 +41,7 @@ fn add_benchmark(c: &mut Criterion) {
     let values = values.collect::<Vec<_>>();
     c.bench_function("growable::primitive::null::non_null", |b| {
         b.iter(|| {
-            let mut a = GrowablePrimitive::new(&[&i32_array], false, 1026 * 10);
+            let mut a = GrowablePrimitive::new(vec![&i32_array], false, 1026 * 10);
             values
                 .clone()
                 .into_iter()
@@ -50,7 +50,7 @@ fn add_benchmark(c: &mut Criterion) {
     });
     c.bench_function("growable::primitive::null::null", |b| {
         b.iter(|| {
-            let mut a = GrowablePrimitive::new(&[&i32_array], true, 1026 * 10);
+            let mut a = GrowablePrimitive::new(vec![&i32_array], true, 1026 * 10);
             values.clone().into_iter().for_each(|start| {
                 if start % 2 == 0 {
                     a.extend_validity(10);
diff --git a/benches/sort_kernel.rs b/benches/sort_kernel.rs
index 30d89c441c..e78e4462b0 100644
--- a/benches/sort_kernel.rs
+++ b/benches/sort_kernel.rs
@@ -19,7 +19,7 @@
 extern crate criterion;
 use criterion::Criterion;
 
-use arrow2::compute::sort::{lexsort, sort, SortColumn, SortOptions};
+use arrow2::compute::sort::{lexsort, sort, sort_to_indices, SortColumn, SortOptions};
 use arrow2::util::bench_util::*;
 use arrow2::{array::*, datatypes::*};
 
@@ -42,6 +42,15 @@ fn bench_sort(arr_a: &dyn Array) {
     sort(criterion::black_box(arr_a), &SortOptions::default(), None).unwrap();
 }
 
+fn bench_sort_limit(arr_a: &dyn Array) {
+    let _: PrimitiveArray<u32> = sort_to_indices(
+        criterion::black_box(arr_a),
+        &SortOptions::default(),
+        Some(100),
+    )
+    .unwrap();
+}
+
 fn add_benchmark(c: &mut Criterion) {
     (10..=20).step_by(2).for_each(|log2_size| {
         let size = 2usize.pow(log2_size);
@@ -51,6 +60,10 @@ fn add_benchmark(c: &mut Criterion) {
             b.iter(|| bench_sort(&arr_a))
         });
 
+        c.bench_function(&format!("sort-limit 2^{} f32", log2_size), |b| {
+            b.iter(|| bench_sort_limit(&arr_a))
+        });
+
         let arr_b = create_primitive_array_with_seed::<f32>(size, DataType::Float32, 0.0, 43);
         c.bench_function(&format!("lexsort 2^{} f32", log2_size), |b| {
             b.iter(|| bench_lexsort(&arr_a, &arr_b))
diff --git a/benches/take_kernels.rs b/benches/take_kernels.rs
index 2ab746b443..9303a719f3 100644
--- a/benches/take_kernels.rs
+++ b/benches/take_kernels.rs
@@ -35,7 +35,7 @@ fn create_random_index(size: usize, null_density: f32) -> PrimitiveArray<i32> {
     (0..size)
         .map(|_| {
             if rng.gen::<f32>() > null_density {
-                let value = rng.gen_range::<i32, _, _>(0i32, size as i32);
+                let value = rng.gen_range::<i32, _>(0i32..size as i32);
                 Some(value)
             } else {
                 None
diff --git a/src/array/specification.rs b/src/array/specification.rs
index 70576cb862..0684d9a6f5 100644
--- a/src/array/specification.rs
+++ b/src/array/specification.rs
@@ -3,7 +3,7 @@ use std::convert::TryFrom;
 use num::Num;
 
 use crate::{
-    buffer::Buffer,
+    buffer::{Buffer, MutableBuffer},
     types::{NativeType, NaturalDataType},
 };
 
@@ -11,6 +11,11 @@ use crate::{
 pub trait Index: NativeType + NaturalDataType {
     fn to_usize(&self) -> usize;
     fn from_usize(index: usize) -> Option<Self>;
+    fn is_usize() -> bool {
+        false
+    }
+
+    fn buffer_from_range(start: usize, end: usize) -> Option<MutableBuffer<Self>>;
 }
 
 /// Trait describing types that can be used as offsets as per Arrow specification.
@@ -71,6 +76,19 @@ impl Index for i32 {
     fn from_usize(value: usize) -> Option<Self> {
         Self::try_from(value).ok()
     }
+
+    fn buffer_from_range(start: usize, end: usize) -> Option<MutableBuffer<Self>> {
+        let start = Self::from_usize(start);
+        let end = Self::from_usize(end);
+        match (start, end) {
+            (Some(start), Some(end)) => unsafe {
+                Some(MutableBuffer::<Self>::from_trusted_len_iter_unchecked(
+                    start..end,
+                ))
+            },
+            _ => None,
+        }
+    }
 }
 
 impl Index for i64 {
@@ -83,6 +101,19 @@ impl Index for i64 {
     fn from_usize(value: usize) -> Option<Self> {
         Self::try_from(value).ok()
     }
+
+    fn buffer_from_range(start: usize, end: usize) -> Option<MutableBuffer<Self>> {
+        let start = Self::from_usize(start);
+        let end = Self::from_usize(end);
+        match (start, end) {
+            (Some(start), Some(end)) => unsafe {
+                Some(MutableBuffer::<Self>::from_trusted_len_iter_unchecked(
+                    start..end,
+                ))
+            },
+            _ => None,
+        }
+    }
 }
 
 impl Index for u32 {
@@ -95,6 +126,23 @@ impl Index for u32 {
     fn from_usize(value: usize) -> Option<Self> {
         Self::try_from(value).ok()
     }
+
+    fn is_usize() -> bool {
+        std::mem::size_of::<Self>() == std::mem::size_of::<usize>()
+    }
+
+    fn buffer_from_range(start: usize, end: usize) -> Option<MutableBuffer<Self>> {
+        let start = Self::from_usize(start);
+        let end = Self::from_usize(end);
+        match (start, end) {
+            (Some(start), Some(end)) => unsafe {
+                Some(MutableBuffer::<Self>::from_trusted_len_iter_unchecked(
+                    start..end,
+                ))
+            },
+            _ => None,
+        }
+    }
 }
 
 impl Index for u64 {
@@ -107,6 +155,23 @@ impl Index for u64 {
     fn from_usize(value: usize) -> Option<Self> {
         Self::try_from(value).ok()
     }
+
+    fn is_usize() -> bool {
+        std::mem::size_of::<Self>() == std::mem::size_of::<usize>()
+    }
+
+    fn buffer_from_range(start: usize, end: usize) -> Option<MutableBuffer<Self>> {
+        let start = Self::from_usize(start);
+        let end = Self::from_usize(end);
+        match (start, end) {
+            (Some(start), Some(end)) => unsafe {
+                Some(MutableBuffer::<Self>::from_trusted_len_iter_unchecked(
+                    start..end,
+                ))
+            },
+            _ => None,
+        }
+    }
 }
 
 #[inline]
diff --git a/src/bitmap/immutable.rs b/src/bitmap/immutable.rs
index 5630e8b4bd..0b1ab34ea4 100644
--- a/src/bitmap/immutable.rs
+++ b/src/bitmap/immutable.rs
@@ -216,7 +216,7 @@ impl Bitmap {
     #[inline]
     pub(crate) fn as_slice(&self) -> &[u8] {
         let start = self.offset / 8;
-        let len = self.length.saturating_add(7) / 8;
+        let len = (self.offset() + self.length).saturating_add(7) / 8;
         &self.bytes[start..start + len]
     }
 }
@@ -261,4 +261,15 @@ mod tests {
 
         assert_eq!(0, b.offset());
     }
+
+    #[test]
+    fn as_slice_offset_middle() {
+        let b = Bitmap::from_u8_slice(&[0, 0, 0, 0b00010101], 27);
+        let b = b.slice(22, 5);
+
+        let slice = b.as_slice();
+        assert_eq!(slice, &[0, 0b00010101]);
+
+        assert_eq!(6, b.offset());
+    }
 }
diff --git a/src/bitmap/utils/slice_iterator.rs b/src/bitmap/utils/slice_iterator.rs
index 2dfbe111ee..f2633a1615 100644
--- a/src/bitmap/utils/slice_iterator.rs
+++ b/src/bitmap/utils/slice_iterator.rs
@@ -284,4 +284,13 @@ mod tests {
         // the first "11" in the second byte
         assert_eq!(chunks, vec![(0, 2)]);
     }
+
+    #[test]
+    fn remainder_1() {
+        let values = Bitmap::from_u8_slice(&[0, 0, 0b00000000, 0b00010101], 27);
+        let values = values.slice(22, 5);
+        let iter = SlicesIterator::new(&values);
+        let chunks = iter.collect::<Vec<_>>();
+        assert_eq!(chunks, vec![(2, 1), (4, 1)]);
+    }
 }
diff --git a/src/buffer/mutable.rs b/src/buffer/mutable.rs
index 7cf5cdae39..e811901f21 100644
--- a/src/buffer/mutable.rs
+++ b/src/buffer/mutable.rs
@@ -93,6 +93,15 @@ impl<T: NativeType> MutableBuffer<T> {
         }
     }
 
+    /// Allocates a new [MutableBuffer] with `len` and capacity to be at least `len` where
+    /// all bytes are not initialized
+    #[inline]
+    pub unsafe fn from_len(len: usize) -> Self {
+        let mut buffer = MutableBuffer::with_capacity(len);
+        buffer.set_len(len);
+        buffer
+    }
+
     /// Ensures that this buffer has at least `self.len + additional` bytes. This re-allocates iff
     /// `self.len + additional > capacity`.
     /// # Example
diff --git a/src/compute/merge_sort/mod.rs b/src/compute/merge_sort/mod.rs
index e3c2d4fedd..3f4736a24a 100644
--- a/src/compute/merge_sort/mod.rs
+++ b/src/compute/merge_sort/mod.rs
@@ -291,6 +291,29 @@ where
             None => self.right = None,
         }
     }
+
+    /// Collect the MergeSortSlices to be a vec for reusing
+    #[warn(dead_code)]
+    pub fn to_vec(self, limit: Option<usize>) -> Vec<MergeSlice> {
+        match limit {
+            Some(limit) => {
+                let mut v = Vec::with_capacity(limit);
+                let mut current_len = 0;
+                for (index, start, len) in self {
+                    if len + current_len >= limit {
+                        v.push((index, start, limit - current_len));
+                        break;
+                    } else {
+                        v.push((index, start, len));
+                    }
+                    current_len += len;
+                }
+
+                v
+            }
+            None => self.into_iter().collect(),
+        }
+    }
 }
 
 impl<'a, L, R> Iterator for MergeSortSlices<'a, L, R>
@@ -562,6 +585,22 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn test_merge_slices_to_vec() -> Result<()> {
+        let a0: &dyn Array = &Int32Array::from_slice(&[0, 2, 4, 6, 8]);
+        let a1: &dyn Array = &Int32Array::from_slice(&[1, 3, 5, 7, 9]);
+
+        let options = SortOptions::default();
+        let arrays = vec![a0, a1];
+        let pairs = vec![(arrays.as_ref(), &options)];
+        let comparator = build_comparator(&pairs)?;
+
+        let slices = merge_sort_slices(once(&(0, 0, 5)), once(&(1, 0, 5)), &comparator);
+        let vec = slices.to_vec(Some(5));
+        assert_eq!(vec, [(0, 0, 1), (1, 0, 1), (0, 1, 1), (1, 1, 1), (0, 2, 1)]);
+        Ok(())
+    }
+
     #[test]
     fn test_merge_4_i32() -> Result<()> {
         let a0: &dyn Array = &Int32Array::from_slice(&[0, 1]);
diff --git a/src/compute/sort/boolean.rs b/src/compute/sort/boolean.rs
index 2e7082a590..6afdd551ec 100644
--- a/src/compute/sort/boolean.rs
+++ b/src/compute/sort/boolean.rs
@@ -26,7 +26,7 @@ pub fn sort_boolean<I: Index>(
     if !descending {
         valids.sort_by(|a, b| a.1.cmp(&b.1));
     } else {
-        valids.sort_by(|a, b| a.1.cmp(&b.1).reverse());
+        valids.sort_by(|a, b| b.1.cmp(&a.1));
         // reverse to keep a stable ordering
         nulls.reverse();
     }
@@ -45,6 +45,7 @@ pub fn sort_boolean<I: Index>(
     // un-efficient; there are much more performant ways of sorting nulls above, anyways.
     if let Some(limit) = limit {
         values.truncate(limit);
+        values.shrink_to_fit();
     }
 
     PrimitiveArray::<I>::from_data(I::DATA_TYPE, values.into(), None)
diff --git a/src/compute/sort/common.rs b/src/compute/sort/common.rs
index c36763831b..7e909e01cd 100644
--- a/src/compute/sort/common.rs
+++ b/src/compute/sort/common.rs
@@ -22,31 +22,21 @@ fn k_element_sort_inner<I: Index, T, G, F>(
     F: FnMut(&T, &T) -> std::cmp::Ordering,
 {
     if descending {
-        let compare = |lhs: &I, rhs: &I| {
+        let mut compare = |lhs: &I, rhs: &I| {
             let lhs = get(lhs.to_usize());
             let rhs = get(rhs.to_usize());
-            cmp(&lhs, &rhs).reverse()
+            cmp(&rhs, &lhs)
         };
-        let (before, _, _) = indices.select_nth_unstable_by(limit, compare);
-        let compare = |lhs: &I, rhs: &I| {
-            let lhs = get(lhs.to_usize());
-            let rhs = get(rhs.to_usize());
-            cmp(&lhs, &rhs).reverse()
-        };
-        before.sort_unstable_by(compare);
+        let (before, _, _) = indices.select_nth_unstable_by(limit, &mut compare);
+        before.sort_unstable_by(&mut compare);
     } else {
-        let compare = |lhs: &I, rhs: &I| {
-            let lhs = get(lhs.to_usize());
-            let rhs = get(rhs.to_usize());
-            cmp(&lhs, &rhs)
-        };
-        let (before, _, _) = indices.select_nth_unstable_by(limit, compare);
-        let compare = |lhs: &I, rhs: &I| {
+        let mut compare = |lhs: &I, rhs: &I| {
             let lhs = get(lhs.to_usize());
             let rhs = get(rhs.to_usize());
             cmp(&lhs, &rhs)
         };
-        before.sort_unstable_by(compare);
+        let (before, _, _) = indices.select_nth_unstable_by(limit, &mut compare);
+        before.sort_unstable_by(&mut compare);
     }
 }
 
@@ -74,7 +64,7 @@ fn sort_unstable_by<I, T, G, F>(
         indices.sort_unstable_by(|lhs, rhs| {
             let lhs = get(lhs.to_usize());
             let rhs = get(rhs.to_usize());
-            cmp(&lhs, &rhs).reverse()
+            cmp(&rhs, &lhs)
         })
     } else {
         indices.sort_unstable_by(|lhs, rhs| {
@@ -110,8 +100,7 @@ where
     let limit = limit.min(length);
 
     let indices = if let Some(validity) = validity {
-        let mut indices = MutableBuffer::<I>::from_len_zeroed(length);
-
+        let mut indices = unsafe { MutableBuffer::<I>::from_len(length) };
         if options.nulls_first {
             let mut nulls = 0;
             let mut valids = 0;
@@ -134,12 +123,12 @@ where
                 // Soundness:
                 // all indices in `indices` are by construction `< array.len() == values.len()`
                 // limit is by construction < indices.len()
-                let limit = limit - validity.null_count();
+                let limit = limit.saturating_sub(validity.null_count());
                 let indices = &mut indices.as_mut_slice()[validity.null_count()..];
                 sort_unstable_by(indices, get, cmp, options.descending, limit)
             }
         } else {
-            let last_valid_index = length - validity.null_count();
+            let last_valid_index = length.saturating_sub(validity.null_count());
             let mut nulls = 0;
             let mut valids = 0;
             validity.iter().zip(0..length).for_each(|(x, index)| {
@@ -165,21 +154,15 @@ where
 
         indices
     } else {
-        let mut indices = unsafe {
-            MutableBuffer::from_trusted_len_iter_unchecked(
-                (0..length).map(|x| I::from_usize(x).unwrap()),
-            )
-        };
-
+        let mut indices = Index::buffer_from_range(0, length).unwrap();
         // Soundness:
         // indices are by construction `< values.len()`
         // limit is by construction `< values.len()`
         sort_unstable_by(&mut indices, get, cmp, descending, limit);
-
         indices.truncate(limit);
         indices.shrink_to_fit();
-
         indices
     };
+
     PrimitiveArray::<I>::from_data(I::DATA_TYPE, indices.into(), None)
 }
diff --git a/src/compute/sort/lex_sort.rs b/src/compute/sort/lex_sort.rs
index 0a596bfd06..3a15a10b67 100644
--- a/src/compute/sort/lex_sort.rs
+++ b/src/compute/sort/lex_sort.rs
@@ -177,6 +177,7 @@ pub fn lexsort_to_indices<I: Index>(
         let (before, _, _) = values.select_nth_unstable_by(limit, lex_comparator);
         before.sort_unstable_by(lex_comparator);
         values.truncate(limit);
+        values.shrink_to_fit();
     } else {
         values.sort_unstable_by(lex_comparator);
     }
diff --git a/src/compute/sort/mod.rs b/src/compute/sort/mod.rs
index 32cf63166c..781263f45d 100644
--- a/src/compute/sort/mod.rs
+++ b/src/compute/sort/mod.rs
@@ -387,7 +387,7 @@ where
     if !options.descending {
         valids.sort_by(|a, b| cmp_array(a.1.as_ref(), b.1.as_ref()))
     } else {
-        valids.sort_by(|a, b| cmp_array(a.1.as_ref(), b.1.as_ref()).reverse())
+        valids.sort_by(|a, b| cmp_array(b.1.as_ref(), a.1.as_ref()))
     }
 
     let values = valids.iter().map(|tuple| tuple.0);
diff --git a/src/compute/sort/primitive/indices.rs b/src/compute/sort/primitive/indices.rs
index e7c79947b4..d3d7f76bd3 100644
--- a/src/compute/sort/primitive/indices.rs
+++ b/src/compute/sort/primitive/indices.rs
@@ -18,10 +18,11 @@ where
     T: NativeType,
     F: Fn(&T, &T) -> std::cmp::Ordering,
 {
+    let values = array.values().as_slice();
     unsafe {
         common::indices_sorted_unstable_by(
             array.validity(),
-            |x: usize| *array.values().as_slice().get_unchecked(x),
+            |x: usize| *values.get_unchecked(x),
             cmp,
             array.len(),
             options,
diff --git a/src/compute/sort/primitive/sort.rs b/src/compute/sort/primitive/sort.rs
index 197ea29941..de6aa58867 100644
--- a/src/compute/sort/primitive/sort.rs
+++ b/src/compute/sort/primitive/sort.rs
@@ -34,7 +34,7 @@ where
     F: FnMut(&T, &T) -> std::cmp::Ordering,
 {
     if descending {
-        let (before, _, _) = values.select_nth_unstable_by(limit, |x, y| cmp(x, y).reverse());
+        let (before, _, _) = values.select_nth_unstable_by(limit, |x, y| cmp(y, x));
         before.sort_unstable_by(|x, y| cmp(x, y));
     } else {
         let (before, _, _) = values.select_nth_unstable_by(limit, |x, y| cmp(x, y));
@@ -52,7 +52,7 @@ where
     }
 
     if descending {
-        values.sort_unstable_by(|x, y| cmp(x, y).reverse());
+        values.sort_unstable_by(|x, y| cmp(y, x));
     } else {
         values.sort_unstable_by(cmp);
     };
@@ -125,6 +125,7 @@ where
     };
     // values are sorted, we can now truncate the remaining.
     buffer.truncate(limit);
+    buffer.shrink_to_fit();
 
     (buffer.into(), new_validity.into())
 }
@@ -154,6 +155,7 @@ where
 
         sort_values(&mut buffer.as_mut_slice(), cmp, options.descending, limit);
         buffer.truncate(limit);
+        buffer.shrink_to_fit();
 
         (buffer.into(), None)
     };
diff --git a/src/compute/sort/utf8.rs b/src/compute/sort/utf8.rs
index 20f2d65fea..17a75aef4e 100644
--- a/src/compute/sort/utf8.rs
+++ b/src/compute/sort/utf8.rs
@@ -32,6 +32,7 @@ pub(super) fn indices_sorted_unstable_by_dictionary<I: Index, K: DictionaryKey,
         // Note: there is no check that the keys are within bounds of the dictionary.
         dict.value(index.to_usize().unwrap())
     };
+
     let cmp = |lhs: &&str, rhs: &&str| lhs.cmp(rhs);
     common::indices_sorted_unstable_by(array.validity(), get, cmp, array.len(), options, limit)
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fbef76bacc..4655dd4afc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -271,7 +271,7 @@ jobs:
           export CARGO_HOME="/github/home/.cargo"
           export CARGO_TARGET_DIR="/github/home/target"
           # no need 
-          cargo build --no-default-features --features=merge_sort,io_ipc,io_csv,io_json --target wasm32-unknown-unknown
+          cargo build --no-default-features --features=merge_sort,io_ipc,io_csv,io_json,io_parquet --target wasm32-unknown-unknown
 
   linux-simd-test:
     name: SIMD