|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright the Vortex contributors |
| 3 | + |
| 4 | +use rstest::rstest; |
| 5 | +use vortex_buffer::ByteBuffer; |
| 6 | +use vortex_buffer::ByteBufferMut; |
| 7 | +use vortex_buffer::buffer; |
| 8 | +use vortex_error::VortexExpect; |
| 9 | +use vortex_error::VortexResult; |
| 10 | +use vortex_session::VortexSession; |
| 11 | +use vortex_session::registry::ReadContext; |
| 12 | + |
| 13 | +use super::SESSION; |
| 14 | +use crate::ArrayContext; |
| 15 | +use crate::ArrayId; |
| 16 | +use crate::ArrayRef; |
| 17 | +use crate::ArrayVTable; |
| 18 | +use crate::IntoArray; |
| 19 | +use crate::VortexSessionExecute; |
| 20 | +use crate::arrays::Bool; |
| 21 | +use crate::arrays::ChunkedArray; |
| 22 | +use crate::arrays::Primitive; |
| 23 | +use crate::arrays::PrimitiveArray; |
| 24 | +use crate::arrays::StructArray; |
| 25 | +use crate::arrays::Widen; |
| 26 | +use crate::arrays::WidenArray; |
| 27 | +use crate::arrays::widen::WidenPlugin; |
| 28 | +use crate::assert_arrays_eq; |
| 29 | +use crate::dtype::DType; |
| 30 | +use crate::dtype::Nullability; |
| 31 | +use crate::dtype::PType; |
| 32 | +use crate::expr::stats::Precision; |
| 33 | +use crate::expr::stats::Stat; |
| 34 | +use crate::expr::stats::StatsProvider; |
| 35 | +use crate::scalar::Scalar; |
| 36 | +use crate::serde::SerializeOptions; |
| 37 | +use crate::serde::SerializedArray; |
| 38 | +use crate::session::ArraySession; |
| 39 | +use crate::session::ArraySessionExt; |
| 40 | +use crate::validity::Validity; |
| 41 | + |
| 42 | +fn serialize_blob( |
| 43 | + array: &ArrayRef, |
| 44 | + ctx: &ArrayContext, |
| 45 | + session: &VortexSession, |
| 46 | +) -> VortexResult<ByteBuffer> { |
| 47 | + let mut blob = ByteBufferMut::empty(); |
| 48 | + for buffer in array.serialize(ctx, session, &SerializeOptions::default())? { |
| 49 | + blob.extend_from_slice(buffer.as_ref()); |
| 50 | + } |
| 51 | + Ok(blob.freeze()) |
| 52 | +} |
| 53 | + |
| 54 | +/// Returns a `Widen` of the logical dtype `logical` over `values`, with the nullability of |
| 55 | +/// `values`. |
| 56 | +fn widen_over(values: impl IntoArray, logical: DType) -> VortexResult<ArrayRef> { |
| 57 | + let values = values.into_array(); |
| 58 | + let logical = logical.with_nullability(values.dtype().nullability()); |
| 59 | + Ok(WidenArray::try_new(values, logical)?.into_array()) |
| 60 | +} |
| 61 | + |
| 62 | +/// Serializes `array` with an unrestricted context and decodes it again. Returns the decoded |
| 63 | +/// array and the IDs that the context interned. |
| 64 | +fn serialize_round_trip( |
| 65 | + array: &ArrayRef, |
| 66 | + session: &VortexSession, |
| 67 | +) -> VortexResult<(ArrayRef, Vec<ArrayId>)> { |
| 68 | + let ctx = ArrayContext::empty(); |
| 69 | + let serialized = SerializedArray::try_from(serialize_blob(array, &ctx, session)?)?; |
| 70 | + let ids = ctx.to_ids(); |
| 71 | + let decoded = serialized.decode( |
| 72 | + array.dtype(), |
| 73 | + array.len(), |
| 74 | + &ReadContext::new(ids.clone()), |
| 75 | + session, |
| 76 | + )?; |
| 77 | + |
| 78 | + Ok((decoded, ids)) |
| 79 | +} |
| 80 | + |
| 81 | +/// Asserts that `decoded` is a canonical leaf at the canonical width of its dtype. |
| 82 | +#[track_caller] |
| 83 | +fn assert_canonical_leaf(decoded: &ArrayRef) { |
| 84 | + assert!(decoded.is::<Primitive>(), "{}", decoded.encoding_id()); |
| 85 | +} |
| 86 | + |
| 87 | +#[rstest] |
| 88 | +#[case::integer(widen_over(buffer![-1i8, 0, 100], DType::from(PType::I64)))] |
| 89 | +#[case::integer_nullable(widen_over( |
| 90 | + PrimitiveArray::from_option_iter([Some(-300i16), None, Some(300)]), |
| 91 | + DType::from(PType::I32), |
| 92 | +))] |
| 93 | +#[case::empty(widen_over( |
| 94 | + PrimitiveArray::from_iter(Vec::<i16>::new()), |
| 95 | + DType::from(PType::I64), |
| 96 | +))] |
| 97 | +#[case::all_null(widen_over( |
| 98 | + PrimitiveArray::new(buffer![i8::MIN, i8::MAX], Validity::AllInvalid), |
| 99 | + DType::from(PType::I64), |
| 100 | +))] |
| 101 | +fn serialization_writes_widen(#[case] widen: VortexResult<ArrayRef>) -> VortexResult<()> { |
| 102 | + let widen = widen?; |
| 103 | + assert!(widen.is::<Widen>()); |
| 104 | + |
| 105 | + let (decoded, ids) = serialize_round_trip(&widen, &SESSION)?; |
| 106 | + |
| 107 | + assert_canonical_leaf(&decoded); |
| 108 | + assert!(!ids.contains(&ArrayVTable::id(&Widen)), "{ids:?}"); |
| 109 | + assert_arrays_eq!(decoded, widen, &mut SESSION.create_execution_ctx()); |
| 110 | + |
| 111 | + Ok(()) |
| 112 | +} |
| 113 | + |
| 114 | +#[test] |
| 115 | +fn serialization_writes_nested_widen() -> VortexResult<()> { |
| 116 | + let integer = widen_over( |
| 117 | + PrimitiveArray::from_option_iter([Some(1i8), None, Some(-7)]), |
| 118 | + DType::from(PType::I64), |
| 119 | + )?; |
| 120 | + let nested = StructArray::from_fields(&[("integer", integer.clone())])?.into_array(); |
| 121 | + let chunked = ChunkedArray::try_new( |
| 122 | + vec![ |
| 123 | + integer, |
| 124 | + PrimitiveArray::from_option_iter([Some(1000i64)]).into_array(), |
| 125 | + ], |
| 126 | + DType::Primitive(PType::I64, Nullability::Nullable), |
| 127 | + )? |
| 128 | + .into_array(); |
| 129 | + |
| 130 | + for array in [nested, chunked] { |
| 131 | + let (decoded, ids) = serialize_round_trip(&array, &SESSION)?; |
| 132 | + assert!(!ids.contains(&ArrayVTable::id(&Widen)), "{ids:?}"); |
| 133 | + assert_arrays_eq!(decoded, array, &mut SESSION.create_execution_ctx()); |
| 134 | + } |
| 135 | + |
| 136 | + Ok(()) |
| 137 | +} |
| 138 | + |
| 139 | +#[test] |
| 140 | +fn serialization_keeps_widen_statistics() -> VortexResult<()> { |
| 141 | + let widen = widen_over(buffer![-3i8, 5, 1], DType::from(PType::I64))?; |
| 142 | + let min = Scalar::primitive(-3i64, Nullability::NonNullable); |
| 143 | + let max = Scalar::primitive(5i64, Nullability::NonNullable); |
| 144 | + |
| 145 | + for (stat, value) in [(Stat::Min, &min), (Stat::Max, &max)] { |
| 146 | + let value = value.value().cloned(); |
| 147 | + let value = value.vortex_expect("the planted statistic is not null"); |
| 148 | + widen.statistics().set(stat, Precision::Exact(value)); |
| 149 | + } |
| 150 | + |
| 151 | + let (decoded, _) = serialize_round_trip(&widen, &SESSION)?; |
| 152 | + |
| 153 | + assert_eq!(decoded.statistics().get(Stat::Min), Precision::Exact(min)); |
| 154 | + assert_eq!(decoded.statistics().get(Stat::Max), Precision::Exact(max)); |
| 155 | + |
| 156 | + Ok(()) |
| 157 | +} |
| 158 | + |
| 159 | +#[test] |
| 160 | +fn serialization_writes_widen_with_restricted_context() -> VortexResult<()> { |
| 161 | + let ctx = ArrayContext::new(vec![ArrayVTable::id(&Primitive)]) |
| 162 | + .with_allowed_ids([ArrayVTable::id(&Primitive)].into_iter().collect()); |
| 163 | + let widen = widen_over(buffer![-1i8, 0, 100], DType::from(PType::I64))?; |
| 164 | + |
| 165 | + let serialized = SerializedArray::try_from(serialize_blob(&widen, &ctx, &SESSION)?)?; |
| 166 | + let read_ctx = ReadContext::new(ctx.to_ids()); |
| 167 | + let decoded = serialized.decode(widen.dtype(), widen.len(), &read_ctx, &SESSION)?; |
| 168 | + |
| 169 | + assert!(decoded.is::<Primitive>()); |
| 170 | + assert_arrays_eq!(decoded, widen, &mut SESSION.create_execution_ctx()); |
| 171 | + |
| 172 | + Ok(()) |
| 173 | +} |
| 174 | + |
| 175 | +#[test] |
| 176 | +fn empty_session_can_register_widen_writer() -> VortexResult<()> { |
| 177 | + let session = VortexSession::empty().with_some(ArraySession::empty()); |
| 178 | + session.arrays().register(Primitive); |
| 179 | + session.arrays().register(Bool); |
| 180 | + session.arrays().register(WidenPlugin); |
| 181 | + let widen = widen_over(buffer![-1i8, 0, 100], DType::from(PType::I64))?; |
| 182 | + |
| 183 | + let (decoded, ids) = serialize_round_trip(&widen, &session)?; |
| 184 | + |
| 185 | + assert!( |
| 186 | + !session |
| 187 | + .arrays() |
| 188 | + .registry() |
| 189 | + .contains_key(&ArrayVTable::id(&Widen)) |
| 190 | + ); |
| 191 | + assert!(!ids.contains(&ArrayVTable::id(&Widen))); |
| 192 | + assert_arrays_eq!(decoded, widen, &mut session.create_execution_ctx()); |
| 193 | + |
| 194 | + Ok(()) |
| 195 | +} |
0 commit comments