Skip to main content

datafusion_functions/
strings.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::marker::PhantomData;
19use std::mem::size_of;
20use std::sync::Arc;
21
22use datafusion_common::{
23    DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err, internal_err,
24    plan_err,
25};
26
27use arrow::array::{
28    Array, ArrayAccessor, ArrayDataBuilder, ArrayRef, BinaryArray, BinaryViewArray,
29    ByteView, GenericStringArray, LargeBinaryArray, LargeStringArray, OffsetSizeTrait,
30    StringArray, StringViewArray, as_largestring_array, make_view,
31};
32use arrow::buffer::{Buffer, MutableBuffer, NullBuffer, ScalarBuffer};
33use arrow::datatypes::DataType;
34use arrow_buffer::ArrowNativeType;
35use datafusion_common::cast::{
36    as_binary_array, as_binary_view_array, as_large_binary_array, as_string_array,
37    as_string_view_array,
38};
39use datafusion_expr_common::columnar_value::ColumnarValue;
40
41/// Trait abstracting concatenating string and binary collections.
42pub(crate) trait ConcatBuilder {
43    fn write<const CHECK_VALID: bool>(
44        &mut self,
45        column: &ColumnarValueRef,
46        i: usize,
47    ) -> Result<()>;
48
49    fn append_offset(&mut self) -> Result<()>;
50
51    fn finish(self, null_buffer: Option<NullBuffer>) -> Result<ArrayRef>;
52}
53
54/// Builder used by `concat`/`concat_ws` to assemble a [`GenericStringArray<O>`]
55/// (`StringArray` or `LargeStringArray`) one row at a time from multiple input columns.
56///
57/// Each row is written via repeated `write` calls (one per input fragment)
58/// followed by a single `append_offset` to commit the row.  The output null
59/// buffer is computed in bulk by the caller and supplied to `finish`, avoiding
60/// per-row NULL handling work.
61///
62/// For the common "produce one `&str` per row" pattern, prefer
63/// `GenericStringArrayBuilder` instead.
64pub(crate) struct ConcatGenericStringBuilder<O: OffsetSizeTrait + ArrowNativeType> {
65    offsets_buffer: MutableBuffer,
66    value_buffer: MutableBuffer,
67    _phantom: PhantomData<O>,
68}
69pub(crate) type ConcatStringBuilder = ConcatGenericStringBuilder<i32>;
70pub(crate) type ConcatLargeStringBuilder = ConcatGenericStringBuilder<i64>;
71
72impl<O: OffsetSizeTrait + ArrowNativeType> ConcatGenericStringBuilder<O> {
73    pub fn with_capacity(item_capacity: usize, data_capacity: usize) -> Self {
74        let capacity = item_capacity
75            .checked_add(1)
76            .map(|i| i.saturating_mul(size_of::<O>()))
77            .expect("capacity integer overflow");
78
79        let mut offsets_buffer = MutableBuffer::with_capacity(capacity);
80        // SAFETY: the first offset value is definitely not going to exceed the bounds.
81        unsafe { offsets_buffer.push_unchecked(O::usize_as(0)) };
82        Self {
83            offsets_buffer,
84            value_buffer: MutableBuffer::with_capacity(data_capacity),
85            _phantom: PhantomData,
86        }
87    }
88}
89
90impl<O: OffsetSizeTrait + ArrowNativeType> ConcatBuilder
91    for ConcatGenericStringBuilder<O>
92{
93    fn write<const CHECK_VALID: bool>(
94        &mut self,
95        column: &ColumnarValueRef,
96        i: usize,
97    ) -> Result<()> {
98        match column {
99            ColumnarValueRef::Scalar(s) => {
100                std::str::from_utf8(s).map_err(|_| {
101                    exec_datafusion_err!("concat: scalar bytes are not valid UTF-8")
102                })?;
103                self.value_buffer.extend_from_slice(s);
104            }
105            ColumnarValueRef::NullableArray(array) => {
106                if !CHECK_VALID || array.is_valid(i) {
107                    self.value_buffer
108                        .extend_from_slice(array.value(i).as_bytes());
109                }
110            }
111            ColumnarValueRef::NullableLargeStringArray(array) => {
112                if !CHECK_VALID || array.is_valid(i) {
113                    self.value_buffer
114                        .extend_from_slice(array.value(i).as_bytes());
115                }
116            }
117            ColumnarValueRef::NullableStringViewArray(array) => {
118                if !CHECK_VALID || array.is_valid(i) {
119                    self.value_buffer
120                        .extend_from_slice(array.value(i).as_bytes());
121                }
122            }
123            ColumnarValueRef::NonNullableArray(array) => {
124                self.value_buffer
125                    .extend_from_slice(array.value(i).as_bytes());
126            }
127            ColumnarValueRef::NonNullableLargeStringArray(array) => {
128                self.value_buffer
129                    .extend_from_slice(array.value(i).as_bytes());
130            }
131            ColumnarValueRef::NonNullableStringViewArray(array) => {
132                self.value_buffer
133                    .extend_from_slice(array.value(i).as_bytes());
134            }
135            _ => {
136                return exec_err!(
137                    "concat: unexpected column type for string builder: {column:?}"
138                );
139            }
140        }
141        Ok(())
142    }
143
144    fn append_offset(&mut self) -> Result<()> {
145        let next_offset: O = O::from_usize(self.value_buffer.len())
146            .ok_or_else(|| exec_datafusion_err!("byte array offset overflow"))?;
147        self.offsets_buffer.push(next_offset);
148        Ok(())
149    }
150
151    /// Finalize the builder into a concrete [`GenericStringArray<O>`].
152    ///
153    /// # Errors
154    ///
155    /// Returns an error when:
156    ///
157    /// - the provided `null_buffer` is not the same length as the `offsets_buffer`.
158    fn finish(self, null_buffer: Option<NullBuffer>) -> Result<ArrayRef> {
159        let row_count = self.offsets_buffer.len() / size_of::<O>() - 1;
160        if let Some(ref null_buffer) = null_buffer
161            && null_buffer.len() != row_count
162        {
163            return internal_err!(
164                "Null buffer and offsets buffer must be the same length"
165            );
166        }
167        let array_builder = ArrayDataBuilder::new(GenericStringArray::<O>::DATA_TYPE)
168            .len(row_count)
169            .add_buffer(self.offsets_buffer.into())
170            .add_buffer(self.value_buffer.into())
171            .nulls(null_buffer);
172        // SAFETY: all data that was appended was valid UTF8 and the values
173        // and offsets were created correctly
174        let array_data = unsafe { array_builder.build_unchecked() };
175        let array = GenericStringArray::<O>::from(array_data);
176        Ok(Arc::new(array))
177    }
178}
179
180/// Builder used by `concat`/`concat_ws` to assemble a [`StringViewArray`] one
181/// row at a time from multiple input columns.
182///
183/// Each row is written via repeated `write` calls (one per input
184/// fragment) followed by a single `append_offset` to commit the row
185/// as a single string view. The output null buffer is supplied by the caller
186/// at `finish` time, avoiding per-row NULL handling work.
187///
188/// For the common "produce one `&str` per row" pattern, prefer
189/// [`StringViewArrayBuilder`] instead.
190pub(crate) struct ConcatStringViewBuilder {
191    views: Vec<u128>,
192    data: Vec<u8>,
193    block: Vec<u8>,
194}
195
196impl ConcatStringViewBuilder {
197    pub fn with_capacity(item_capacity: usize, data_capacity: usize) -> Self {
198        Self {
199            views: Vec::with_capacity(item_capacity),
200            data: Vec::with_capacity(data_capacity),
201            block: vec![],
202        }
203    }
204}
205
206impl ConcatBuilder for ConcatStringViewBuilder {
207    fn write<const CHECK_VALID: bool>(
208        &mut self,
209        column: &ColumnarValueRef,
210        i: usize,
211    ) -> Result<()> {
212        match column {
213            ColumnarValueRef::Scalar(s) => {
214                std::str::from_utf8(s).map_err(|_| {
215                    exec_datafusion_err!("concat: scalar bytes are not valid UTF-8")
216                })?;
217                self.block.extend_from_slice(s);
218            }
219            ColumnarValueRef::NullableArray(array) => {
220                if !CHECK_VALID || array.is_valid(i) {
221                    self.block.extend_from_slice(array.value(i).as_bytes());
222                }
223            }
224            ColumnarValueRef::NullableLargeStringArray(array) => {
225                if !CHECK_VALID || array.is_valid(i) {
226                    self.block.extend_from_slice(array.value(i).as_bytes());
227                }
228            }
229            ColumnarValueRef::NullableStringViewArray(array) => {
230                if !CHECK_VALID || array.is_valid(i) {
231                    self.block.extend_from_slice(array.value(i).as_bytes());
232                }
233            }
234            ColumnarValueRef::NonNullableArray(array) => {
235                self.block.extend_from_slice(array.value(i).as_bytes());
236            }
237            ColumnarValueRef::NonNullableLargeStringArray(array) => {
238                self.block.extend_from_slice(array.value(i).as_bytes());
239            }
240            ColumnarValueRef::NonNullableStringViewArray(array) => {
241                self.block.extend_from_slice(array.value(i).as_bytes());
242            }
243            _ => {
244                return exec_err!(
245                    "concat: unexpected column type for string view builder: {column:?}"
246                );
247            }
248        }
249        Ok(())
250    }
251
252    /// Finalizes the current row by converting the accumulated data into a
253    /// StringView and appending it to the views buffer.
254    fn append_offset(&mut self) -> Result<()> {
255        let v = &self.block;
256        if v.len() > 12 {
257            let offset: u32 = self
258                .data
259                .len()
260                .try_into()
261                .map_err(|_| exec_datafusion_err!("byte array offset overflow"))?;
262            self.data.extend_from_slice(v);
263            self.views.push(make_view(v, 0, offset));
264        } else {
265            self.views.push(make_view(v, 0, 0));
266        }
267
268        self.block.clear();
269        Ok(())
270    }
271
272    /// Finalize the builder into a concrete [`StringViewArray`].
273    ///
274    /// # Errors
275    ///
276    /// Returns an error when:
277    ///
278    /// - the provided `null_buffer` length does not match the row count.
279    fn finish(self, null_buffer: Option<NullBuffer>) -> Result<ArrayRef> {
280        if let Some(ref nulls) = null_buffer
281            && nulls.len() != self.views.len()
282        {
283            return internal_err!(
284                "Null buffer length ({}) must match row count ({})",
285                nulls.len(),
286                self.views.len()
287            );
288        }
289
290        let buffers: Vec<Buffer> = if self.data.is_empty() {
291            vec![]
292        } else {
293            vec![Buffer::from(self.data)]
294        };
295
296        // SAFETY: views were constructed with correct lengths, offsets, and
297        // prefixes. All input fragments came from string arrays or string
298        // scalars, all of which are valid UTF-8.
299        let array = unsafe {
300            StringViewArray::new_unchecked(
301                ScalarBuffer::from(self.views),
302                buffers,
303                null_buffer,
304            )
305        };
306        Ok(Arc::new(array))
307    }
308}
309
310// ----------------------------------------------------------------------------
311// Bulk-nulls builders
312//
313// These builders are similar to Arrow's `GenericStringBuilder` and
314// `StringViewBuilder` but tuned for string UDFs along two axes:
315//
316//   * Bulk-NULL handling. The NULL bitmap is passed to `finish()` rather than
317//     maintained per-row. Many string UDFs can compute the bitmap in bulk,
318//     where this is significantly more efficient.
319//   * Closure-based row emission. Beyond `append_value(&str)`, the builders
320//     expose `append_with` (fragments written into the builder via a
321//     `StringWriter`) and `append_byte_map` (byte-to-byte mapping of an input
322//     slice), letting UDFs emit a row without first assembling it in a scratch
323//     `String`.
324// ----------------------------------------------------------------------------
325
326/// Builder for a [`GenericStringArray<O>`]. Instantiate with `O = i32` for
327/// [`StringArray`] (Utf8) or `O = i64` for [`LargeStringArray`] (LargeUtf8).
328pub(crate) struct GenericStringArrayBuilder<O: OffsetSizeTrait> {
329    offsets_buffer: MutableBuffer,
330    value_buffer: MutableBuffer,
331    placeholder_count: usize,
332    _phantom: PhantomData<O>,
333}
334
335fn offset_overflow_error<O: OffsetSizeTrait>() -> DataFusionError {
336    exec_datafusion_err!(
337        "byte array offset overflow: output size exceeds {} bytes",
338        O::MAX_OFFSET
339    )
340}
341
342fn string_view_overflow_error(field: &str) -> DataFusionError {
343    exec_datafusion_err!("byte array offset overflow: {field} exceeds i32::MAX")
344}
345
346fn try_offset<O: OffsetSizeTrait>(len: usize) -> Result<O> {
347    if len > O::MAX_OFFSET {
348        return Err(offset_overflow_error::<O>());
349    }
350    Ok(O::usize_as(len))
351}
352
353impl<O: OffsetSizeTrait> GenericStringArrayBuilder<O> {
354    pub fn with_capacity(item_capacity: usize, data_capacity: usize) -> Self {
355        let capacity = item_capacity
356            .checked_add(1)
357            .map(|i| i.saturating_mul(size_of::<O>()))
358            .expect("capacity integer overflow");
359
360        let mut offsets_buffer = MutableBuffer::with_capacity(capacity);
361        offsets_buffer.push(O::usize_as(0));
362        Self {
363            offsets_buffer,
364            value_buffer: MutableBuffer::with_capacity(data_capacity),
365            placeholder_count: 0,
366            _phantom: PhantomData,
367        }
368    }
369
370    #[inline]
371    fn try_push_offset_for_len(&mut self, len: usize) -> Result<()> {
372        let next_offset = try_offset::<O>(len)?;
373        self.offsets_buffer.push(next_offset);
374        Ok(())
375    }
376
377    #[inline]
378    fn try_append_bytes<F>(&mut self, additional_len: usize, append: F) -> Result<()>
379    where
380        F: FnOnce(&mut MutableBuffer),
381    {
382        let next_len = self
383            .value_buffer
384            .len()
385            .checked_add(additional_len)
386            .ok_or_else(offset_overflow_error::<O>)?;
387        let next_offset = try_offset::<O>(next_len)?;
388        append(&mut self.value_buffer);
389        debug_assert_eq!(self.value_buffer.len(), next_len);
390        self.offsets_buffer.push(next_offset);
391        Ok(())
392    }
393
394    /// Fallible variant of [`Self::append_value`].
395    ///
396    /// # Errors
397    ///
398    /// Returns an error if the cumulative byte length exceeds this builder's
399    /// offset type limit.
400    #[inline]
401    pub fn try_append_value(&mut self, value: &str) -> Result<()> {
402        self.try_append_bytes(value.len(), |value_buffer| {
403            value_buffer.extend_from_slice(value.as_bytes());
404        })
405    }
406
407    /// Fallible variant of [`Self::append_placeholder`].
408    ///
409    /// # Errors
410    ///
411    /// Returns an error if the current cumulative byte length exceeds this
412    /// builder's offset type limit.
413    #[inline]
414    pub fn try_append_placeholder(&mut self) -> Result<()> {
415        self.try_push_offset_for_len(self.value_buffer.len())?;
416        self.placeholder_count += 1;
417        Ok(())
418    }
419
420    /// See [`BulkNullStringArrayBuilder::append_value`].
421    ///
422    /// Note: new call sites that need recoverable overflow handling should
423    /// prefer [`Self::try_append_value`].
424    ///
425    /// # Panics
426    ///
427    /// Panics if the cumulative byte length exceeds `O::MAX`.
428    #[inline]
429    pub fn append_value(&mut self, value: &str) {
430        self.try_append_value(value)
431            .expect("byte array offset overflow");
432    }
433
434    /// See [`BulkNullStringArrayBuilder::append_placeholder`].
435    ///
436    /// Note: new call sites that need recoverable overflow handling should
437    /// prefer [`Self::try_append_placeholder`].
438    #[inline]
439    pub fn append_placeholder(&mut self) {
440        self.try_append_placeholder()
441            .expect("byte array offset overflow");
442    }
443
444    /// Fallible variant of [`Self::append_byte_map`].
445    ///
446    /// # Safety
447    ///
448    /// The bytes produced by applying `map` to each byte of `src`, in order,
449    /// must form valid UTF-8.
450    ///
451    /// # Errors
452    ///
453    /// Returns an error if the cumulative byte length exceeds this builder's
454    /// offset type limit.
455    #[inline]
456    pub unsafe fn try_append_byte_map<F: FnMut(u8) -> u8>(
457        &mut self,
458        src: &[u8],
459        mut map: F,
460    ) -> Result<()> {
461        self.try_append_bytes(src.len(), |value_buffer| {
462            value_buffer.extend(src.iter().map(|&b| map(b)));
463        })
464    }
465
466    /// See [`BulkNullStringArrayBuilder::append_byte_map`].
467    ///
468    /// Note: new call sites that need recoverable overflow handling should
469    /// prefer [`Self::try_append_byte_map`].
470    ///
471    /// # Safety
472    ///
473    /// The bytes produced by applying `map` to each byte of `src`, in order,
474    /// must form valid UTF-8.
475    ///
476    /// # Panics
477    ///
478    /// Panics if the cumulative byte length exceeds `O::MAX`.
479    #[inline]
480    pub unsafe fn append_byte_map<F: FnMut(u8) -> u8>(&mut self, src: &[u8], map: F) {
481        // SAFETY: caller upholds this method's UTF-8 contract.
482        unsafe { self.try_append_byte_map(src, map) }
483            .expect("byte array offset overflow");
484    }
485
486    /// Fallible variant of [`Self::append_with`].
487    ///
488    /// # Errors
489    ///
490    /// Returns an error if the cumulative byte length exceeds this builder's
491    /// offset type limit.
492    #[inline]
493    pub fn try_append_with<F>(&mut self, f: F) -> Result<()>
494    where
495        F: FnOnce(&mut GenericStringWriter<'_>),
496    {
497        let old_len = self.value_buffer.len();
498        let mut writer = GenericStringWriter {
499            value_buffer: &mut self.value_buffer,
500        };
501        f(&mut writer);
502        let next_offset = match try_offset::<O>(self.value_buffer.len()) {
503            Ok(offset) => offset,
504            Err(e) => {
505                // SAFETY: `old_len` was the initialized length before `f` wrote to
506                // this owned buffer, so shrinking back preserves initialized data.
507                unsafe { self.value_buffer.set_len(old_len) };
508                return Err(e);
509            }
510        };
511        self.offsets_buffer.push(next_offset);
512        Ok(())
513    }
514
515    /// See [`BulkNullStringArrayBuilder::append_with`].
516    ///
517    /// Note: new call sites that need recoverable overflow handling should
518    /// prefer [`Self::try_append_with`].
519    ///
520    /// # Panics
521    ///
522    /// Panics if the cumulative byte length exceeds `O::MAX`.
523    #[inline]
524    pub fn append_with<F>(&mut self, f: F)
525    where
526        F: FnOnce(&mut GenericStringWriter<'_>),
527    {
528        // Do not delegate to `try_append_with`: it rolls back value_buffer on
529        // overflow before returning Err, which would change this infallible
530        // method's state if its panic is caught.
531        let mut writer = GenericStringWriter {
532            value_buffer: &mut self.value_buffer,
533        };
534        f(&mut writer);
535        let next_offset =
536            O::from_usize(self.value_buffer.len()).expect("byte array offset overflow");
537        self.offsets_buffer.push(next_offset);
538    }
539
540    /// Finalize into a [`GenericStringArray<O>`] using the caller-supplied
541    /// null buffer.
542    ///
543    /// # Errors
544    ///
545    /// Returns an error when `null_buffer.len()` does not match the number of
546    /// appended rows.
547    pub fn finish(
548        self,
549        null_buffer: Option<NullBuffer>,
550    ) -> Result<GenericStringArray<O>> {
551        let row_count = self.offsets_buffer.len() / size_of::<O>() - 1;
552        if let Some(ref n) = null_buffer
553            && n.len() != row_count
554        {
555            return internal_err!(
556                "Null buffer length ({}) must match row count ({row_count})",
557                n.len()
558            );
559        }
560        let null_count = null_buffer.as_ref().map_or(0, |n| n.null_count());
561        debug_assert!(
562            null_count >= self.placeholder_count,
563            "{} placeholder rows but null buffer has {null_count} nulls",
564            self.placeholder_count,
565        );
566        let array_data = ArrayDataBuilder::new(GenericStringArray::<O>::DATA_TYPE)
567            .len(row_count)
568            .add_buffer(self.offsets_buffer.into())
569            .add_buffer(self.value_buffer.into())
570            .nulls(null_buffer);
571        // SAFETY: every appended value came from a `&str`, so the value
572        // buffer is valid UTF-8 and offsets are monotonically non-decreasing.
573        let array_data = unsafe { array_data.build_unchecked() };
574        Ok(GenericStringArray::<O>::from(array_data))
575    }
576}
577
578/// Starting size for the long-string data block used by `StringView`-style
579/// arrays; matches Arrow's `GenericByteViewBuilder` default.
580pub(crate) const STRING_VIEW_INIT_BLOCK_SIZE: u32 = 8 * 1024;
581/// Maximum size each long-string data block in a `StringView`-style array
582/// grows to; matches Arrow's `GenericByteViewBuilder` default.
583pub(crate) const STRING_VIEW_MAX_BLOCK_SIZE: u32 = 2 * 1024 * 1024;
584
585/// Append-only writer handed to closures passed to `append_with`.
586pub(crate) trait StringWriter {
587    fn write_str(&mut self, s: &str);
588    fn write_char(&mut self, c: char);
589}
590
591/// [`StringWriter`] for [`GenericStringArrayBuilder`]. Writes go straight to
592/// the value buffer.
593pub(crate) struct GenericStringWriter<'a> {
594    value_buffer: &'a mut MutableBuffer,
595}
596
597impl StringWriter for GenericStringWriter<'_> {
598    #[inline(always)]
599    fn write_str(&mut self, s: &str) {
600        push_bytes_to_mutable_buffer(self.value_buffer, s.as_bytes());
601    }
602
603    #[inline(always)]
604    fn write_char(&mut self, c: char) {
605        push_char_to_mutable_buffer(self.value_buffer, c);
606    }
607}
608
609/// Write `bytes` into `value_buffer`. For repeated small writes,
610/// MutableBuffer::extend_from_slice can be slow (memcpy per call), so we extend
611/// the buffer here directly and force inlining.
612#[inline(always)]
613fn push_bytes_to_mutable_buffer(value_buffer: &mut MutableBuffer, bytes: &[u8]) {
614    let n = bytes.len();
615    let old_len = value_buffer.len();
616    value_buffer.reserve(n);
617
618    // SAFETY: we reserved `n` bytes; the source and destination do not alias
619    // because `bytes` was passed in by the caller and `value_buffer` is owned.
620    unsafe {
621        let dst = value_buffer.as_mut_ptr().add(old_len);
622        let src = bytes.as_ptr();
623        match n {
624            0 => {}
625            1 => std::ptr::copy_nonoverlapping(src, dst, 1),
626            2 => std::ptr::copy_nonoverlapping(src, dst, 2),
627            3 => std::ptr::copy_nonoverlapping(src, dst, 3),
628            4 => std::ptr::copy_nonoverlapping(src, dst, 4),
629            5 => std::ptr::copy_nonoverlapping(src, dst, 5),
630            6 => std::ptr::copy_nonoverlapping(src, dst, 6),
631            7 => std::ptr::copy_nonoverlapping(src, dst, 7),
632            8 => std::ptr::copy_nonoverlapping(src, dst, 8),
633            _ => std::ptr::copy_nonoverlapping(src, dst, n),
634        }
635        value_buffer.set_len(old_len + n);
636    }
637}
638
639#[inline(always)]
640fn push_char_to_mutable_buffer(value_buffer: &mut MutableBuffer, c: char) {
641    let len = c.len_utf8();
642    let old_len = value_buffer.len();
643    value_buffer.reserve(len);
644
645    // SAFETY: we reserved `len` bytes above, write valid UTF-8 into those
646    // bytes, then update the initialized length to include them.
647    unsafe {
648        let dst = value_buffer.as_mut_ptr().add(old_len);
649        if len == 1 {
650            *dst = c as u8;
651        } else {
652            c.encode_utf8(std::slice::from_raw_parts_mut(dst, len));
653        }
654        value_buffer.set_len(old_len + len);
655    }
656}
657
658/// Builder for a [`StringViewArray`].
659///
660/// Short strings (≤ 12 bytes) are inlined into the view itself; long strings
661/// are appended into an in-progress data block. When the in-progress block
662/// fills up it is flushed into `completed` and a new block — double the size
663/// of the last, capped at [`STRING_VIEW_MAX_BLOCK_SIZE`] — is started.
664pub(crate) struct StringViewArrayBuilder {
665    views: Vec<u128>,
666    in_progress: Vec<u8>,
667    completed: Vec<Buffer>,
668    block_size: u32,
669    placeholder_count: usize,
670}
671
672impl StringViewArrayBuilder {
673    pub fn with_capacity(item_capacity: usize) -> Self {
674        Self {
675            views: Vec::with_capacity(item_capacity),
676            in_progress: Vec::new(),
677            completed: Vec::new(),
678            block_size: STRING_VIEW_INIT_BLOCK_SIZE,
679            placeholder_count: 0,
680        }
681    }
682
683    /// Doubles the block-size target and returns the new size.
684    fn next_block_size(&mut self) -> u32 {
685        if self.block_size < STRING_VIEW_MAX_BLOCK_SIZE {
686            self.block_size = self.block_size.saturating_mul(2);
687        }
688        self.block_size
689    }
690
691    /// Fallible variant of [`Self::append_value`].
692    ///
693    /// # Errors
694    ///
695    /// Returns an error if the value length, in-progress buffer offset, or
696    /// number of completed buffers exceeds `i32::MAX`. The ByteView spec uses
697    /// signed 32-bit integers for these fields; exceeding `i32::MAX` would
698    /// produce an array that does not round-trip through Arrow IPC (see
699    /// <https://github.com/apache/arrow-rs/issues/6172>).
700    #[inline]
701    pub fn try_append_value(&mut self, value: &str) -> Result<()> {
702        let v = value.as_bytes();
703        let length: u32 = i32::try_from(v.len())
704            .map_err(|_| string_view_overflow_error("value length"))?
705            as u32;
706        if length <= 12 {
707            self.views.push(make_view(v, 0, 0));
708            return Ok(());
709        }
710
711        self.try_ensure_long_capacity(length)?;
712
713        let offset: u32 = i32::try_from(self.in_progress.len())
714            .map_err(|_| string_view_overflow_error("offset"))?
715            as u32;
716        let buffer_index: u32 = i32::try_from(self.completed.len())
717            .map_err(|_| string_view_overflow_error("buffer count"))?
718            as u32;
719        self.in_progress.extend_from_slice(v);
720        self.views.push(Self::make_long_view_checked(
721            length,
722            buffer_index,
723            offset,
724            v,
725        ));
726        Ok(())
727    }
728
729    /// See [`BulkNullStringArrayBuilder::append_value`].
730    ///
731    /// Note: new call sites that need recoverable overflow handling should
732    /// prefer [`Self::try_append_value`].
733    ///
734    /// # Panics
735    ///
736    /// Panics under the same conditions that [`Self::try_append_value`] returns
737    /// an error.
738    #[inline]
739    pub fn append_value(&mut self, value: &str) {
740        self.try_append_value(value)
741            .expect("byte array offset overflow");
742    }
743
744    /// Fallible variant of [`Self::append_placeholder`].
745    ///
746    /// # Errors
747    ///
748    /// This currently cannot fail; it returns `Result` for API symmetry with
749    /// other fallible append methods.
750    #[inline]
751    pub fn try_append_placeholder(&mut self) -> Result<()> {
752        self.append_placeholder();
753        Ok(())
754    }
755
756    /// See [`BulkNullStringArrayBuilder::append_placeholder`].
757    ///
758    /// Note: new call sites that need recoverable overflow handling should
759    /// prefer [`Self::try_append_placeholder`].
760    #[inline]
761    pub fn append_placeholder(&mut self) {
762        // Zero-length inline view — `length` field is 0, no buffer ref.
763        self.views.push(0);
764        self.placeholder_count += 1;
765    }
766
767    /// Fallible variant of [`Self::ensure_long_capacity`].
768    #[inline]
769    fn try_ensure_long_capacity(&mut self, length: u32) -> Result<()> {
770        let required_cap = self
771            .in_progress
772            .len()
773            .checked_add(length as usize)
774            .ok_or_else(|| string_view_overflow_error("string view block size"))?;
775        if self.in_progress.capacity() < required_cap {
776            self.flush_in_progress();
777            let to_reserve = (length as usize).max(self.next_block_size() as usize);
778            #[expect(
779                clippy::disallowed_methods,
780                reason = "StringView's block size bounds growth, so reserve cannot overflow capacity arithmetically. This hot loop intentionally avoids the extra `try_reserve` checks. It remains subject to allocator failure/OOM, which must be managed externally."
781            )]
782            self.in_progress.reserve(to_reserve);
783        }
784        Ok(())
785    }
786
787    /// Ensure the in-progress block has room for `length` more bytes,
788    /// flushing the current block and starting a new (doubled) one if not.
789    /// Caller must invoke this only when no bytes of the current row are
790    /// yet in `in_progress` — flushing mid-row would orphan partial data.
791    #[inline]
792    fn ensure_long_capacity(&mut self, length: u32) {
793        self.try_ensure_long_capacity(length)
794            .expect("byte array offset overflow");
795    }
796
797    /// Encode a long-form view referencing `length` bytes already written
798    /// into the in-progress block at `offset`. `prefix_bytes` is the row's
799    /// data slice (or any slice starting with the row's first 4 bytes).
800    ///
801    /// Built inline rather than going through Arrow's `make_view`: that
802    /// function is `[inline(never)]` and has to handle short strings, so
803    /// building the view here ourselves is faster.
804    #[inline]
805    fn make_long_view_checked(
806        length: u32,
807        buffer_index: u32,
808        offset: u32,
809        prefix_bytes: &[u8],
810    ) -> u128 {
811        ByteView {
812            length,
813            // length > 12, so prefix_bytes has at least 4 bytes.
814            prefix: u32::from_le_bytes(prefix_bytes[..4].try_into().unwrap()),
815            buffer_index,
816            offset,
817        }
818        .into()
819    }
820
821    #[inline]
822    fn make_long_view(&self, length: u32, offset: u32, prefix_bytes: &[u8]) -> u128 {
823        let buffer_index: u32 = i32::try_from(self.completed.len())
824            .expect("buffer count exceeds i32::MAX")
825            as u32;
826        Self::make_long_view_checked(length, buffer_index, offset, prefix_bytes)
827    }
828
829    /// See [`BulkNullStringArrayBuilder::append_byte_map`].
830    ///
831    /// # Safety
832    ///
833    /// The bytes produced by applying `map` to each byte of `src`, in order,
834    /// must form valid UTF-8.
835    ///
836    /// # Panics
837    ///
838    /// Panics under the same conditions as [`Self::append_value`]: if
839    /// `src.len()`, the in-progress buffer offset, or the number of completed
840    /// buffers exceeds `i32::MAX`.
841    #[inline]
842    pub unsafe fn append_byte_map<F: FnMut(u8) -> u8>(&mut self, src: &[u8], mut map: F) {
843        let length: u32 =
844            i32::try_from(src.len()).expect("value length exceeds i32::MAX") as u32;
845        if length <= 12 {
846            let mut bytes = [0u8; 12];
847            for (d, &b) in bytes[..src.len()].iter_mut().zip(src) {
848                *d = map(b);
849            }
850            self.views.push(make_view(&bytes[..src.len()], 0, 0));
851            return;
852        }
853
854        self.ensure_long_capacity(length);
855
856        let cursor = self.in_progress.len();
857        let offset: u32 = i32::try_from(cursor).expect("offset exceeds i32::MAX") as u32;
858        self.in_progress.extend(src.iter().map(|&b| map(b)));
859        self.views
860            .push(self.make_long_view(length, offset, &self.in_progress[cursor..]));
861    }
862
863    /// See [`BulkNullStringArrayBuilder::append_with`].
864    ///
865    /// # Panics
866    ///
867    /// Panics under the same conditions as [`Self::append_value`]: if the
868    /// row's byte length, the in-progress buffer offset, or the number of
869    /// completed buffers exceeds `i32::MAX`.
870    #[inline]
871    pub fn append_with<F>(&mut self, f: F)
872    where
873        F: FnOnce(&mut StringViewWriter<'_>),
874    {
875        let mut writer = StringViewWriter {
876            inline_buf: [0u8; 12],
877            inline_len: 0,
878            spill_cursor: None,
879            builder: self,
880        };
881        f(&mut writer);
882        // Destructure to release the borrow on `self` and pull out the
883        // inline-buffer state by-value. Copy types only; the &mut self is
884        // dropped here, ending the borrow.
885        let StringViewWriter {
886            inline_buf,
887            inline_len,
888            spill_cursor,
889            ..
890        } = writer;
891
892        match spill_cursor {
893            None => {
894                self.views
895                    .push(make_view(&inline_buf[..inline_len as usize], 0, 0));
896            }
897            Some(start) => {
898                let end = self.in_progress.len();
899                let length: u32 = i32::try_from(end - start)
900                    .expect("value length exceeds i32::MAX")
901                    as u32;
902                let offset: u32 =
903                    i32::try_from(start).expect("offset exceeds i32::MAX") as u32;
904                self.views.push(self.make_long_view(
905                    length,
906                    offset,
907                    &self.in_progress[start..],
908                ));
909            }
910        }
911    }
912
913    fn flush_in_progress(&mut self) {
914        if !self.in_progress.is_empty() {
915            let block = std::mem::take(&mut self.in_progress);
916            self.completed.push(Buffer::from_vec(block));
917        }
918    }
919
920    /// Finalize into a [`StringViewArray`] using the caller-supplied null
921    /// buffer.
922    ///
923    /// # Errors
924    ///
925    /// Returns an error when `null_buffer.len()` does not match the number of
926    /// appended rows.
927    pub fn finish(mut self, null_buffer: Option<NullBuffer>) -> Result<StringViewArray> {
928        if let Some(ref n) = null_buffer
929            && n.len() != self.views.len()
930        {
931            return internal_err!(
932                "Null buffer length ({}) must match row count ({})",
933                n.len(),
934                self.views.len()
935            );
936        }
937        let null_count = null_buffer.as_ref().map_or(0, |n| n.null_count());
938        debug_assert!(
939            null_count >= self.placeholder_count,
940            "{} placeholder rows but null buffer has {null_count} nulls",
941            self.placeholder_count,
942        );
943        self.flush_in_progress();
944        // SAFETY: every long-string view references bytes we wrote ourselves
945        // into `self.completed`, with prefixes derived from those same bytes.
946        // Inline views were built from valid `&str`. Placeholder views are
947        // zero-length with no buffer reference.
948        let array = unsafe {
949            StringViewArray::new_unchecked(
950                ScalarBuffer::from(self.views),
951                self.completed,
952                null_buffer,
953            )
954        };
955        Ok(array)
956    }
957}
958
959/// [`StringWriter`] for [`StringViewArrayBuilder`].
960///
961/// The writer accumulates the first up-to-12 bytes of a row in a stack
962/// buffer; if the row stays inline-sized, it never touches the data block.
963/// On the first write that would exceed 12 bytes, the stack buffer is
964/// spilled into the builder's in-progress block and subsequent writes go
965/// directly there.
966pub(crate) struct StringViewWriter<'a> {
967    inline_buf: [u8; 12],
968    inline_len: u8,
969    /// `None` while the row fits inline; becomes `Some(start)` (offset of
970    /// the row's first byte in `in_progress`) at first spill.
971    spill_cursor: Option<usize>,
972    builder: &'a mut StringViewArrayBuilder,
973}
974
975impl StringWriter for StringViewWriter<'_> {
976    #[inline]
977    fn write_str(&mut self, s: &str) {
978        let bytes = s.as_bytes();
979        if self.spill_cursor.is_some() {
980            self.builder.in_progress.extend_from_slice(bytes);
981            return;
982        }
983
984        let inline_len = self.inline_len as usize;
985        let new_len = inline_len + bytes.len();
986        if new_len <= 12 {
987            self.inline_buf[inline_len..new_len].copy_from_slice(bytes);
988            self.inline_len = new_len as u8;
989            return;
990        }
991
992        // First spill of this row: `ensure_long_capacity` may flush the
993        // current block, which is safe because no row-data for this row
994        // is in it yet — the inline prefix is still in `inline_buf`.
995        self.builder.ensure_long_capacity(new_len as u32);
996        let cursor = self.builder.in_progress.len();
997        self.builder
998            .in_progress
999            .extend_from_slice(&self.inline_buf[..inline_len]);
1000        self.builder.in_progress.extend_from_slice(bytes);
1001        self.spill_cursor = Some(cursor);
1002    }
1003
1004    #[inline]
1005    fn write_char(&mut self, c: char) {
1006        let len = c.len_utf8();
1007        if self.spill_cursor.is_some() {
1008            push_char_to_vec(&mut self.builder.in_progress, c);
1009            return;
1010        }
1011
1012        let inline_len = self.inline_len as usize;
1013        let new_len = inline_len + len;
1014        if new_len <= 12 {
1015            c.encode_utf8(&mut self.inline_buf[inline_len..new_len]);
1016            self.inline_len = new_len as u8;
1017            return;
1018        }
1019
1020        self.builder.ensure_long_capacity(new_len as u32);
1021        let cursor = self.builder.in_progress.len();
1022        self.builder
1023            .in_progress
1024            .extend_from_slice(&self.inline_buf[..inline_len]);
1025        push_char_to_vec(&mut self.builder.in_progress, c);
1026        self.spill_cursor = Some(cursor);
1027    }
1028}
1029
1030#[inline]
1031fn push_char_to_vec(v: &mut Vec<u8>, c: char) {
1032    let mut buf = [0u8; 4];
1033    v.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
1034}
1035
1036/// Trait abstracting over the bulk-NULL string array builders.
1037///
1038/// Similar to Arrow's `StringLikeArrayBuilder`, this allows generic dispatch
1039/// over the three string array types (Utf8, LargeUtf8, Utf8View) when the
1040/// function body is uniform across them.
1041///
1042/// Three methods append a non-null row; which method to pick depends on how the
1043/// row is produced:
1044///
1045/// - [`append_value`](Self::append_value) pushes an already-finished `&str`.
1046///   Use it when the row is forwarded from an existing slice (e.g. an input
1047///   column) — there is nothing to elide.
1048/// - [`append_byte_map`](Self::append_byte_map) emits a row whose bytes are a
1049///   byte-to-byte mapping of an input slice. Output length is known up front
1050///   and the inner loop is straight-line, so this is the fastest path when the
1051///   shape fits.
1052/// - [`append_with`](Self::append_with) emits a row by feeding fragments to a
1053///   [`StringWriter`]. Use it when the row is computed from multiple sources or
1054///   when the output length is not known up front. Bytes are written directly
1055///   into the builder, so it is typically faster than assembling a `String` and
1056///   calling `append_value(&scratch)`.
1057///
1058/// For a NULL row, call [`append_placeholder`](Self::append_placeholder) to
1059/// advance the row count without writing into the value buffer; the caller MUST
1060/// clear the corresponding bit in the null buffer passed to
1061/// [`finish`](Self::finish).
1062pub(crate) trait BulkNullStringArrayBuilder {
1063    /// Per-builder concrete writer type, exposed as a GAT so generic callers
1064    /// can use the inherent (non-`dyn`) writer methods without vtable
1065    /// dispatch.
1066    type Writer<'a>: StringWriter
1067    where
1068        Self: 'a;
1069
1070    /// Append `value` as the next row.
1071    ///
1072    /// # Panics
1073    ///
1074    /// Panics if the resulting array would exceed the per-implementation
1075    /// size limit. See the inherent method on each builder for specifics.
1076    fn append_value(&mut self, value: &str);
1077
1078    /// Append an empty placeholder row. The corresponding slot MUST be masked
1079    /// as null by the null buffer passed to [`finish`](Self::finish).
1080    fn append_placeholder(&mut self);
1081
1082    /// Append a row whose bytes are produced by `f` calling write methods on
1083    /// the supplied [`StringWriter`].
1084    ///
1085    /// The closure can call `write_str` or `write_char` on the supplied
1086    /// `StringWriter` zero or more times. Zero calls produces a row containing
1087    /// the empty string.
1088    ///
1089    /// # Panics
1090    ///
1091    /// See [`append_value`](Self::append_value).
1092    fn append_with<F>(&mut self, f: F)
1093    where
1094        F: for<'a> FnOnce(&mut Self::Writer<'a>);
1095
1096    /// Append a row whose bytes are produced by mapping each byte of `src`
1097    /// through `map`, in order. Output length equals `src.len()`.
1098    ///
1099    /// Because the output length is known up front and the inner loop is
1100    /// straight-line, this is more efficient than
1101    /// [`append_with`](Self::append_with) for byte-to-byte mappings and
1102    /// autovectorizes well.
1103    ///
1104    /// # Safety
1105    ///
1106    /// The bytes produced by applying `map` to each byte of `src`, in order,
1107    /// must form valid UTF-8.
1108    ///
1109    /// # Panics
1110    ///
1111    /// See [`append_value`](Self::append_value).
1112    unsafe fn append_byte_map<F: FnMut(u8) -> u8>(&mut self, src: &[u8], map: F);
1113
1114    /// Finalize into a concrete array using the caller-supplied null buffer.
1115    ///
1116    /// # Errors
1117    ///
1118    /// Returns an error when `null_buffer.len()` does not match the number
1119    /// of appended rows.
1120    fn finish(self, nulls: Option<NullBuffer>) -> Result<ArrayRef>;
1121}
1122
1123impl<O: OffsetSizeTrait> BulkNullStringArrayBuilder for GenericStringArrayBuilder<O> {
1124    type Writer<'a> = GenericStringWriter<'a>;
1125
1126    #[inline]
1127    fn append_value(&mut self, value: &str) {
1128        GenericStringArrayBuilder::<O>::append_value(self, value)
1129    }
1130    #[inline]
1131    fn append_placeholder(&mut self) {
1132        GenericStringArrayBuilder::<O>::append_placeholder(self)
1133    }
1134    #[inline]
1135    fn append_with<F>(&mut self, f: F)
1136    where
1137        F: for<'a> FnOnce(&mut Self::Writer<'a>),
1138    {
1139        GenericStringArrayBuilder::<O>::append_with(self, f)
1140    }
1141    #[inline]
1142    unsafe fn append_byte_map<F: FnMut(u8) -> u8>(&mut self, src: &[u8], map: F) {
1143        // SAFETY: contract forwarded.
1144        unsafe { GenericStringArrayBuilder::<O>::append_byte_map(self, src, map) }
1145    }
1146    fn finish(self, nulls: Option<NullBuffer>) -> Result<ArrayRef> {
1147        Ok(Arc::new(GenericStringArrayBuilder::<O>::finish(
1148            self, nulls,
1149        )?))
1150    }
1151}
1152
1153impl BulkNullStringArrayBuilder for StringViewArrayBuilder {
1154    type Writer<'a> = StringViewWriter<'a>;
1155
1156    #[inline]
1157    fn append_value(&mut self, value: &str) {
1158        StringViewArrayBuilder::append_value(self, value)
1159    }
1160    #[inline]
1161    fn append_placeholder(&mut self) {
1162        StringViewArrayBuilder::append_placeholder(self)
1163    }
1164    #[inline]
1165    fn append_with<F>(&mut self, f: F)
1166    where
1167        F: for<'a> FnOnce(&mut Self::Writer<'a>),
1168    {
1169        StringViewArrayBuilder::append_with(self, f)
1170    }
1171    #[inline]
1172    unsafe fn append_byte_map<F: FnMut(u8) -> u8>(&mut self, src: &[u8], map: F) {
1173        // SAFETY: contract forwarded.
1174        unsafe { StringViewArrayBuilder::append_byte_map(self, src, map) }
1175    }
1176    fn finish(self, nulls: Option<NullBuffer>) -> Result<ArrayRef> {
1177        Ok(Arc::new(StringViewArrayBuilder::finish(self, nulls)?))
1178    }
1179}
1180
1181/// Append a new view to the views buffer with the given substr.
1182///
1183/// Callers are responsible for their own null tracking.
1184///
1185/// # Safety
1186///
1187/// original_view must be a valid view (the format described on
1188/// [`GenericByteViewArray`](arrow::array::GenericByteViewArray).
1189///
1190/// # Arguments
1191/// - views_buffer: The buffer to append the new view to
1192/// - original_view: The original view value
1193/// - substr: The substring to append. Must be a valid substring of the original view
1194/// - start_offset: The start offset of the substring in the view
1195///
1196/// LLVM is apparently overly eager to inline this function into some hot loops,
1197/// which bloats them and regresses performance, so we disable inlining for now.
1198#[inline(never)]
1199pub(crate) fn append_view(
1200    views_buffer: &mut Vec<u128>,
1201    original_view: &u128,
1202    substr: &str,
1203    start_offset: u32,
1204) {
1205    let substr_len = substr.len();
1206    let sub_view = if substr_len > 12 {
1207        let view = ByteView::from(*original_view);
1208        make_view(
1209            substr.as_bytes(),
1210            view.buffer_index,
1211            view.offset + start_offset,
1212        )
1213    } else {
1214        make_view(substr.as_bytes(), 0, 0)
1215    };
1216    views_buffer.push(sub_view);
1217}
1218
1219#[derive(Debug)]
1220pub(crate) enum ColumnarValueRef<'a> {
1221    Scalar(&'a [u8]),
1222    NullableArray(&'a StringArray),
1223    NonNullableArray(&'a StringArray),
1224    NullableLargeStringArray(&'a LargeStringArray),
1225    NonNullableLargeStringArray(&'a LargeStringArray),
1226    NullableStringViewArray(&'a StringViewArray),
1227    NonNullableStringViewArray(&'a StringViewArray),
1228    NullableBinaryArray(&'a BinaryArray),
1229    NonNullableBinaryArray(&'a BinaryArray),
1230    NullableLargeBinaryArray(&'a LargeBinaryArray),
1231    NonNullableLargeBinaryArray(&'a LargeBinaryArray),
1232    NullableBinaryViewArray(&'a BinaryViewArray),
1233    NonNullableBinaryViewArray(&'a BinaryViewArray),
1234}
1235
1236impl ColumnarValueRef<'_> {
1237    #[inline]
1238    pub fn is_valid(&self, i: usize) -> bool {
1239        match &self {
1240            Self::Scalar(_)
1241            | Self::NonNullableArray(_)
1242            | Self::NonNullableLargeStringArray(_)
1243            | Self::NonNullableStringViewArray(_)
1244            | Self::NonNullableBinaryArray(_)
1245            | Self::NonNullableLargeBinaryArray(_)
1246            | Self::NonNullableBinaryViewArray(_) => true,
1247            Self::NullableArray(array) => array.is_valid(i),
1248            Self::NullableStringViewArray(array) => array.is_valid(i),
1249            Self::NullableLargeStringArray(array) => array.is_valid(i),
1250            Self::NullableBinaryArray(array) => array.is_valid(i),
1251            Self::NullableLargeBinaryArray(array) => array.is_valid(i),
1252            Self::NullableBinaryViewArray(array) => array.is_valid(i),
1253        }
1254    }
1255
1256    #[inline]
1257    pub fn nulls(&self) -> Option<NullBuffer> {
1258        match &self {
1259            Self::Scalar(_)
1260            | Self::NonNullableArray(_)
1261            | Self::NonNullableStringViewArray(_)
1262            | Self::NonNullableLargeStringArray(_)
1263            | Self::NonNullableBinaryArray(_)
1264            | Self::NonNullableLargeBinaryArray(_)
1265            | Self::NonNullableBinaryViewArray(_) => None,
1266            Self::NullableArray(array) => array.nulls().cloned(),
1267            Self::NullableStringViewArray(array) => array.nulls().cloned(),
1268            Self::NullableLargeStringArray(array) => array.nulls().cloned(),
1269            Self::NullableBinaryArray(array) => array.nulls().cloned(),
1270            Self::NullableLargeBinaryArray(array) => array.nulls().cloned(),
1271            Self::NullableBinaryViewArray(array) => array.nulls().cloned(),
1272        }
1273    }
1274
1275    /// Parse a [`ColumnarValue`] argument into `ColumnarValueRef`.
1276    /// Returns `None` when the argument is null or null scalar
1277    /// Returns an error when a columnar value type is not supported.
1278    /// Shared by `concat` and `concat_ws`.
1279    pub(crate) fn from_columnar_value<'a>(
1280        col: &'a ColumnarValue,
1281        data_size: &mut usize,
1282        len: usize,
1283        size_factor: usize,
1284        convert_to_str: bool,
1285    ) -> Result<Option<ColumnarValueRef<'a>>> {
1286        match col {
1287            ColumnarValue::Scalar(ScalarValue::Utf8(maybe_value))
1288            | ColumnarValue::Scalar(ScalarValue::LargeUtf8(maybe_value))
1289            | ColumnarValue::Scalar(ScalarValue::Utf8View(maybe_value)) => {
1290                if let Some(s) = maybe_value {
1291                    *data_size += s.len() * len * size_factor;
1292                    Ok(Some(ColumnarValueRef::Scalar(s.as_bytes())))
1293                } else {
1294                    Ok(None)
1295                }
1296            }
1297            ColumnarValue::Scalar(ScalarValue::Binary(maybe_value))
1298            | ColumnarValue::Scalar(ScalarValue::LargeBinary(maybe_value))
1299            | ColumnarValue::Scalar(ScalarValue::BinaryView(maybe_value))
1300            | ColumnarValue::Scalar(ScalarValue::FixedSizeBinary(_, maybe_value)) => {
1301                if let Some(b) = maybe_value {
1302                    *data_size += b.len() * len * size_factor;
1303                    Ok(Some(ColumnarValueRef::Scalar(b.as_slice())))
1304                } else {
1305                    Ok(None)
1306                }
1307            }
1308            ColumnarValue::Scalar(scalar) if scalar.is_null() => {
1309                // null scalar is skipped
1310                Ok(None)
1311            }
1312            ColumnarValue::Scalar(scalar) if convert_to_str => {
1313                match scalar.try_as_str() {
1314                    Some(Some(s)) => {
1315                        *data_size += s.len() * len * size_factor;
1316                        Ok(Some(ColumnarValueRef::Scalar(s.as_bytes())))
1317                    }
1318                    Some(None) => unreachable!("null handled above"),
1319                    None => {
1320                        internal_err!("Expected string or binary, got {scalar:?}")
1321                    }
1322                }
1323            }
1324            ColumnarValue::Array(array) => match array.data_type() {
1325                DataType::Utf8 => {
1326                    let string_array = as_string_array(array)?;
1327                    *data_size += string_array.values().len() * size_factor;
1328                    let column = if array.is_nullable() {
1329                        ColumnarValueRef::NullableArray(string_array)
1330                    } else {
1331                        ColumnarValueRef::NonNullableArray(string_array)
1332                    };
1333                    Ok(Some(column))
1334                }
1335                DataType::LargeUtf8 => {
1336                    let string_array = as_largestring_array(array);
1337                    *data_size += string_array.values().len() * size_factor;
1338                    let column = if array.is_nullable() {
1339                        ColumnarValueRef::NullableLargeStringArray(string_array)
1340                    } else {
1341                        ColumnarValueRef::NonNullableLargeStringArray(string_array)
1342                    };
1343                    Ok(Some(column))
1344                }
1345                DataType::Utf8View => {
1346                    let string_array = as_string_view_array(array)?;
1347                    *data_size += string_array.total_buffer_bytes_used() * size_factor;
1348                    let column = if array.is_nullable() {
1349                        ColumnarValueRef::NullableStringViewArray(string_array)
1350                    } else {
1351                        ColumnarValueRef::NonNullableStringViewArray(string_array)
1352                    };
1353                    Ok(Some(column))
1354                }
1355                DataType::Binary => {
1356                    let binary_array = as_binary_array(array)?;
1357                    *data_size += binary_array.values().len() * size_factor;
1358                    let column = if array.is_nullable() {
1359                        ColumnarValueRef::NullableBinaryArray(binary_array)
1360                    } else {
1361                        ColumnarValueRef::NonNullableBinaryArray(binary_array)
1362                    };
1363                    Ok(Some(column))
1364                }
1365                DataType::LargeBinary => {
1366                    let binary_array = as_large_binary_array(array)?;
1367                    *data_size += binary_array.values().len() * size_factor;
1368                    let column = if array.is_nullable() {
1369                        ColumnarValueRef::NullableLargeBinaryArray(binary_array)
1370                    } else {
1371                        ColumnarValueRef::NonNullableLargeBinaryArray(binary_array)
1372                    };
1373                    Ok(Some(column))
1374                }
1375                DataType::BinaryView => {
1376                    let binary_array = as_binary_view_array(array)?;
1377                    *data_size += binary_array.total_buffer_bytes_used() * size_factor;
1378                    let column = if array.is_nullable() {
1379                        ColumnarValueRef::NullableBinaryViewArray(binary_array)
1380                    } else {
1381                        ColumnarValueRef::NonNullableBinaryViewArray(binary_array)
1382                    };
1383                    Ok(Some(column))
1384                }
1385                other => {
1386                    plan_err!(
1387                        "Input was {other} which is not a supported datatype for concat function"
1388                    )
1389                }
1390            },
1391            _ => {
1392                plan_err!(
1393                    "Input was {col} which is not a supported datatype for concat function"
1394                )
1395            }
1396        }
1397    }
1398}
1399
1400/// Return the widest binary type found in `types`.
1401/// Order: `BinaryView` > `LargeBinary` / `FixedSizeBinary` > `Binary`.
1402pub(crate) fn widest_binary_type(types: &[DataType]) -> DataType {
1403    if types.iter().any(|t| matches!(t, DataType::BinaryView)) {
1404        DataType::BinaryView
1405    } else if types
1406        .iter()
1407        .any(|t| matches!(t, DataType::LargeBinary | DataType::FixedSizeBinary(_)))
1408    {
1409        DataType::LargeBinary
1410    } else {
1411        DataType::Binary
1412    }
1413}
1414
1415/// Return the widest string type found in `types`.
1416/// Order: `Utf8View` > `LargeUtf8` > `Utf8`.
1417pub(crate) fn widest_string_type(types: &[DataType]) -> DataType {
1418    if types.iter().any(|t| matches!(t, DataType::Utf8View)) {
1419        DataType::Utf8View
1420    } else if types.iter().any(|t| matches!(t, DataType::LargeUtf8)) {
1421        DataType::LargeUtf8
1422    } else {
1423        DataType::Utf8
1424    }
1425}
1426
1427#[cfg(test)]
1428mod tests {
1429    use super::*;
1430
1431    /// Run `scenario` against `builder`, finish with a null buffer derived
1432    /// from `expected` (a bit is set wherever `expected[i].is_some()`), and
1433    /// assert the resulting array equals the corresponding
1434    /// `*Array::from(expected)`.
1435    ///
1436    /// The caller is responsible for driving NULLs in `scenario` — usually
1437    /// by calling `append_placeholder` at each index where `expected[i]` is
1438    /// `None`.
1439    fn run_scenario<B, F>(mut builder: B, expected: &[Option<&str>], scenario: F)
1440    where
1441        B: BulkNullStringArrayBuilder,
1442        F: FnOnce(&mut B),
1443    {
1444        scenario(&mut builder);
1445        let bits: Vec<bool> = expected.iter().map(|x| x.is_some()).collect();
1446        let nulls = if bits.iter().any(|v| !v) {
1447            Some(NullBuffer::from(bits))
1448        } else {
1449            None
1450        };
1451        let array = builder.finish(nulls).unwrap();
1452        let owned: Vec<Option<&str>> = expected.to_vec();
1453        if let Some(a) = array.as_any().downcast_ref::<StringArray>() {
1454            assert_eq!(a, &StringArray::from(owned));
1455        } else if let Some(a) = array.as_any().downcast_ref::<LargeStringArray>() {
1456            assert_eq!(a, &LargeStringArray::from(owned));
1457        } else if let Some(a) = array.as_any().downcast_ref::<StringViewArray>() {
1458            assert_eq!(a, &StringViewArray::from(owned));
1459        } else {
1460            panic!("unexpected array type");
1461        }
1462    }
1463
1464    /// Run `$scenario` against all three bulk-null builders, asserting each
1465    /// produces an array equivalent to `$expected`. `$scenario` is a closure
1466    /// `|builder| { ... }`; it is duplicated syntactically at each call site
1467    /// so the `BulkNullStringArrayBuilder::Writer` GAT can specialize per
1468    /// builder.
1469    macro_rules! check_on_all_builders {
1470        ($expected:expr, $scenario:expr $(,)?) => {{
1471            let expected = $expected;
1472            run_scenario(
1473                GenericStringArrayBuilder::<i32>::with_capacity(0, 0),
1474                expected,
1475                $scenario,
1476            );
1477            run_scenario(
1478                GenericStringArrayBuilder::<i64>::with_capacity(0, 0),
1479                expected,
1480                $scenario,
1481            );
1482            run_scenario(
1483                StringViewArrayBuilder::with_capacity(0),
1484                expected,
1485                $scenario,
1486            );
1487        }};
1488    }
1489
1490    fn assert_finish_errs_on_length_mismatch<B>(mut builder: B)
1491    where
1492        B: BulkNullStringArrayBuilder,
1493    {
1494        builder.append_value("a");
1495        builder.append_value("b");
1496        let nulls = NullBuffer::from(vec![true, false, true]);
1497        assert!(builder.finish(Some(nulls)).is_err());
1498    }
1499
1500    #[test]
1501    #[should_panic(expected = "capacity integer overflow")]
1502    fn test_overflow_concat_string_builder() {
1503        let _builder = ConcatStringBuilder::with_capacity(usize::MAX, usize::MAX);
1504    }
1505
1506    #[test]
1507    #[should_panic(expected = "capacity integer overflow")]
1508    fn test_overflow_concat_large_string_builder() {
1509        let _builder = ConcatLargeStringBuilder::with_capacity(usize::MAX, usize::MAX);
1510    }
1511
1512    #[test]
1513    fn bulk_append_value_with_nulls() {
1514        check_on_all_builders!(
1515            &[
1516                Some("a string longer than twelve bytes"),
1517                None,
1518                Some("short"),
1519                None,
1520            ],
1521            |b| {
1522                b.append_value("a string longer than twelve bytes");
1523                b.append_placeholder();
1524                b.append_value("short");
1525                b.append_placeholder();
1526            },
1527        );
1528    }
1529
1530    #[test]
1531    fn bulk_empty_builder() {
1532        check_on_all_builders!(&[], |_b| {});
1533    }
1534
1535    #[test]
1536    fn bulk_all_placeholders() {
1537        check_on_all_builders!(&[None, None, None], |b| {
1538            b.append_placeholder();
1539            b.append_placeholder();
1540            b.append_placeholder();
1541        });
1542    }
1543
1544    #[test]
1545    fn bulk_append_value_no_nulls() {
1546        check_on_all_builders!(
1547            &[
1548                Some("foo"),
1549                Some(""),
1550                Some("a string longer than twelve bytes")
1551            ],
1552            |b| {
1553                b.append_value("foo");
1554                b.append_value("");
1555                b.append_value("a string longer than twelve bytes");
1556            },
1557        );
1558    }
1559
1560    #[test]
1561    fn bulk_append_with() {
1562        check_on_all_builders!(
1563            &[
1564                Some("hello"),
1565                None,
1566                Some("hello world"),
1567                Some("a long string of 25 bytes"),
1568                Some(""),
1569            ],
1570            |b| {
1571                b.append_with(|w| w.write_str("hello"));
1572                b.append_placeholder();
1573                b.append_with(|w| {
1574                    w.write_str("hello ");
1575                    w.write_str("world");
1576                });
1577                b.append_with(|w| w.write_str("a long string of 25 bytes"));
1578                b.append_with(|_w| {});
1579            },
1580        );
1581    }
1582
1583    #[test]
1584    fn bulk_append_with_chars() {
1585        check_on_all_builders!(&[Some("hé!"), Some("x")], |b| {
1586            b.append_with(|w| {
1587                w.write_char('h');
1588                w.write_char('é');
1589                w.write_char('!');
1590            });
1591            b.append_with(|w| w.write_char('x'));
1592        });
1593    }
1594
1595    #[test]
1596    fn bulk_append_byte_map() {
1597        // SAFETY: ASCII inputs and ASCII outputs in every call.
1598        check_on_all_builders!(&[Some("HELLO"), Some("aXcaX"), Some("")], |b| unsafe {
1599            b.append_byte_map(b"hello", |x| x.to_ascii_uppercase());
1600            b.append_byte_map(b"abcab", |x| if x == b'b' { b'X' } else { x });
1601            b.append_byte_map(b"", |x| x);
1602        },);
1603    }
1604
1605    #[test]
1606    fn bulk_finish_errors_on_null_buffer_length_mismatch() {
1607        assert_finish_errs_on_length_mismatch(
1608            GenericStringArrayBuilder::<i32>::with_capacity(2, 4),
1609        );
1610        assert_finish_errs_on_length_mismatch(
1611            GenericStringArrayBuilder::<i64>::with_capacity(2, 4),
1612        );
1613        assert_finish_errs_on_length_mismatch(StringViewArrayBuilder::with_capacity(2));
1614    }
1615
1616    #[test]
1617    fn generic_string_builder_try_append_success_path() {
1618        let mut builder = GenericStringArrayBuilder::<i32>::with_capacity(4, 16);
1619        builder.try_append_value("abc").unwrap();
1620        builder.try_append_placeholder().unwrap();
1621        // SAFETY: ASCII input and output.
1622        unsafe {
1623            builder
1624                .try_append_byte_map(b"de", |b| b.to_ascii_uppercase())
1625                .unwrap();
1626        }
1627        builder
1628            .try_append_with(|w| {
1629                w.write_str("f");
1630                w.write_char('é');
1631            })
1632            .unwrap();
1633
1634        let nulls = Some(NullBuffer::from(vec![true, false, true, true]));
1635        let array = builder.finish(nulls).unwrap();
1636        assert_eq!(
1637            &array,
1638            &StringArray::from(vec![Some("abc"), None, Some("DE"), Some("fé")])
1639        );
1640    }
1641
1642    #[test]
1643    fn generic_string_builder_mixed_append_success_path() {
1644        let mut builder = GenericStringArrayBuilder::<i32>::with_capacity(4, 16);
1645        builder.append_value("ab");
1646        builder.try_append_value("cd").unwrap();
1647        // SAFETY: ASCII input and output.
1648        unsafe {
1649            builder.append_byte_map(b"ef", |b| b.to_ascii_uppercase());
1650            builder
1651                .try_append_byte_map(b"gh", |b| b.to_ascii_uppercase())
1652                .unwrap();
1653        }
1654
1655        let array = builder.finish(None).unwrap();
1656        assert_eq!(
1657            &array,
1658            &StringArray::from(vec![Some("ab"), Some("cd"), Some("EF"), Some("GH")])
1659        );
1660    }
1661
1662    #[test]
1663    fn string_view_builder_try_append_success_path() {
1664        let mut builder = StringViewArrayBuilder::with_capacity(3);
1665        builder.try_append_value("abc").unwrap();
1666        builder.try_append_placeholder().unwrap();
1667        builder.try_append_value("a long string value").unwrap();
1668
1669        let nulls = Some(NullBuffer::from(vec![true, false, true]));
1670        let array = builder.finish(nulls).unwrap();
1671        assert_eq!(array.value(0), "abc");
1672        assert!(array.is_null(1));
1673        assert_eq!(array.value(2), "a long string value");
1674    }
1675
1676    #[test]
1677    fn generic_string_builder_try_offset_overflow() {
1678        let err = try_offset::<i32>(i32::MAX as usize + 1)
1679            .unwrap_err()
1680            .to_string();
1681        assert!(
1682            err.contains("byte array offset overflow"),
1683            "unexpected error: {err}"
1684        );
1685    }
1686
1687    #[test]
1688    fn generic_string_builder_try_append_bytes_overflow() {
1689        let mut builder = GenericStringArrayBuilder::<i32>::with_capacity(0, 0);
1690        let err = builder
1691            .try_append_bytes(i32::MAX as usize + 1, |_| unreachable!())
1692            .unwrap_err()
1693            .to_string();
1694        assert!(
1695            err.contains("byte array offset overflow"),
1696            "unexpected error: {err}"
1697        );
1698    }
1699
1700    #[test]
1701    #[cfg(debug_assertions)]
1702    #[should_panic(expected = "placeholder rows")]
1703    fn string_array_builder_placeholder_without_null_mask() {
1704        let mut builder = GenericStringArrayBuilder::<i32>::with_capacity(2, 4);
1705        builder.append_value("a");
1706        builder.append_placeholder();
1707        // Slot 1 is a placeholder but the null buffer doesn't mark it null.
1708        let nulls = NullBuffer::from(vec![true, true]);
1709        let _ = builder.finish(Some(nulls));
1710    }
1711
1712    #[test]
1713    #[cfg(debug_assertions)]
1714    #[should_panic(expected = "placeholder rows")]
1715    fn string_array_builder_placeholder_with_none_null_buffer() {
1716        let mut builder = GenericStringArrayBuilder::<i32>::with_capacity(1, 4);
1717        builder.append_placeholder();
1718        let _ = builder.finish(None);
1719    }
1720
1721    #[test]
1722    #[cfg(debug_assertions)]
1723    #[should_panic(expected = "placeholder rows")]
1724    fn string_view_array_builder_placeholder_without_null_mask() {
1725        let mut builder = StringViewArrayBuilder::with_capacity(2);
1726        builder.append_value("a");
1727        builder.append_placeholder();
1728        let nulls = NullBuffer::from(vec![true, true]);
1729        let _ = builder.finish(Some(nulls));
1730    }
1731
1732    #[test]
1733    #[cfg(debug_assertions)]
1734    #[should_panic(expected = "placeholder rows")]
1735    fn string_view_array_builder_placeholder_with_none_null_buffer() {
1736        let mut builder = StringViewArrayBuilder::with_capacity(1);
1737        builder.append_placeholder();
1738        let _ = builder.finish(None);
1739    }
1740
1741    #[test]
1742    fn string_view_array_builder_append_with_inline() {
1743        // Rows that stay ≤ 12 bytes never touch the data block.
1744        let mut builder = StringViewArrayBuilder::with_capacity(4);
1745        let inputs = ["hello", "world!", "", "0123456789ab"];
1746        for s in &inputs {
1747            builder.append_with(|w| w.write_str(s));
1748        }
1749        let array = builder.finish(None).unwrap();
1750        assert_eq!(array.len(), inputs.len());
1751        for (i, s) in inputs.iter().enumerate() {
1752            assert_eq!(array.value(i), *s);
1753        }
1754        assert_eq!(array.data_buffers().len(), 0);
1755    }
1756
1757    #[test]
1758    fn string_view_array_builder_append_byte_map() {
1759        let mut builder = StringViewArrayBuilder::with_capacity(4);
1760        // SAFETY: ASCII inputs and ASCII outputs in every call.
1761        unsafe {
1762            builder.append_byte_map(b"hello", |b| b.to_ascii_uppercase());
1763            builder.append_byte_map(b"a long string of 25 bytes", |b| {
1764                if b == b' ' { b'_' } else { b }
1765            });
1766            // 12 bytes — exactly at the inline boundary.
1767            builder.append_byte_map(b"abcdefghijkl", |b| b);
1768            builder.append_byte_map(b"", |b| b);
1769        }
1770        let array = builder.finish(None).unwrap();
1771        assert_eq!(array.value(0), "HELLO");
1772        assert_eq!(array.value(1), "a_long_string_of_25_bytes");
1773        assert_eq!(array.value(2), "abcdefghijkl");
1774        assert_eq!(array.value(3), "");
1775        assert_eq!(array.data_buffers().len(), 1);
1776        assert_eq!(array.data_buffers()[0].len(), 25);
1777    }
1778
1779    #[test]
1780    fn string_view_array_builder_append_with_at_inline_boundary() {
1781        // Building exactly 12 bytes via several writes should still go inline.
1782        let mut builder = StringViewArrayBuilder::with_capacity(2);
1783        builder.append_with(|w| {
1784            w.write_str("hello");
1785            w.write_str(" world!");
1786        });
1787        builder.append_with(|w| {
1788            for _ in 0..6 {
1789                w.write_str("ab");
1790            }
1791        });
1792        let array = builder.finish(None).unwrap();
1793        assert_eq!(array.value(0), "hello world!");
1794        assert_eq!(array.value(1), "abababababab");
1795        assert_eq!(array.data_buffers().len(), 0);
1796    }
1797
1798    #[test]
1799    fn string_view_array_builder_append_with_spill_on_overflow() {
1800        // 12 bytes from one write, +1 byte from another → spill at boundary.
1801        let mut builder = StringViewArrayBuilder::with_capacity(1);
1802        builder.append_with(|w| {
1803            w.write_str("hello world!");
1804            w.write_str("X");
1805        });
1806        let array = builder.finish(None).unwrap();
1807        assert_eq!(array.value(0), "hello world!X");
1808        assert_eq!(array.data_buffers().len(), 1);
1809        assert_eq!(array.data_buffers()[0].len(), 13);
1810    }
1811
1812    #[test]
1813    fn string_view_array_builder_append_with_long_single_write() {
1814        // A single write larger than 12 bytes spills immediately with an
1815        // empty inline_buf prefix.
1816        let mut builder = StringViewArrayBuilder::with_capacity(1);
1817        builder.append_with(|w| w.write_str("a long string of 25 bytes"));
1818        let array = builder.finish(None).unwrap();
1819        assert_eq!(array.value(0), "a long string of 25 bytes");
1820        assert_eq!(array.data_buffers().len(), 1);
1821        assert_eq!(array.data_buffers()[0].len(), 25);
1822    }
1823
1824    #[test]
1825    fn string_view_array_builder_append_with_many_small_writes_spilling() {
1826        // 30 × "ab" (60 bytes total): first 6 fit inline, remainder spills.
1827        let mut builder = StringViewArrayBuilder::with_capacity(1);
1828        builder.append_with(|w| {
1829            for _ in 0..30 {
1830                w.write_str("ab");
1831            }
1832        });
1833        let array = builder.finish(None).unwrap();
1834        assert_eq!(array.value(0), "ab".repeat(30));
1835        assert_eq!(array.data_buffers().len(), 1);
1836        assert_eq!(array.data_buffers()[0].len(), 60);
1837    }
1838
1839    #[test]
1840    fn string_view_array_builder_append_with_chars() {
1841        // write_char with multi-byte UTF-8: row 0 stays inline (3 bytes),
1842        // row 1 spills (40 bytes).
1843        let mut builder = StringViewArrayBuilder::with_capacity(2);
1844        builder.append_with(|w| {
1845            w.write_char('é');
1846            w.write_char('!');
1847        });
1848        builder.append_with(|w| {
1849            for _ in 0..10 {
1850                w.write_char('🦀');
1851            }
1852        });
1853        let array = builder.finish(None).unwrap();
1854        assert_eq!(array.value(0), "é!");
1855        assert_eq!(array.value(1), "🦀".repeat(10));
1856    }
1857
1858    #[test]
1859    fn string_view_array_builder_append_with_block_rotation() {
1860        // 40 long rows, 500 bytes each, exceeds the first doubled block
1861        // (~16 KiB). Forces the builder to rotate blocks between rows.
1862        const STR_LEN: usize = 500;
1863        const N: usize = 40;
1864        let s = "x".repeat(STR_LEN);
1865        let mut builder = StringViewArrayBuilder::with_capacity(N);
1866        for _ in 0..N {
1867            builder.append_with(|w| w.write_str(&s));
1868        }
1869        let array = builder.finish(None).unwrap();
1870        assert_eq!(array.len(), N);
1871        assert!(
1872            array.data_buffers().len() >= 2,
1873            "expected multiple data buffers, got {}",
1874            array.data_buffers().len()
1875        );
1876        let total: usize = array.data_buffers().iter().map(|b| b.len()).sum();
1877        assert_eq!(total, N * STR_LEN);
1878        for i in 0..N {
1879            assert_eq!(array.value(i), s);
1880        }
1881    }
1882
1883    #[test]
1884    fn string_view_array_builder_flushes_full_blocks() {
1885        // Each value is 300 bytes. The first data block is 2 × STRING_VIEW_INIT_BLOCK_SIZE
1886        // = 16 KiB, so ~50 values saturate it and the rest spill into additional
1887        // blocks.
1888        let value = "x".repeat(300);
1889        let mut builder = StringViewArrayBuilder::with_capacity(100);
1890        for _ in 0..100 {
1891            builder.append_value(&value);
1892        }
1893        let array = builder.finish(None).unwrap();
1894        assert_eq!(array.len(), 100);
1895        assert!(
1896            array.data_buffers().len() > 1,
1897            "expected multiple data buffers, got {}",
1898            array.data_buffers().len()
1899        );
1900        for i in 0..100 {
1901            assert_eq!(array.value(i), value);
1902        }
1903    }
1904}