Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/array/fill_null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub fn fill_null_canonical_array(
}
Canonical::Struct(_)
| Canonical::List(_)
| Canonical::Map(_)
| Canonical::FixedSizeList(_)
| Canonical::Extension(_) => canonical.into_array().fill_null(fill_value.clone())?,
Canonical::Union(_) => {
Expand Down
1 change: 1 addition & 0 deletions fuzz/src/array/mask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub fn mask_canonical_array(
Canonical::Union(_) => {
todo!("TODO(connor)[Union]: support Union arrays in the mask fuzzer")
}
Canonical::Map(_) => unreachable!("Map arrays are not fuzzed"),
Canonical::Variant(_) => unreachable!("Variant arrays are not fuzzed"),
})
}
Expand Down
1 change: 1 addition & 0 deletions fuzz/src/array/scalar_at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub fn scalar_at_canonical_array(
Canonical::Union(_) => {
todo!("TODO(connor)[Union]: support Union arrays in the scalar_at fuzzer")
}
Canonical::Map(_) => unreachable!("Map arrays are not fuzzed"),
Canonical::Variant(_) => unreachable!("Variant arrays are not fuzzed"),
})
}
3 changes: 3 additions & 0 deletions vortex-array/src/aggregate_fn/fns/is_constant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,9 @@ impl AggregateFnVTable for IsConstant {
Canonical::Struct(s) => check_struct_constant(s, ctx)?,
Canonical::Extension(e) => check_extension_constant(e, ctx)?,
Canonical::List(l) => check_listview_constant(l, ctx)?,
Canonical::Map(_) => {
vortex_bail!("Map arrays don't support IsConstant")
}
Canonical::FixedSizeList(f) => check_fixed_size_list_constant(f, ctx)?,
Canonical::Null(_) => true,
Canonical::Union(_) => {
Expand Down
1 change: 1 addition & 0 deletions vortex-array/src/aggregate_fn/fns/min_max/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ impl AggregateFnVTable for MinMax {
}
Canonical::Struct(_)
| Canonical::List(_)
| Canonical::Map(_)
| Canonical::FixedSizeList(_)
| Canonical::Variant(_) => {
vortex_bail!("Unsupported canonical type for min_max: {}", batch.dtype())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use crate::aggregate_fn::EmptyOptions;
use crate::array::ArrayView;
use crate::arrays::Constant;
use crate::arrays::ConstantArray;
use crate::arrays::map::MapArrayExt;
use crate::arrays::varbinview::BinaryView;
use crate::dtype::DType;
use crate::dtype::DecimalType;
Expand Down Expand Up @@ -199,6 +200,9 @@ pub(crate) fn canonical_uncompressed_size_in_bytes(
Canonical::Decimal(array) => decimal_uncompressed_size_in_bytes(array, ctx),
Canonical::VarBinView(array) => varbinview_uncompressed_size_in_bytes(array, ctx),
Canonical::List(array) => list_view_uncompressed_size_in_bytes(array, ctx),
Canonical::Map(array) => {
list_view_uncompressed_size_in_bytes(&array.entries().into_owned(), ctx)
}
Canonical::FixedSizeList(array) => fixed_size_list_uncompressed_size_in_bytes(array, ctx),
Canonical::Struct(array) => struct_uncompressed_size_in_bytes(array, ctx),
Canonical::Union(array) => union_uncompressed_size_in_bytes(array, ctx),
Expand Down Expand Up @@ -232,13 +236,14 @@ pub(crate) fn constant_uncompressed_size_in_bytes(
array.len(),
array.scalar().as_binary().value().map(|value| value.len()),
)?,
DType::List(..) | DType::FixedSizeList(..) | DType::Struct(..) | DType::Extension(_) => {
DType::List(..)
| DType::Map(..)
| DType::FixedSizeList(..)
| DType::Struct(..)
| DType::Extension(_) => {
let canonical = array.array().clone().execute::<Canonical>(ctx)?;
return canonical_uncompressed_size_in_bytes(&canonical, ctx);
}
DType::Map(..) => {
vortex_bail!("UncompressedSizeInBytes is not supported for map arrays yet")
}
DType::Union(..) => {
todo!(
"TODO(connor)[Union]: support constant Union size accounting after constant Union \
Expand Down Expand Up @@ -297,7 +302,10 @@ fn supports_uncompressed_size_in_bytes(dtype: &DType) -> bool {
DType::List(element_dtype, _) | DType::FixedSizeList(element_dtype, ..) => {
supports_uncompressed_size_in_bytes(element_dtype)
}
DType::Map(..) => false,
DType::Map(map_dtype, _) => {
supports_uncompressed_size_in_bytes(&map_dtype.key_dtype())
&& supports_uncompressed_size_in_bytes(&map_dtype.value_dtype())
}
DType::Struct(fields, _) => fields
.fields()
.all(|field| supports_uncompressed_size_in_bytes(&field)),
Expand Down
12 changes: 11 additions & 1 deletion vortex-array/src/arrays/constant/vtable/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::arrays::DecimalArray;
use crate::arrays::ExtensionArray;
use crate::arrays::FixedSizeListArray;
use crate::arrays::ListViewArray;
use crate::arrays::MapArray;
use crate::arrays::NullArray;
use crate::arrays::PrimitiveArray;
use crate::arrays::StructArray;
Expand Down Expand Up @@ -126,7 +127,16 @@ pub(crate) fn constant_canonicalize(
))
}
DType::List(..) => Canonical::List(constant_canonical_list_array(scalar, array.len())),
DType::Map(..) => vortex_error::vortex_bail!("canonical map arrays are not yet supported"),
DType::Map(map_dtype, nullability) => {
let entries_scalar = Scalar::try_new(
DType::List(Arc::new(map_dtype.entries_dtype()), *nullability),
scalar.value().cloned(),
)?;
Canonical::Map(MapArray::try_new(
map_dtype.clone(),
constant_canonical_list_array(&entries_scalar, array.len()),
)?)
}
DType::FixedSizeList(element_dtype, list_size, _) => {
let value = scalar.as_list();

Expand Down
1 change: 1 addition & 0 deletions vortex-array/src/arrays/dict/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub(crate) fn take_canonical(
Canonical::Decimal(a) => Canonical::Decimal(take_decimal(&a, codes, ctx)),
Canonical::VarBinView(a) => Canonical::VarBinView(take_varbinview(&a, codes, ctx)),
Canonical::List(a) => Canonical::List(take_listview(&a, codes, ctx)),
Canonical::Map(_) => vortex_error::vortex_bail!("Map arrays don't support take"),
Canonical::FixedSizeList(a) => {
Canonical::FixedSizeList(take_fixed_size_list(&a, codes, ctx))
}
Expand Down
2 changes: 2 additions & 0 deletions vortex-array/src/arrays/filter/execute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::sync::Arc;

use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_panic;
use vortex_mask::Mask;
use vortex_mask::MaskValues;

Expand Down Expand Up @@ -95,6 +96,7 @@ pub(super) fn execute_filter(canonical: Canonical, mask: &Arc<MaskValues>) -> Ca
Canonical::Decimal(a) => Canonical::Decimal(decimal::filter_decimal(&a, mask)),
Canonical::VarBinView(a) => Canonical::VarBinView(varbinview::filter_varbinview(&a, mask)),
Canonical::List(a) => Canonical::List(listview::filter_listview(&a, mask)),
Canonical::Map(_) => vortex_panic!("Map arrays don't support filter"),
Canonical::FixedSizeList(a) => {
Canonical::FixedSizeList(fixed_size_list::filter_fixed_size_list(&a, mask))
}
Expand Down
190 changes: 190 additions & 0 deletions vortex-array/src/arrays/map/array.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hasher;
use std::sync::Arc;

use smallvec::smallvec;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_ensure;

use crate::ArrayEq;
use crate::ArrayHash;
use crate::ArrayRef;
use crate::EqMode;
use crate::IntoArray;
use crate::array::Array;
use crate::array::ArrayParts;
use crate::array::ArrayView;
use crate::array::TypedArrayRef;
use crate::arrays::ListView;
use crate::arrays::ListViewArray;
use crate::arrays::listview::ListViewArrayExt;
use crate::arrays::map::Map;
use crate::dtype::DType;
use crate::dtype::MapDType;
use crate::validity::Validity;

/// The one child slot holding a [`ListViewArray`] of map entries.
pub(super) const ENTRIES_SLOT: usize = 0;
pub(super) const NUM_SLOTS: usize = 1;
pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = ["entries"];

/// Encoding-specific metadata for [`MapArray`].
///
/// All map metadata is represented by the outer [`DType::Map`] and the entries child, so this
/// value is intentionally empty.
#[derive(Clone, Debug, Default)]
pub struct MapData;

impl Display for MapData {
fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}

impl ArrayEq for MapData {
fn array_eq(&self, _other: &Self, _accuracy: EqMode) -> bool {
true
}
}

impl ArrayHash for MapData {
fn array_hash<H: Hasher>(&self, _state: &mut H, _accuracy: EqMode) {}
}

/// The logical and physical inputs used to construct a [`MapArray`].
pub struct MapDataParts {
/// The key/value type and sortedness assertion for the map.
pub map_dtype: MapDType,
/// The physical list-view storage of `{key, value}` entry structs.
pub entries: ListViewArray,
}

/// Accessors for the canonical map representation.
pub trait MapArrayExt: TypedArrayRef<Map> {
/// Returns the list-view storage of map entry structs.
fn entries(&self) -> ArrayView<'_, ListView> {
self.as_ref().slots()[ENTRIES_SLOT]
.as_ref()
.vortex_expect("MapArray entries slot")
.as_::<ListView>()
}

/// Returns the entry structs for one map row.
fn entries_at(&self, index: usize) -> VortexResult<ArrayRef> {
self.entries().list_elements_at(index)
}

/// Returns the number of entries in one map row.
fn entry_count_at(&self, index: usize) -> usize {
self.entries().size_at(index)
}

/// Returns the outer map validity delegated from the entries list-view.
fn map_validity(&self) -> Validity {
self.entries().listview_validity()
}

/// Returns this map's key/value type information.
fn map_dtype(&self) -> &MapDType {
self.as_ref()
.dtype()
.as_map_opt()
.vortex_expect("MapArray requires a map dtype")
}

/// Returns whether producers assert sorted keys within each map value.
fn keys_sorted(&self) -> bool {
self.map_dtype().keys_sorted()
}
}
impl<T: TypedArrayRef<Map>> MapArrayExt for T {}

impl Array<Map> {
/// Creates a canonical map array from its map dtype and list-view entry storage.
///
/// # Panics
///
/// Panics if `entries` is not a list of the map dtype's non-nullable `{key, value}` entry
/// struct with matching outer nullability.
pub fn new(map_dtype: MapDType, entries: ListViewArray) -> Self {
Self::try_new(map_dtype, entries).vortex_expect("MapArray construction failed")
}

/// Constructs a canonical map array from its map dtype and list-view entry storage.
///
/// # Errors
///
/// Returns an error when the entry child is not `ListView<Struct<key, value>>`, has a
/// different outer nullability, or has a different length than the outer map array.
pub fn try_new(map_dtype: MapDType, entries: ListViewArray) -> VortexResult<Self> {
let nullability = entries.nullability();
let dtype = DType::Map(map_dtype, nullability);
let len = entries.len();
let parts = ArrayParts::new(Map, dtype, len, MapData)
.with_slots(smallvec![Some(entries.into_array())]);
Self::try_from_parts(parts)
}

/// Creates a canonical map array without validating its entry storage.
///
/// # Safety
///
/// The caller must ensure that `entries` has dtype
/// `List(Struct { key, value }, entries.nullability())`, where the struct exactly matches
/// `map_dtype.entries_dtype()`.
pub unsafe fn new_unchecked(map_dtype: MapDType, entries: ListViewArray) -> Self {
let nullability = entries.nullability();
let dtype = DType::Map(map_dtype, nullability);
let len = entries.len();
let parts = ArrayParts::new(Map, dtype, len, MapData)
.with_slots(smallvec![Some(entries.into_array())]);
unsafe { Self::from_parts_unchecked(parts) }
}

/// Decomposes this map array into its logical dtype and physical entries child.
pub fn into_data_parts(self) -> MapDataParts {
let map_dtype = self
.dtype()
.as_map_opt()
.vortex_expect("MapArray requires a map dtype")
.clone();
let entries = self.entries().into_owned();
MapDataParts { map_dtype, entries }
}
}

fn expected_entries_dtype(map_dtype: &MapDType, nullability: crate::dtype::Nullability) -> DType {
DType::List(Arc::new(map_dtype.entries_dtype()), nullability)
}

pub(super) fn validate_entries(
map_dtype: &MapDType,
nullability: crate::dtype::Nullability,
len: usize,
entries: &ArrayRef,
) -> VortexResult<()> {
vortex_ensure!(
entries.is::<ListView>(),
"MapArray entries must use vortex.listview encoding, got {}",
entries.encoding_id()
);
vortex_ensure!(
entries.len() == len,
"MapArray entries length {} does not match outer length {len}",
entries.len()
);

let expected_dtype = expected_entries_dtype(map_dtype, nullability);
vortex_ensure!(
entries.dtype() == &expected_dtype,
"MapArray entries dtype {} does not match expected {expected_dtype}",
entries.dtype()
);

Ok(())
}
16 changes: 16 additions & 0 deletions vortex-array/src/arrays/map/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

//! Canonical map arrays backed by [`ListView`](crate::arrays::ListView) entry storage.

mod array;
pub use array::MapArrayExt;
pub use array::MapData;
pub use array::MapDataParts;

mod vtable;
pub use vtable::Map;
pub use vtable::MapArray;

#[cfg(test)]
mod tests;
Loading