Skip to main content

vortex_buffer/
arrow.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use arrow_buffer::ArrowNativeType;
5use arrow_buffer::OffsetBuffer;
6use vortex_error::vortex_panic;
7
8use crate::Alignment;
9use crate::Buffer;
10use crate::ByteBuffer;
11
12impl<T: ArrowNativeType> Buffer<T> {
13    /// Converts the buffer zero-copy into a `arrow_buffer::Buffer`.
14    pub fn into_arrow_scalar_buffer(self) -> arrow_buffer::ScalarBuffer<T> {
15        if self.is_empty() {
16            return Vec::new().into();
17        }
18        let buffer = self.into_byte_buffer().into_arrow_buffer();
19        arrow_buffer::ScalarBuffer::from(buffer)
20    }
21
22    /// Convert an Arrow scalar buffer into a Vortex scalar buffer.
23    ///
24    /// ## Panics
25    ///
26    /// Panics if the Arrow buffer is not aligned to the requested alignment, or if the requested
27    /// alignment is not sufficient for type T.
28    pub fn from_arrow_scalar_buffer(arrow: arrow_buffer::ScalarBuffer<T>) -> Self {
29        let length = arrow.len();
30        let arrow = arrow.into_inner();
31
32        let alignment = Alignment::of::<T>();
33        if arrow.as_ptr().align_offset(alignment.as_usize()) != 0 {
34            vortex_panic!(
35                "Arrow buffer is not aligned to the requested alignment: {}",
36                alignment
37            );
38        }
39
40        debug_assert_eq!(length, arrow.len() / size_of::<T>());
41        Self::from_arrow_owner(arrow, length, alignment)
42    }
43
44    /// Converts the buffer zero-copy into a `arrow_buffer::OffsetBuffer`.
45    ///
46    /// ## Panics
47    ///
48    /// Panics if the buffer is empty, its first offset is negative, or it is not monotonically
49    /// increasing -- the invariants `OffsetBuffer` requires of its contents.
50    pub fn into_arrow_offset_buffer(self) -> OffsetBuffer<T> {
51        OffsetBuffer::new(self.into_arrow_scalar_buffer())
52    }
53
54    /// Converts the buffer zero-copy into a `arrow_buffer::OffsetBuffer` without validation.
55    ///
56    /// Use this when the offsets were already validated, for example when they come from an
57    /// array whose invariants guarantee valid offsets.
58    ///
59    /// # Safety
60    ///
61    /// The buffer must be non-empty, its first offset must be greater than or equal to zero, and
62    /// it must be monotonically non-decreasing.
63    pub unsafe fn into_arrow_offset_buffer_unchecked(self) -> OffsetBuffer<T> {
64        // SAFETY: the caller guarantees the `OffsetBuffer` invariants.
65        unsafe { OffsetBuffer::new_unchecked(self.into_arrow_scalar_buffer()) }
66    }
67}
68
69impl ByteBuffer {
70    /// Converts the buffer zero-copy into a `arrow_buffer::Buffer`.
71    pub fn into_arrow_buffer(self) -> arrow_buffer::Buffer {
72        if let Some(crate::BufferBacking::Arrow(arrow)) = self.backing.as_deref() {
73            let offset = self.ptr.addr().get() - arrow.as_ptr().addr();
74            return arrow.slice_with_length(offset, self.length);
75        }
76        arrow_buffer::Buffer::from(self.into_bytes())
77    }
78
79    /// Convert an Arrow scalar buffer into a Vortex scalar buffer.
80    ///
81    /// ## Panics
82    ///
83    /// Panics if the Arrow buffer is not sufficiently aligned.
84    pub fn from_arrow_buffer(arrow: arrow_buffer::Buffer, alignment: Alignment) -> Self {
85        let length = arrow.len();
86
87        if arrow.as_ptr().align_offset(alignment.as_usize()) != 0 {
88            vortex_panic!(
89                "Arrow buffer is not aligned to the requested alignment: {}",
90                alignment
91            );
92        }
93
94        Self::from_arrow_owner(arrow, length, alignment)
95    }
96}
97
98#[cfg(test)]
99mod test {
100    use arrow_buffer::Buffer as ArrowBuffer;
101    use arrow_buffer::ScalarBuffer;
102
103    use crate::Alignment;
104    use crate::Buffer;
105    use crate::buffer;
106
107    #[test]
108    fn into_arrow_buffer() {
109        let buf = buffer![0u8, 1, 2];
110        let arrow: ArrowBuffer = buf.clone().into_arrow_buffer();
111        assert_eq!(arrow.as_ref(), buf.as_slice(), "Buffer values differ");
112        assert_eq!(arrow.as_ptr(), buf.as_ptr(), "Conversion not zero-copy")
113    }
114
115    #[test]
116    fn into_arrow_scalar_buffer() {
117        let buf = buffer![0i32, 1, 2];
118        let scalar: ScalarBuffer<i32> = buf.clone().into_arrow_scalar_buffer();
119        assert_eq!(scalar.as_ref(), buf.as_slice(), "Buffer values differ");
120        assert_eq!(scalar.as_ptr(), buf.as_ptr(), "Conversion not zero-copy")
121    }
122
123    #[test]
124    fn empty_into_arrow_scalar_buffer() {
125        let scalar = Buffer::<i64>::empty().into_arrow_scalar_buffer();
126
127        assert!(scalar.is_empty());
128        assert_eq!(scalar.as_ptr().align_offset(align_of::<i64>()), 0);
129    }
130
131    #[test]
132    fn from_arrow_buffer() {
133        let arrow = ArrowBuffer::from_vec(vec![0i32, 1, 2]);
134        let buf = Buffer::from_arrow_buffer(arrow.clone(), Alignment::of::<i32>());
135        assert_eq!(arrow.as_ref(), buf.as_slice(), "Buffer values differ");
136        assert_eq!(arrow.as_ptr(), buf.as_ptr(), "Conversion not zero-copy");
137
138        let round_trip = buf.into_arrow_buffer();
139        assert_eq!(round_trip.as_ptr(), arrow.as_ptr());
140    }
141
142    #[test]
143    fn into_arrow_offset_buffer() {
144        let buf = buffer![0i32, 2, 2, 5];
145        let offsets = buf.clone().into_arrow_offset_buffer();
146        assert_eq!(offsets.as_ref(), buf.as_slice(), "Buffer values differ");
147        assert_eq!(offsets.as_ptr(), buf.as_ptr(), "Conversion not zero-copy");
148    }
149
150    #[test]
151    fn into_arrow_offset_buffer_unchecked() {
152        let buf = buffer![0i32, 2, 2, 5];
153
154        // SAFETY: the offsets are non-empty, non-negative and monotonically non-decreasing.
155        let offsets = unsafe { buf.clone().into_arrow_offset_buffer_unchecked() };
156
157        assert_eq!(offsets.as_ref(), buf.as_slice(), "Buffer values differ");
158        assert_eq!(offsets.as_ptr(), buf.as_ptr(), "Conversion not zero-copy");
159    }
160
161    #[test]
162    fn into_arrow_offset_buffer_allows_nonzero_start() {
163        // Offsets into a sliced child array need not begin at zero.
164        let offsets = buffer![3i64, 4, 9].into_arrow_offset_buffer();
165        assert_eq!(offsets.as_ref(), &[3, 4, 9]);
166    }
167
168    #[test]
169    #[should_panic(expected = "offsets must be monotonically increasing")]
170    fn into_arrow_offset_buffer_rejects_decreasing_offsets() {
171        drop(buffer![0i32, 5, 3].into_arrow_offset_buffer());
172    }
173
174    #[test]
175    #[should_panic(expected = "offsets must be greater than 0")]
176    fn into_arrow_offset_buffer_rejects_negative_offsets() {
177        drop(buffer![-1i32, 2].into_arrow_offset_buffer());
178    }
179
180    #[test]
181    #[should_panic(expected = "offsets cannot be empty")]
182    fn into_arrow_offset_buffer_rejects_empty() {
183        drop(Buffer::<i32>::empty().into_arrow_offset_buffer());
184    }
185}