1use std::cmp::Ordering;
13use std::error::Error;
14use std::{fmt, mem};
15
16use mz_ore::cast::CastFrom;
17use mz_persist_types::columnar::FixedSizeCodec;
18use mz_proto::{RustType, TryFromProtoError};
19#[cfg(any(test, feature = "proptest"))]
20use proptest_derive::Arbitrary;
21use serde::{Deserialize, Serialize};
22
23use crate::Datum;
24use crate::row::DatumList;
25use crate::scalar::SqlScalarType;
26
27include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.array.rs"));
28
29pub const MAX_ARRAY_DIMENSIONS: u8 = 6;
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
41pub struct Array<'a, T = Datum<'a>> {
42 pub(crate) elements: DatumList<'a, T>,
46 pub(crate) dims: ArrayDimensions<'a>,
48}
49
50impl<'a, T> Array<'a, T> {
51 pub fn dims(&self) -> ArrayDimensions<'a> {
53 self.dims
54 }
55
56 pub fn elements(&self) -> DatumList<'a, T> {
58 self.elements
59 }
60
61 pub fn has_int2vector_dims(&self) -> bool {
64 self.dims().len() == 1
65 || (self.dims().len() == 0 && self.elements().iter().next().is_none())
66 }
67}
68
69impl<'a> crate::scalar::SqlContainerType for Array<'a, Datum<'a>> {
70 fn unwrap_element_type(container: &SqlScalarType) -> &SqlScalarType {
71 container.unwrap_array_element_type()
72 }
73 fn wrap_element_type(element: SqlScalarType) -> SqlScalarType {
74 SqlScalarType::Array(Box::new(element))
75 }
76}
77
78#[derive(Clone, Copy, Eq, PartialEq, Hash)]
80pub struct ArrayDimensions<'a> {
81 pub(crate) data: &'a [u8],
82}
83
84impl Default for ArrayDimensions<'static> {
85 fn default() -> Self {
86 Self { data: &[] }
87 }
88}
89
90impl ArrayDimensions<'_> {
91 pub fn ndims(&self) -> u8 {
93 let ndims = self.data.len() / (mem::size_of::<usize>() * 2);
94 ndims.try_into().expect("ndims is known to fit in a u8")
95 }
96
97 pub fn len(&self) -> usize {
99 self.ndims().into()
100 }
101
102 pub fn is_empty(&self) -> bool {
104 self.len() == 0
105 }
106}
107
108impl Ord for ArrayDimensions<'_> {
112 fn cmp(&self, other: &Self) -> Ordering {
113 self.ndims()
114 .cmp(&other.ndims())
115 .then_with(|| {
116 self.into_iter()
117 .map(|d| d.length)
118 .cmp(other.into_iter().map(|d| d.length))
119 })
120 .then_with(|| {
121 self.into_iter()
122 .map(|d| d.lower_bound)
123 .cmp(other.into_iter().map(|d| d.lower_bound))
124 })
125 }
126}
127
128impl PartialOrd for ArrayDimensions<'_> {
129 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
130 Some(self.cmp(other))
131 }
132}
133
134impl fmt::Debug for ArrayDimensions<'_> {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 f.debug_list().entries(*self).finish()
137 }
138}
139
140impl<'a> IntoIterator for ArrayDimensions<'a> {
141 type Item = ArrayDimension;
142 type IntoIter = ArrayDimensionsIter<'a>;
143
144 fn into_iter(self) -> ArrayDimensionsIter<'a> {
145 ArrayDimensionsIter { data: self.data }
146 }
147}
148
149#[derive(Debug)]
151pub struct ArrayDimensionsIter<'a> {
152 data: &'a [u8],
153}
154
155impl Iterator for ArrayDimensionsIter<'_> {
156 type Item = ArrayDimension;
157
158 fn next(&mut self) -> Option<ArrayDimension> {
159 if self.data.is_empty() {
160 None
161 } else {
162 let sz = mem::size_of::<usize>();
163 let lower_bound = isize::from_ne_bytes(self.data[..sz].try_into().unwrap());
164 self.data = &self.data[sz..];
165 let length = usize::from_ne_bytes(self.data[..sz].try_into().unwrap());
166 self.data = &self.data[sz..];
167 Some(ArrayDimension {
168 lower_bound,
169 length,
170 })
171 }
172 }
173}
174
175#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
177pub struct ArrayDimension {
178 pub lower_bound: isize,
181 pub length: usize,
183}
184
185impl ArrayDimension {
186 pub fn dimension_bounds(&self) -> (isize, isize) {
191 (
192 self.lower_bound,
193 self.lower_bound
194 + isize::try_from(self.length).expect("fewer than isize::MAX elements")
195 - 1,
196 )
197 }
198}
199
200#[derive(
202 Clone,
203 Copy,
204 Debug,
205 Eq,
206 PartialEq,
207 Hash,
208 Ord,
209 PartialOrd,
210 Serialize,
211 Deserialize
212)]
213#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
214pub enum InvalidArrayError {
215 TooManyDimensions(usize),
217 WrongCardinality { actual: usize, expected: usize },
220}
221
222impl fmt::Display for InvalidArrayError {
223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 match self {
225 InvalidArrayError::TooManyDimensions(n) => write!(
226 f,
227 "number of array dimensions ({}) exceeds the maximum allowed ({})",
228 n, MAX_ARRAY_DIMENSIONS
229 ),
230 InvalidArrayError::WrongCardinality { actual, expected } => write!(
231 f,
232 "number of array elements ({}) does not match declared cardinality ({})",
233 actual, expected
234 ),
235 }
236 }
237}
238
239impl Error for InvalidArrayError {
240 fn source(&self) -> Option<&(dyn Error + 'static)> {
241 None
242 }
243}
244
245impl RustType<ProtoInvalidArrayError> for InvalidArrayError {
246 fn into_proto(&self) -> ProtoInvalidArrayError {
247 use Kind::*;
248 use proto_invalid_array_error::*;
249 let kind = match self {
250 InvalidArrayError::TooManyDimensions(dims) => TooManyDimensions(dims.into_proto()),
251 InvalidArrayError::WrongCardinality { actual, expected } => {
252 WrongCardinality(ProtoWrongCardinality {
253 actual: actual.into_proto(),
254 expected: expected.into_proto(),
255 })
256 }
257 };
258 ProtoInvalidArrayError { kind: Some(kind) }
259 }
260
261 fn from_proto(proto: ProtoInvalidArrayError) -> Result<Self, TryFromProtoError> {
262 use proto_invalid_array_error::Kind::*;
263 match proto.kind {
264 Some(kind) => match kind {
265 TooManyDimensions(dims) => Ok(InvalidArrayError::TooManyDimensions(
266 usize::from_proto(dims)?,
267 )),
268 WrongCardinality(v) => Ok(InvalidArrayError::WrongCardinality {
269 actual: usize::from_proto(v.actual)?,
270 expected: usize::from_proto(v.expected)?,
271 }),
272 },
273 None => Err(TryFromProtoError::missing_field(
274 "`ProtoInvalidArrayError::kind`",
275 )),
276 }
277 }
278}
279
280#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
285pub struct PackedArrayDimension([u8; Self::SIZE]);
286
287#[allow(clippy::as_conversions)]
291impl FixedSizeCodec<ArrayDimension> for PackedArrayDimension {
292 const SIZE: usize = 16;
293
294 fn as_bytes(&self) -> &[u8] {
295 &self.0
296 }
297
298 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
299 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
300 format!(
301 "size for PackedArrayDimension is {} bytes, got {}",
302 Self::SIZE,
303 slice.len()
304 )
305 })?;
306 Ok(PackedArrayDimension(buf))
307 }
308
309 fn from_value(value: ArrayDimension) -> Self {
310 let mut buf = [0; 16];
311
312 let lower_bound = (i64::cast_from(value.lower_bound) as u64) ^ (0x8000_0000_0000_0000u64);
313 buf[..8].copy_from_slice(&lower_bound.to_be_bytes());
314 let length = u64::cast_from(value.length);
315 buf[8..].copy_from_slice(&length.to_be_bytes());
316
317 PackedArrayDimension(buf)
318 }
319
320 fn into_value(self) -> ArrayDimension {
321 let mut lower_bound: [u8; 8] = self.0[..8].try_into().unwrap();
322 lower_bound.copy_from_slice(&self.0[..8]);
323 let lower_bound = u64::from_be_bytes(lower_bound) ^ 0x8000_0000_0000_0000u64;
324
325 let mut length: [u8; 8] = self.0[8..].try_into().unwrap();
326 length.copy_from_slice(&self.0[8..]);
327 let length = u64::from_be_bytes(length);
328
329 ArrayDimension {
330 lower_bound: isize::cast_from(lower_bound as i64),
331 length: usize::cast_from(length),
332 }
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use std::iter::empty;
339
340 use mz_ore::assert_ok;
341 use mz_proto::protobuf_roundtrip;
342 use proptest::prelude::*;
343
344 use crate::Datum;
345 use crate::row::Row;
346
347 use super::*;
348
349 #[mz_ore::test]
350 fn test_has_int2vector_dims() {
351 let mut row = Row::default();
353 row.packer()
354 .try_push_array(
355 &[ArrayDimension {
356 lower_bound: 1,
357 length: 2,
358 }],
359 [Datum::Int16(1), Datum::Int16(2)],
360 )
361 .unwrap();
362 let arr = row.unpack_first().unwrap_array();
363 assert!(
364 arr.has_int2vector_dims(),
365 "1-D array should have int2vector dims"
366 );
367
368 let mut row = Row::default();
370 row.packer()
371 .try_push_array(
372 &[ArrayDimension {
373 lower_bound: 1,
374 length: 0,
375 }],
376 empty::<Datum>(),
377 )
378 .unwrap();
379 let arr = row.unpack_first().unwrap_array();
380 assert!(
381 arr.has_int2vector_dims(),
382 "1-D empty array should have int2vector dims"
383 );
384
385 let mut row = Row::default();
387 row.packer().try_push_array(&[], empty::<Datum>()).unwrap();
388 let arr = row.unpack_first().unwrap_array();
389 assert!(
390 arr.has_int2vector_dims(),
391 "0-D empty array should have int2vector dims"
392 );
393
394 let mut row = Row::default();
396 row.packer()
397 .try_push_array(
398 &[
399 ArrayDimension {
400 lower_bound: 1,
401 length: 1,
402 },
403 ArrayDimension {
404 lower_bound: 1,
405 length: 2,
406 },
407 ],
408 [Datum::Int16(1), Datum::Int16(2)],
409 )
410 .unwrap();
411 let arr = row.unpack_first().unwrap_array();
412 assert!(
413 !arr.has_int2vector_dims(),
414 "2-D array should not have int2vector dims"
415 );
416 }
417
418 proptest! {
419 #[mz_ore::test]
420 fn invalid_array_error_protobuf_roundtrip(expect in any::<InvalidArrayError>()) {
421 let actual = protobuf_roundtrip::<_, ProtoInvalidArrayError>(&expect);
422 assert_ok!(actual);
423 assert_eq!(actual.unwrap(), expect);
424 }
425 }
426
427 fn arb_array_dimension() -> impl Strategy<Value = ArrayDimension> {
428 (any::<isize>(), any::<usize>()).prop_map(|(lower, length)| ArrayDimension {
429 lower_bound: lower,
430 length,
431 })
432 }
433
434 #[mz_ore::test]
435 fn proptest_packed_array_dimension_roundtrip() {
436 fn test(og: ArrayDimension) {
437 let packed = PackedArrayDimension::from_value(og);
438 let rnd = packed.into_value();
439 assert_eq!(og, rnd);
440 }
441
442 proptest!(|(dim in arb_array_dimension())| test(dim))
443 }
444
445 #[mz_ore::test]
446 fn proptest_packed_array_dimension_sorts() {
447 fn test(mut og: Vec<ArrayDimension>) {
448 let mut packed: Vec<_> = og
449 .iter()
450 .copied()
451 .map(PackedArrayDimension::from_value)
452 .collect();
453
454 packed.sort();
455 og.sort();
456
457 let rnd: Vec<_> = packed
458 .into_iter()
459 .map(PackedArrayDimension::into_value)
460 .collect();
461 assert_eq!(og, rnd);
462 }
463
464 let strat = proptest::collection::vec(arb_array_dimension(), 0..16);
465 proptest!(|(dim in strat)| test(dim))
466 }
467}