Skip to main content

polars_arrow/array/binview/
builder.rs

1use std::marker::PhantomData;
2use std::sync::LazyLock;
3
4use hashbrown::hash_map::Entry;
5use polars_buffer::Buffer;
6use polars_utils::IdxSize;
7use polars_utils::aliases::{InitHashMaps, PlHashMap};
8
9use crate::array::binview::{
10    BINVIEW_ARROW_BUFFER_LEN_LIMIT, DEFAULT_BLOCK_SIZE, MAX_EXP_BLOCK_SIZE,
11};
12use crate::array::builder::{ShareStrategy, StaticArrayBuilder};
13use crate::array::{Array, BINVIEW_MAX_ROW_BYTE_LEN, BinaryViewArrayGeneric, View, ViewType};
14use crate::bitmap::OptBitmapBuilder;
15use crate::datatypes::ArrowDataType;
16use crate::pushable::Pushable;
17
18static PLACEHOLDER_BUFFER: LazyLock<Buffer<u8>> = LazyLock::new(|| Buffer::from_static(&[]));
19
20pub struct BinaryViewArrayGenericBuilder<V: ViewType + ?Sized> {
21    dtype: ArrowDataType,
22    views: Vec<View>,
23    active_buffer: Vec<u8>,
24    active_buffer_idx: u32,
25    buffer_set: Vec<Buffer<u8>>,
26    stolen_buffers: PlHashMap<usize, u32>,
27
28    // With these we can amortize buffer set translation costs if repeatedly
29    // stealing from the same set of buffers.
30    last_buffer_set_stolen_from: Option<Buffer<Buffer<u8>>>,
31    buffer_set_translation_idxs: Vec<(u32, u32)>, // (idx, generation)
32    buffer_set_translation_generation: u32,
33
34    validity: OptBitmapBuilder,
35    /// Total bytes length if we would concatenate them all.
36    total_bytes_len: usize,
37    /// Total bytes in the buffer set (excluding remaining capacity).
38    total_buffer_len: usize,
39    view_type: PhantomData<V>,
40}
41
42impl<V: ViewType + ?Sized> BinaryViewArrayGenericBuilder<V> {
43    pub fn new(dtype: ArrowDataType) -> Self {
44        Self {
45            dtype,
46            views: Vec::new(),
47            active_buffer: Vec::new(),
48            active_buffer_idx: 0,
49            buffer_set: Vec::new(),
50            stolen_buffers: PlHashMap::new(),
51            last_buffer_set_stolen_from: None,
52            buffer_set_translation_idxs: Vec::new(),
53            buffer_set_translation_generation: 0,
54            validity: OptBitmapBuilder::default(),
55            total_bytes_len: 0,
56            total_buffer_len: 0,
57            view_type: PhantomData,
58        }
59    }
60
61    #[inline]
62    fn reserve_active_buffer(&mut self, additional: usize) {
63        let len = self.active_buffer.len();
64        let cap = self.active_buffer.capacity();
65        if len.saturating_add(additional) > usize::min(BINVIEW_ARROW_BUFFER_LEN_LIMIT, cap) {
66            self.reserve_active_buffer_slow(additional);
67        }
68    }
69
70    #[cold]
71    fn reserve_active_buffer_slow(&mut self, additional: usize) {
72        assert!(
73            additional <= BINVIEW_MAX_ROW_BYTE_LEN,
74            "strings longer than 2^32 - 2 are not supported"
75        );
76
77        const {
78            assert!(MAX_EXP_BLOCK_SIZE < BINVIEW_ARROW_BUFFER_LEN_LIMIT);
79        }
80
81        // Allocate a new buffer and flush the old buffer.
82        let new_capacity = usize::max(
83            additional,
84            (self.active_buffer.capacity() * 2).clamp(DEFAULT_BLOCK_SIZE, MAX_EXP_BLOCK_SIZE),
85        );
86
87        let old_buffer =
88            core::mem::replace(&mut self.active_buffer, Vec::with_capacity(new_capacity));
89        if !old_buffer.is_empty() {
90            //  Replace dummy with real buffer.
91            self.buffer_set[self.active_buffer_idx as usize] = Buffer::from(old_buffer);
92        }
93        self.active_buffer_idx = self.buffer_set.len().try_into().unwrap();
94        self.buffer_set.push(PLACEHOLDER_BUFFER.clone()) // Push placeholder so active_buffer_idx stays valid.
95    }
96
97    pub fn push_value_ignore_validity(&mut self, bytes: &V) {
98        let bytes = bytes.to_bytes();
99        self.total_bytes_len += bytes.len();
100        unsafe {
101            let view = if bytes.len() > View::MAX_INLINE_SIZE as usize {
102                self.reserve_active_buffer(bytes.len());
103
104                let offset = self.active_buffer.len() as u32; // Ensured no overflow by reserve_active_buffer.
105                self.active_buffer.extend_from_slice(bytes);
106                self.total_buffer_len += bytes.len();
107                View::new_noninline_unchecked(bytes, self.active_buffer_idx, offset)
108            } else {
109                View::new_inline_unchecked(bytes)
110            };
111            self.views.push(view);
112        }
113    }
114
115    /// # Safety
116    /// The view must be inline.
117    pub unsafe fn push_inline_view_ignore_validity(&mut self, view: View) {
118        debug_assert!(view.is_inline());
119        self.total_bytes_len += view.length as usize;
120        self.views.push(view);
121    }
122
123    fn switch_active_stealing_bufferset_to(&mut self, buffer_set: &Buffer<Buffer<u8>>) {
124        if self
125            .last_buffer_set_stolen_from
126            .as_ref()
127            .is_some_and(|stolen_bs| {
128                stolen_bs.as_ptr() == buffer_set.as_ptr() && stolen_bs.len() >= buffer_set.len()
129            })
130        {
131            return; // Already active.
132        }
133
134        // Switch to new generation (invalidating all old translation indices),
135        // and resizing the buffer with invalid indices if necessary.
136        let old_gen = self.buffer_set_translation_generation;
137        self.buffer_set_translation_generation = old_gen.wrapping_add(1);
138        if self.buffer_set_translation_idxs.len() < buffer_set.len() {
139            self.buffer_set_translation_idxs
140                .resize(buffer_set.len(), (0, old_gen));
141        }
142    }
143
144    unsafe fn translate_view(
145        &mut self,
146        mut view: View,
147        other_bufferset: &Buffer<Buffer<u8>>,
148    ) -> View {
149        // Translate from old array-local buffer idx to global stolen buffer idx.
150        let (mut new_buffer_idx, gen_) = *self
151            .buffer_set_translation_idxs
152            .get_unchecked(view.buffer_idx as usize);
153        if gen_ != self.buffer_set_translation_generation {
154            // This buffer index wasn't seen before for this array, do a dedup lookup.
155            // Since we map by starting pointer and different subslices may have different lengths, we expand
156            // the buffer to the maximum it could be.
157            let buffer = other_bufferset
158                .get_unchecked(view.buffer_idx as usize)
159                .clone()
160                .expand_end_to_storage();
161            let buf_id = buffer.as_slice().as_ptr().addr();
162            let idx = match self.stolen_buffers.entry(buf_id) {
163                Entry::Occupied(o) => *o.get(),
164                Entry::Vacant(v) => {
165                    let idx = self.buffer_set.len() as u32;
166                    self.total_buffer_len += buffer.len();
167                    self.buffer_set.push(buffer);
168                    v.insert(idx);
169                    idx
170                },
171            };
172
173            // Cache result for future lookups.
174            *self
175                .buffer_set_translation_idxs
176                .get_unchecked_mut(view.buffer_idx as usize) =
177                (idx, self.buffer_set_translation_generation);
178            new_buffer_idx = idx;
179        }
180        view.buffer_idx = new_buffer_idx;
181        view
182    }
183
184    unsafe fn extend_views_dedup_ignore_validity(
185        &mut self,
186        views: impl IntoIterator<Item = View>,
187        other_bufferset: &Buffer<Buffer<u8>>,
188    ) {
189        // TODO: if there are way more buffers than length translate per-view
190        // rather than all at once.
191        self.switch_active_stealing_bufferset_to(other_bufferset);
192
193        for mut view in views {
194            if view.length > View::MAX_INLINE_SIZE {
195                view = self.translate_view(view, other_bufferset);
196            }
197            self.total_bytes_len += view.length as usize;
198            self.views.push(view);
199        }
200    }
201
202    unsafe fn extend_views_each_repeated_dedup_ignore_validity(
203        &mut self,
204        views: impl IntoIterator<Item = View>,
205        repeats: usize,
206        other_bufferset: &Buffer<Buffer<u8>>,
207    ) {
208        // TODO: if there are way more buffers than length translate per-view
209        // rather than all at once.
210        self.switch_active_stealing_bufferset_to(other_bufferset);
211
212        for mut view in views {
213            if view.length > View::MAX_INLINE_SIZE {
214                view = self.translate_view(view, other_bufferset);
215            }
216            self.total_bytes_len += repeats * view.length as usize;
217            for _ in 0..repeats {
218                self.views.push(view);
219            }
220        }
221    }
222}
223
224impl<V: ViewType + ?Sized> StaticArrayBuilder for BinaryViewArrayGenericBuilder<V> {
225    type Array = BinaryViewArrayGeneric<V>;
226
227    fn dtype(&self) -> &ArrowDataType {
228        &self.dtype
229    }
230
231    fn reserve(&mut self, additional: usize) {
232        self.views.reserve(additional);
233        self.validity.reserve(additional);
234    }
235
236    fn freeze(mut self) -> Self::Array {
237        // Flush active buffer and/or remove extra placeholder buffer.
238        if !self.active_buffer.is_empty() {
239            self.buffer_set[self.active_buffer_idx as usize] = Buffer::from(self.active_buffer);
240        } else if self.buffer_set.last().is_some_and(|b| b.is_empty()) {
241            self.buffer_set.pop();
242        }
243
244        unsafe {
245            BinaryViewArrayGeneric::new_unchecked(
246                self.dtype,
247                Buffer::from(self.views),
248                Buffer::from(self.buffer_set),
249                self.validity.into_opt_validity(),
250                Some(self.total_bytes_len),
251                self.total_buffer_len,
252            )
253        }
254    }
255
256    fn freeze_reset(&mut self) -> Self::Array {
257        // Flush active buffer and/or remove extra placeholder buffer.
258        if !self.active_buffer.is_empty() {
259            self.buffer_set[self.active_buffer_idx as usize] =
260                Buffer::from(core::mem::take(&mut self.active_buffer));
261        } else if self.buffer_set.last().is_some_and(|b| b.is_empty()) {
262            self.buffer_set.pop();
263        }
264
265        let out = unsafe {
266            BinaryViewArrayGeneric::new_unchecked(
267                self.dtype.clone(),
268                Buffer::from(core::mem::take(&mut self.views)),
269                Buffer::from(core::mem::take(&mut self.buffer_set)),
270                core::mem::take(&mut self.validity).into_opt_validity(),
271                Some(self.total_bytes_len),
272                self.total_buffer_len,
273            )
274        };
275
276        self.total_buffer_len = 0;
277        self.total_bytes_len = 0;
278        self.active_buffer_idx = 0;
279        self.stolen_buffers.clear();
280        self.last_buffer_set_stolen_from = None;
281        out
282    }
283
284    fn len(&self) -> usize {
285        self.views.len()
286    }
287
288    fn extend_nulls(&mut self, length: usize) {
289        self.views.extend_constant(length, View::default());
290        self.validity.extend_constant(length, false);
291    }
292
293    fn subslice_extend(
294        &mut self,
295        other: &Self::Array,
296        start: usize,
297        length: usize,
298        share: ShareStrategy,
299    ) {
300        self.views.reserve(length);
301
302        unsafe {
303            match share {
304                ShareStrategy::Never => {
305                    if let Some(v) = other.validity() {
306                        for i in start..start + length {
307                            if v.get_bit_unchecked(i) {
308                                self.push_value_ignore_validity(other.value_unchecked(i));
309                            } else {
310                                self.views.push(View::default())
311                            }
312                        }
313                    } else {
314                        for i in start..start + length {
315                            self.push_value_ignore_validity(other.value_unchecked(i));
316                        }
317                    }
318                },
319                ShareStrategy::Always => {
320                    let other_views = &other.views()[start..start + length];
321                    self.extend_views_dedup_ignore_validity(
322                        other_views.iter().copied(),
323                        other.data_buffers(),
324                    );
325                },
326            }
327        }
328
329        self.validity
330            .subslice_extend_from_opt_validity(other.validity(), start, length);
331    }
332
333    fn subslice_extend_each_repeated(
334        &mut self,
335        other: &Self::Array,
336        start: usize,
337        length: usize,
338        repeats: usize,
339        share: ShareStrategy,
340    ) {
341        self.views.reserve(length * repeats);
342
343        unsafe {
344            match share {
345                ShareStrategy::Never => {
346                    if let Some(v) = other.validity() {
347                        for i in start..start + length {
348                            if v.get_bit_unchecked(i) {
349                                for _ in 0..repeats {
350                                    self.push_value_ignore_validity(other.value_unchecked(i));
351                                }
352                            } else {
353                                for _ in 0..repeats {
354                                    self.views.push(View::default())
355                                }
356                            }
357                        }
358                    } else {
359                        for i in start..start + length {
360                            for _ in 0..repeats {
361                                self.push_value_ignore_validity(other.value_unchecked(i));
362                            }
363                        }
364                    }
365                },
366                ShareStrategy::Always => {
367                    let other_views = &other.views()[start..start + length];
368                    self.extend_views_each_repeated_dedup_ignore_validity(
369                        other_views.iter().copied(),
370                        repeats,
371                        other.data_buffers(),
372                    );
373                },
374            }
375        }
376
377        self.validity
378            .subslice_extend_each_repeated_from_opt_validity(
379                other.validity(),
380                start,
381                length,
382                repeats,
383            );
384    }
385
386    unsafe fn gather_extend(
387        &mut self,
388        other: &Self::Array,
389        idxs: &[IdxSize],
390        share: ShareStrategy,
391    ) {
392        self.views.reserve(idxs.len());
393
394        unsafe {
395            match share {
396                ShareStrategy::Never => {
397                    if let Some(v) = other.validity() {
398                        for idx in idxs {
399                            if v.get_bit_unchecked(*idx as usize) {
400                                self.push_value_ignore_validity(
401                                    other.value_unchecked(*idx as usize),
402                                );
403                            } else {
404                                self.views.push(View::default())
405                            }
406                        }
407                    } else {
408                        for idx in idxs {
409                            self.push_value_ignore_validity(other.value_unchecked(*idx as usize));
410                        }
411                    }
412                },
413                ShareStrategy::Always => {
414                    let other_view_slice = other.views().as_slice();
415                    let other_views = idxs
416                        .iter()
417                        .map(|idx| *other_view_slice.get_unchecked(*idx as usize));
418                    self.extend_views_dedup_ignore_validity(other_views, other.data_buffers());
419                },
420            }
421        }
422
423        self.validity
424            .gather_extend_from_opt_validity(other.validity(), idxs);
425    }
426
427    fn opt_gather_extend(&mut self, other: &Self::Array, idxs: &[IdxSize], share: ShareStrategy) {
428        self.views.reserve(idxs.len());
429
430        unsafe {
431            match share {
432                ShareStrategy::Never => {
433                    if let Some(v) = other.validity() {
434                        for idx in idxs {
435                            if (*idx as usize) < v.len() && v.get_bit_unchecked(*idx as usize) {
436                                self.push_value_ignore_validity(
437                                    other.value_unchecked(*idx as usize),
438                                );
439                            } else {
440                                self.views.push(View::default())
441                            }
442                        }
443                    } else {
444                        for idx in idxs {
445                            if (*idx as usize) < other.len() {
446                                self.push_value_ignore_validity(
447                                    other.value_unchecked(*idx as usize),
448                                );
449                            } else {
450                                self.views.push(View::default())
451                            }
452                        }
453                    }
454                },
455                ShareStrategy::Always => {
456                    let other_view_slice = other.views().as_slice();
457                    let other_views = idxs.iter().map(|idx| {
458                        other_view_slice
459                            .get(*idx as usize)
460                            .copied()
461                            .unwrap_or_default()
462                    });
463                    self.extend_views_dedup_ignore_validity(other_views, other.data_buffers());
464                },
465            }
466        }
467
468        self.validity
469            .opt_gather_extend_from_opt_validity(other.validity(), idxs, other.len());
470    }
471}