1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! [`TypedArray<L>`]: the data half of a [`Column`](crate::Column) —
//! the arrow array plus its downcast view, without the metadata.
use arrow::array::{Array as _, ArrayRef};
use crate::datatype::{PrimitiveType, RefType};
use crate::{ColumnError, LogicalType};
/// A strongly-typed, validated, zero-copy view of one arrow array:
/// a [`Column`](crate::Column) minus the per-column metadata.
///
/// Validates the array **once, eagerly** at construction
/// (exact datatype, including the inner types of nested arrays, plus nulls at
/// every non-`Option` nesting level). After that, element access is infallible,
/// fully typed, and zero-copy.
pub(crate) struct TypedArray<L: LogicalType> {
/// The original arrow array (kept for cheap conversion back to arrow).
array: ArrayRef,
/// The fully-downcast representation.
typed: L::Typed,
}
impl<L: LogicalType> TypedArray<L> {
/// Validates the array against the logical type `L` (datatype and nullability,
/// recursively), then downcasts it (zero-copy).
///
/// # Errors
/// Errors on datatype mismatch, or on nulls at any non-`Option` nesting level.
pub fn try_new(array: ArrayRef) -> Result<Self, ColumnError> {
// Validate (and downcast) the datatype first — `downcast` rejects a wrong
// datatype, including parameters the concrete arrow array type doesn't
// encode (a fixed size, a timestamp timezone), and checks all child-level
// nulls. Doing it before the top-level null check means a datatype
// mismatch is reported as `WrongDatatype`, not masked by `UnexpectedNulls`.
let typed = L::downcast(&*array)?;
if !L::NULLABLE && 0 < array.null_count() {
return Err(ColumnError::UnexpectedNulls {
null_count: array.null_count(),
});
}
Ok(Self { array, typed })
}
#[inline]
pub fn len(&self) -> usize {
self.array.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.array.is_empty()
}
/// The value at `index`, or `None` if out of bounds.
#[inline]
pub fn get(&self, index: usize) -> Option<L::Value<'_>> {
// SAFETY: bounds checked here, once.
(index < self.len()).then(|| unsafe { self.value_unchecked(index) })
}
/// The value at `index`. Panics if out of bounds.
#[inline]
pub fn value(&self, index: usize) -> L::Value<'_> {
assert!(index < self.len(), "Index {index} out of bounds");
// SAFETY: bounds checked just above.
unsafe { self.value_unchecked(index) }
}
/// The value at `index`, without bounds checking.
///
/// # Safety
/// `index < self.len()`. That is the only quiver-level precondition; the
/// read itself relies on arrow's buffer/offset invariants, which the array
/// upholds by construction (quiver validated its datatype and nullability,
/// not those internal invariants).
#[inline]
pub(crate) unsafe fn value_unchecked(&self, index: usize) -> L::Value<'_> {
// SAFETY: forwarded from the caller's contract.
unsafe { L::value_unchecked(&self.typed, index) }
}
/// The underlying arrow array.
pub fn as_arrow(&self) -> &ArrayRef {
&self.array
}
/// Extract the underlying arrow array.
pub fn into_arrow(self) -> ArrayRef {
self.array
}
}
impl<L: RefType> TypedArray<L> {
/// Like [`TypedArray::value`], but borrows from the array.
/// Panics if out of bounds.
#[inline]
pub fn value_ref(&self, index: usize) -> &L::Ref {
assert!(index < self.len(), "Index {index} out of bounds");
L::value_ref(&self.typed, index)
}
}
impl<L: PrimitiveType> TypedArray<L> {
/// The values as a contiguous zero-copy slice.
#[inline]
pub fn values(&self) -> &[L::Native] {
L::values(&self.typed)
}
}
/// Compares the data (like arrow array equality).
impl<L: LogicalType> PartialEq for TypedArray<L> {
fn eq(&self, other: &Self) -> bool {
self.array.as_ref() == other.array.as_ref()
}
}
impl<L: LogicalType> Clone for TypedArray<L> {
fn clone(&self) -> Self {
Self {
array: ArrayRef::clone(&self.array),
typed: self.typed.clone(),
}
}
}
impl<L: LogicalType> std::fmt::Debug for TypedArray<L> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedArray")
.field("array", &self.array)
.finish_non_exhaustive()
}
}