Skip to main content

hdbconnect_arrow/builders/
string.rs

1//! String and binary type builders.
2//!
3//! Implements builders for:
4//! - `Utf8` (VARCHAR, NVARCHAR, etc.)
5//! - `LargeUtf8` (CLOB, NCLOB)
6//! - `Binary` (BINARY)
7//! - `LargeBinary` (BLOB)
8//! - `FixedSizeBinary` (FIXED8, FIXED12, FIXED16)
9
10use std::sync::Arc;
11
12use arrow_array::ArrayRef;
13use arrow_array::builder::{
14    BinaryBuilder, FixedSizeBinaryBuilder, LargeBinaryBuilder, LargeStringBuilder, StringBuilder,
15};
16
17use crate::Result;
18use crate::traits::builder::HanaCompatibleBuilder;
19use crate::traits::sealed::private::Sealed;
20
21// ═══════════════════════════════════════════════════════════════════════════
22// String Builders
23// ═══════════════════════════════════════════════════════════════════════════
24
25/// Builder for Arrow Utf8 arrays (VARCHAR, NVARCHAR).
26#[derive(Debug)]
27pub struct StringBuilderWrapper {
28    builder: StringBuilder,
29    len: usize,
30}
31
32impl StringBuilderWrapper {
33    /// Create a new string builder.
34    ///
35    /// # Arguments
36    ///
37    /// * `capacity` - Number of strings to pre-allocate
38    /// * `data_capacity` - Bytes to pre-allocate for string data
39    #[must_use]
40    pub fn new(capacity: usize, data_capacity: usize) -> Self {
41        Self {
42            builder: StringBuilder::with_capacity(capacity, data_capacity),
43            len: 0,
44        }
45    }
46
47    /// Create builder with custom capacity for values and data.
48    #[must_use]
49    pub fn with_capacity(item_capacity: usize, data_capacity: usize) -> Self {
50        Self::new(item_capacity, data_capacity)
51    }
52
53    /// Create with default capacities (1024 items, 32KB data).
54    #[must_use]
55    pub fn default_capacity() -> Self {
56        Self::new(1024, 32 * 1024)
57    }
58}
59
60impl Sealed for StringBuilderWrapper {}
61
62impl HanaCompatibleBuilder for StringBuilderWrapper {
63    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
64        use hdbconnect::HdbValue;
65
66        match value {
67            HdbValue::STRING(s) => {
68                self.builder.append_value(s);
69            }
70            // Fallback: convert other types to string representation
71            other => {
72                self.builder.append_value(format!("{other:?}"));
73            }
74        }
75        self.len += 1;
76        Ok(())
77    }
78
79    fn append_null(&mut self) {
80        self.builder.append_null();
81        self.len += 1;
82    }
83
84    fn finish(&mut self) -> ArrayRef {
85        self.len = 0;
86        Arc::new(self.builder.finish())
87    }
88
89    fn len(&self) -> usize {
90        self.len
91    }
92
93    fn capacity(&self) -> Option<usize> {
94        // StringBuilder doesn't expose capacity()
95        None
96    }
97}
98
99/// Builder for Arrow `LargeUtf8` arrays (CLOB, NCLOB).
100///
101/// Supports eager materialization of CLOB and NCLOB LOB values with optional
102/// size limits to prevent OOM conditions.
103#[derive(Debug)]
104pub struct LargeStringBuilderWrapper {
105    builder: LargeStringBuilder,
106    len: usize,
107    max_lob_bytes: Option<usize>,
108}
109
110impl LargeStringBuilderWrapper {
111    /// Create a new large string builder.
112    #[must_use]
113    pub fn new(capacity: usize, data_capacity: usize) -> Self {
114        Self {
115            builder: LargeStringBuilder::with_capacity(capacity, data_capacity),
116            len: 0,
117            max_lob_bytes: None,
118        }
119    }
120
121    /// Create builder with custom capacity for values and data.
122    #[must_use]
123    pub fn with_capacity(item_capacity: usize, data_capacity: usize) -> Self {
124        Self::new(item_capacity, data_capacity)
125    }
126
127    /// Create with default capacities.
128    #[must_use]
129    pub fn default_capacity() -> Self {
130        Self::new(1024, 1024 * 1024) // 1MB default for LOBs
131    }
132
133    /// Set the maximum LOB size in bytes.
134    ///
135    /// LOB values exceeding this size will cause an error during conversion.
136    #[must_use]
137    pub const fn with_max_lob_bytes(mut self, max: usize) -> Self {
138        self.max_lob_bytes = Some(max);
139        self
140    }
141
142    /// Materialize a CLOB value with size checking.
143    fn materialize_clob(&self, clob: hdbconnect::types::CLob) -> Result<String> {
144        if let Some(max) = self.max_lob_bytes {
145            // Intentional truncation: LOBs > usize::MAX are rejected anyway by size check
146            #[allow(clippy::cast_possible_truncation)]
147            let lob_size = clob.total_byte_length() as usize;
148            if lob_size > max {
149                return Err(crate::ArrowConversionError::lob_streaming(format!(
150                    "CLOB size {lob_size} bytes exceeds max_lob_bytes limit {max} bytes",
151                )));
152            }
153        }
154
155        clob.into_string().map_err(|e| {
156            crate::ArrowConversionError::lob_streaming(format!("CLOB read failed: {e}"))
157        })
158    }
159
160    /// Materialize an NCLOB value with size checking.
161    fn materialize_nclob(&self, nclob: hdbconnect::types::NCLob) -> Result<String> {
162        if let Some(max) = self.max_lob_bytes {
163            // Intentional truncation: LOBs > usize::MAX are rejected anyway by size check
164            #[allow(clippy::cast_possible_truncation)]
165            let lob_size = nclob.total_byte_length() as usize;
166            if lob_size > max {
167                return Err(crate::ArrowConversionError::lob_streaming(format!(
168                    "NCLOB size {lob_size} bytes exceeds max_lob_bytes limit {max} bytes",
169                )));
170            }
171        }
172
173        nclob.into_string().map_err(|e| {
174            crate::ArrowConversionError::lob_streaming(format!("NCLOB read failed: {e}"))
175        })
176    }
177}
178
179impl Sealed for LargeStringBuilderWrapper {}
180
181impl HanaCompatibleBuilder for LargeStringBuilderWrapper {
182    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
183        use hdbconnect::HdbValue;
184
185        match value {
186            HdbValue::STRING(s) => {
187                self.builder.append_value(s);
188            }
189            HdbValue::SYNC_CLOB(clob) => {
190                let content = self.materialize_clob(clob.clone())?;
191                self.builder.append_value(&content);
192            }
193            HdbValue::SYNC_NCLOB(nclob) => {
194                let content = self.materialize_nclob(nclob.clone())?;
195                self.builder.append_value(&content);
196            }
197            other => {
198                return Err(crate::ArrowConversionError::value_conversion(
199                    "large_string",
200                    format!(
201                        "cannot convert {:?} to LargeUtf8",
202                        std::mem::discriminant(other)
203                    ),
204                ));
205            }
206        }
207        self.len += 1;
208        Ok(())
209    }
210
211    fn append_null(&mut self) {
212        self.builder.append_null();
213        self.len += 1;
214    }
215
216    fn finish(&mut self) -> ArrayRef {
217        self.len = 0;
218        Arc::new(self.builder.finish())
219    }
220
221    fn len(&self) -> usize {
222        self.len
223    }
224
225    fn capacity(&self) -> Option<usize> {
226        None
227    }
228}
229
230// ═══════════════════════════════════════════════════════════════════════════
231// Binary Builders
232// ═══════════════════════════════════════════════════════════════════════════
233
234/// Builder for Arrow Binary arrays (BINARY).
235#[derive(Debug)]
236pub struct BinaryBuilderWrapper {
237    builder: BinaryBuilder,
238    len: usize,
239}
240
241impl BinaryBuilderWrapper {
242    /// Create a new binary builder.
243    #[must_use]
244    pub fn new(capacity: usize, data_capacity: usize) -> Self {
245        Self {
246            builder: BinaryBuilder::with_capacity(capacity, data_capacity),
247            len: 0,
248        }
249    }
250
251    /// Create with default capacities.
252    #[must_use]
253    pub fn default_capacity() -> Self {
254        Self::new(1024, 64 * 1024) // 64KB default
255    }
256}
257
258impl Sealed for BinaryBuilderWrapper {}
259
260impl HanaCompatibleBuilder for BinaryBuilderWrapper {
261    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
262        use hdbconnect::HdbValue;
263
264        match value {
265            // Binary and spatial types as WKB
266            HdbValue::BINARY(bytes) | HdbValue::GEOMETRY(bytes) | HdbValue::POINT(bytes) => {
267                self.builder.append_value(bytes);
268            }
269            other => {
270                return Err(crate::ArrowConversionError::value_conversion(
271                    "binary",
272                    format!("cannot convert {other:?} to binary"),
273                ));
274            }
275        }
276        self.len += 1;
277        Ok(())
278    }
279
280    fn append_null(&mut self) {
281        self.builder.append_null();
282        self.len += 1;
283    }
284
285    fn finish(&mut self) -> ArrayRef {
286        self.len = 0;
287        Arc::new(self.builder.finish())
288    }
289
290    fn len(&self) -> usize {
291        self.len
292    }
293
294    fn capacity(&self) -> Option<usize> {
295        None
296    }
297}
298
299/// Builder for Arrow `LargeBinary` arrays (BLOB).
300///
301/// Supports eager materialization of BLOB LOB values with optional
302/// size limits to prevent OOM conditions.
303#[derive(Debug)]
304pub struct LargeBinaryBuilderWrapper {
305    builder: LargeBinaryBuilder,
306    len: usize,
307    max_lob_bytes: Option<usize>,
308}
309
310impl LargeBinaryBuilderWrapper {
311    /// Create a new large binary builder.
312    #[must_use]
313    pub fn new(capacity: usize, data_capacity: usize) -> Self {
314        Self {
315            builder: LargeBinaryBuilder::with_capacity(capacity, data_capacity),
316            len: 0,
317            max_lob_bytes: None,
318        }
319    }
320
321    /// Create with default capacities.
322    #[must_use]
323    pub fn default_capacity() -> Self {
324        Self::new(1024, 1024 * 1024) // 1MB default for BLOBs
325    }
326
327    /// Set the maximum LOB size in bytes.
328    ///
329    /// LOB values exceeding this size will cause an error during conversion.
330    #[must_use]
331    pub const fn with_max_lob_bytes(mut self, max: usize) -> Self {
332        self.max_lob_bytes = Some(max);
333        self
334    }
335
336    /// Materialize a BLOB value with size checking.
337    fn materialize_blob(&self, blob: hdbconnect::types::BLob) -> Result<Vec<u8>> {
338        if let Some(max) = self.max_lob_bytes {
339            // Intentional truncation: LOBs > usize::MAX are rejected anyway by size check
340            #[allow(clippy::cast_possible_truncation)]
341            let lob_size = blob.total_byte_length() as usize;
342            if lob_size > max {
343                return Err(crate::ArrowConversionError::lob_streaming(format!(
344                    "BLOB size {lob_size} bytes exceeds max_lob_bytes limit {max} bytes",
345                )));
346            }
347        }
348
349        blob.into_bytes().map_err(|e| {
350            crate::ArrowConversionError::lob_streaming(format!("BLOB read failed: {e}"))
351        })
352    }
353}
354
355impl Sealed for LargeBinaryBuilderWrapper {}
356
357impl HanaCompatibleBuilder for LargeBinaryBuilderWrapper {
358    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
359        use hdbconnect::HdbValue;
360
361        match value {
362            HdbValue::BINARY(bytes) => {
363                self.builder.append_value(bytes);
364            }
365            HdbValue::SYNC_BLOB(blob) => {
366                let content = self.materialize_blob(blob.clone())?;
367                self.builder.append_value(&content);
368            }
369            other => {
370                return Err(crate::ArrowConversionError::value_conversion(
371                    "large_binary",
372                    format!(
373                        "cannot convert {:?} to LargeBinary",
374                        std::mem::discriminant(other)
375                    ),
376                ));
377            }
378        }
379        self.len += 1;
380        Ok(())
381    }
382
383    fn append_null(&mut self) {
384        self.builder.append_null();
385        self.len += 1;
386    }
387
388    fn finish(&mut self) -> ArrayRef {
389        self.len = 0;
390        Arc::new(self.builder.finish())
391    }
392
393    fn len(&self) -> usize {
394        self.len
395    }
396
397    fn capacity(&self) -> Option<usize> {
398        None
399    }
400}
401
402/// Builder for Arrow `FixedSizeBinary` arrays (FIXED8, FIXED12, FIXED16).
403#[derive(Debug)]
404pub struct FixedSizeBinaryBuilderWrapper {
405    builder: FixedSizeBinaryBuilder,
406    byte_width: i32,
407    len: usize,
408}
409
410impl FixedSizeBinaryBuilderWrapper {
411    /// Create a new fixed-size binary builder.
412    ///
413    /// # Arguments
414    ///
415    /// * `capacity` - Number of fixed-size binary values to pre-allocate
416    /// * `byte_width` - Size of each binary value in bytes
417    #[must_use]
418    pub fn new(capacity: usize, byte_width: i32) -> Self {
419        Self {
420            builder: FixedSizeBinaryBuilder::with_capacity(capacity, byte_width),
421            byte_width,
422            len: 0,
423        }
424    }
425}
426
427impl Sealed for FixedSizeBinaryBuilderWrapper {}
428
429impl HanaCompatibleBuilder for FixedSizeBinaryBuilderWrapper {
430    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
431        use hdbconnect::HdbValue;
432
433        match value {
434            HdbValue::BINARY(bytes) => {
435                #[allow(clippy::cast_sign_loss)]
436                if bytes.len() != self.byte_width as usize {
437                    return Err(crate::ArrowConversionError::value_conversion(
438                        "fixed_size_binary",
439                        format!("expected {} bytes, got {}", self.byte_width, bytes.len()),
440                    ));
441                }
442                self.builder.append_value(bytes).map_err(|e| {
443                    crate::ArrowConversionError::value_conversion(
444                        "fixed_size_binary",
445                        e.to_string(),
446                    )
447                })?;
448            }
449            other => {
450                return Err(crate::ArrowConversionError::value_conversion(
451                    "fixed_size_binary",
452                    format!("cannot convert {other:?} to fixed-size binary"),
453                ));
454            }
455        }
456        self.len += 1;
457        Ok(())
458    }
459
460    fn append_null(&mut self) {
461        self.builder.append_null();
462        self.len += 1;
463    }
464
465    fn finish(&mut self) -> ArrayRef {
466        self.len = 0;
467        Arc::new(self.builder.finish())
468    }
469
470    fn len(&self) -> usize {
471        self.len
472    }
473
474    fn capacity(&self) -> Option<usize> {
475        None
476    }
477}
478
479#[cfg(test)]
480mod tests {
481    use arrow_array::{
482        Array, BinaryArray, FixedSizeBinaryArray, LargeBinaryArray, LargeStringArray, StringArray,
483    };
484    use hdbconnect::HdbValue;
485
486    use super::*;
487
488    // ═══════════════════════════════════════════════════════════════════════════
489    // StringBuilderWrapper Tests
490    // ═══════════════════════════════════════════════════════════════════════════
491
492    #[test]
493    fn test_string_builder_new() {
494        let builder = StringBuilderWrapper::new(10, 100);
495        assert_eq!(builder.len(), 0);
496        assert!(builder.capacity().is_none());
497    }
498
499    #[test]
500    fn test_string_builder_with_capacity() {
501        let builder = StringBuilderWrapper::with_capacity(10, 100);
502        assert_eq!(builder.len(), 0);
503        assert!(builder.capacity().is_none());
504    }
505
506    #[test]
507    fn test_string_builder_default_capacity() {
508        let builder = StringBuilderWrapper::default_capacity();
509        assert_eq!(builder.len(), 0);
510    }
511
512    #[test]
513    fn test_string_builder_append_string() {
514        let mut builder = StringBuilderWrapper::new(10, 100);
515        builder
516            .append_hana_value(&HdbValue::STRING("hello".to_string()))
517            .unwrap();
518        assert_eq!(builder.len(), 1);
519    }
520
521    #[test]
522    fn test_string_builder_append_null() {
523        let mut builder = StringBuilderWrapper::new(10, 100);
524        builder.append_null();
525        assert_eq!(builder.len(), 1);
526
527        let array = builder.finish();
528        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
529        assert!(string_array.is_null(0));
530    }
531
532    #[test]
533    fn test_string_builder_append_non_string_type() {
534        let mut builder = StringBuilderWrapper::new(10, 100);
535        builder.append_hana_value(&HdbValue::INT(42)).unwrap();
536        assert_eq!(builder.len(), 1);
537
538        let array = builder.finish();
539        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
540        assert!(string_array.value(0).contains("INT"));
541    }
542
543    #[test]
544    fn test_string_builder_finish_and_reuse() {
545        let mut builder = StringBuilderWrapper::new(10, 100);
546        builder
547            .append_hana_value(&HdbValue::STRING("first".to_string()))
548            .unwrap();
549        let _array1 = builder.finish();
550        assert_eq!(builder.len(), 0);
551
552        builder
553            .append_hana_value(&HdbValue::STRING("second".to_string()))
554            .unwrap();
555        let array2 = builder.finish();
556        assert_eq!(array2.len(), 1);
557    }
558
559    #[test]
560    fn test_string_builder_empty_string() {
561        let mut builder = StringBuilderWrapper::new(10, 100);
562        builder
563            .append_hana_value(&HdbValue::STRING(String::new()))
564            .unwrap();
565
566        let array = builder.finish();
567        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
568        assert_eq!(string_array.value(0), "");
569    }
570
571    #[test]
572    fn test_string_builder_unicode() {
573        let mut builder = StringBuilderWrapper::new(10, 1000);
574        builder
575            .append_hana_value(&HdbValue::STRING("日本語テスト".to_string()))
576            .unwrap();
577        builder
578            .append_hana_value(&HdbValue::STRING("émojis: 🚀🎉".to_string()))
579            .unwrap();
580
581        let array = builder.finish();
582        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
583        assert_eq!(string_array.value(0), "日本語テスト");
584        assert_eq!(string_array.value(1), "émojis: 🚀🎉");
585    }
586
587    // ═══════════════════════════════════════════════════════════════════════════
588    // LargeStringBuilderWrapper Tests
589    // ═══════════════════════════════════════════════════════════════════════════
590
591    #[test]
592    fn test_large_string_builder_new() {
593        let builder = LargeStringBuilderWrapper::new(10, 1000);
594        assert_eq!(builder.len(), 0);
595        assert!(builder.max_lob_bytes.is_none());
596    }
597
598    #[test]
599    fn test_large_string_builder_with_capacity() {
600        let builder = LargeStringBuilderWrapper::with_capacity(10, 1000);
601        assert_eq!(builder.len(), 0);
602        assert!(builder.max_lob_bytes.is_none());
603    }
604
605    #[test]
606    fn test_large_string_builder_default_capacity() {
607        let builder = LargeStringBuilderWrapper::default_capacity();
608        assert_eq!(builder.len(), 0);
609    }
610
611    #[test]
612    fn test_large_string_builder_with_max_lob_bytes() {
613        let builder = LargeStringBuilderWrapper::new(10, 1000).with_max_lob_bytes(1_000_000);
614        assert_eq!(builder.max_lob_bytes, Some(1_000_000));
615    }
616
617    #[test]
618    fn test_large_string_builder_append_string() {
619        let mut builder = LargeStringBuilderWrapper::new(10, 1000);
620        builder
621            .append_hana_value(&HdbValue::STRING("large text".to_string()))
622            .unwrap();
623
624        let array = builder.finish();
625        let large_string_array = array.as_any().downcast_ref::<LargeStringArray>().unwrap();
626        assert_eq!(large_string_array.value(0), "large text");
627    }
628
629    #[test]
630    fn test_large_string_builder_append_null() {
631        let mut builder = LargeStringBuilderWrapper::new(10, 1000);
632        builder.append_null();
633
634        let array = builder.finish();
635        let large_string_array = array.as_any().downcast_ref::<LargeStringArray>().unwrap();
636        assert!(large_string_array.is_null(0));
637    }
638
639    #[test]
640    fn test_large_string_builder_reject_non_string_type() {
641        let mut builder = LargeStringBuilderWrapper::new(10, 1000);
642        let result = builder.append_hana_value(&HdbValue::BIGINT(123456789));
643
644        assert!(result.is_err());
645        let err = result.unwrap_err();
646        assert!(err.is_value_conversion());
647    }
648
649    // ═══════════════════════════════════════════════════════════════════════════
650    // BinaryBuilderWrapper Tests
651    // ═══════════════════════════════════════════════════════════════════════════
652
653    #[test]
654    fn test_binary_builder_new() {
655        let builder = BinaryBuilderWrapper::new(10, 100);
656        assert_eq!(builder.len(), 0);
657    }
658
659    #[test]
660    fn test_binary_builder_default_capacity() {
661        let builder = BinaryBuilderWrapper::default_capacity();
662        assert_eq!(builder.len(), 0);
663    }
664
665    #[test]
666    fn test_binary_builder_append_binary() {
667        let mut builder = BinaryBuilderWrapper::new(10, 100);
668        builder
669            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3]))
670            .unwrap();
671
672        let array = builder.finish();
673        let binary_array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
674        assert_eq!(binary_array.value(0), &[1, 2, 3]);
675    }
676
677    #[test]
678    fn test_binary_builder_append_null() {
679        let mut builder = BinaryBuilderWrapper::new(10, 100);
680        builder.append_null();
681
682        let array = builder.finish();
683        let binary_array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
684        assert!(binary_array.is_null(0));
685    }
686
687    #[test]
688    fn test_binary_builder_append_geometry() {
689        let mut builder = BinaryBuilderWrapper::new(10, 100);
690        builder
691            .append_hana_value(&HdbValue::GEOMETRY(vec![0x00, 0x01, 0x02]))
692            .unwrap();
693
694        let array = builder.finish();
695        assert_eq!(array.len(), 1);
696    }
697
698    #[test]
699    fn test_binary_builder_append_point() {
700        let mut builder = BinaryBuilderWrapper::new(10, 100);
701        builder
702            .append_hana_value(&HdbValue::POINT(vec![0xAB, 0xCD]))
703            .unwrap();
704
705        let array = builder.finish();
706        assert_eq!(array.len(), 1);
707    }
708
709    #[test]
710    fn test_binary_builder_reject_string() {
711        let mut builder = BinaryBuilderWrapper::new(10, 100);
712        let result = builder.append_hana_value(&HdbValue::STRING("text".to_string()));
713        assert!(result.is_err());
714        assert!(result.unwrap_err().is_value_conversion());
715    }
716
717    #[test]
718    fn test_binary_builder_empty_binary() {
719        let mut builder = BinaryBuilderWrapper::new(10, 100);
720        builder
721            .append_hana_value(&HdbValue::BINARY(vec![]))
722            .unwrap();
723
724        let array = builder.finish();
725        let binary_array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
726        assert!(binary_array.value(0).is_empty());
727    }
728
729    // ═══════════════════════════════════════════════════════════════════════════
730    // LargeBinaryBuilderWrapper Tests
731    // ═══════════════════════════════════════════════════════════════════════════
732
733    #[test]
734    fn test_large_binary_builder_new() {
735        let builder = LargeBinaryBuilderWrapper::new(10, 1000);
736        assert_eq!(builder.len(), 0);
737        assert!(builder.max_lob_bytes.is_none());
738    }
739
740    #[test]
741    fn test_large_binary_builder_default_capacity() {
742        let builder = LargeBinaryBuilderWrapper::default_capacity();
743        assert_eq!(builder.len(), 0);
744    }
745
746    #[test]
747    fn test_large_binary_builder_with_max_lob_bytes() {
748        let builder = LargeBinaryBuilderWrapper::new(10, 1000).with_max_lob_bytes(1_000_000);
749        assert_eq!(builder.max_lob_bytes, Some(1_000_000));
750    }
751
752    #[test]
753    fn test_large_binary_builder_append_binary() {
754        let mut builder = LargeBinaryBuilderWrapper::new(10, 1000);
755        builder
756            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4, 5]))
757            .unwrap();
758
759        let array = builder.finish();
760        let large_binary_array = array.as_any().downcast_ref::<LargeBinaryArray>().unwrap();
761        assert_eq!(large_binary_array.value(0), &[1, 2, 3, 4, 5]);
762    }
763
764    #[test]
765    fn test_large_binary_builder_append_null() {
766        let mut builder = LargeBinaryBuilderWrapper::new(10, 1000);
767        builder.append_null();
768
769        let array = builder.finish();
770        let large_binary_array = array.as_any().downcast_ref::<LargeBinaryArray>().unwrap();
771        assert!(large_binary_array.is_null(0));
772    }
773
774    #[test]
775    fn test_large_binary_builder_reject_string() {
776        let mut builder = LargeBinaryBuilderWrapper::new(10, 1000);
777        let result = builder.append_hana_value(&HdbValue::STRING("text".to_string()));
778        assert!(result.is_err());
779        assert!(result.unwrap_err().is_value_conversion());
780    }
781
782    // ═══════════════════════════════════════════════════════════════════════════
783    // FixedSizeBinaryBuilderWrapper Tests
784    // ═══════════════════════════════════════════════════════════════════════════
785
786    #[test]
787    fn test_fixed_size_binary_builder_new() {
788        let builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
789        assert_eq!(builder.len(), 0);
790        assert_eq!(builder.byte_width, 4);
791    }
792
793    #[test]
794    fn test_fixed_size_binary_builder_correct_size() {
795        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
796        builder
797            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4]))
798            .unwrap();
799
800        let array = builder.finish();
801        let fixed_binary = array
802            .as_any()
803            .downcast_ref::<FixedSizeBinaryArray>()
804            .unwrap();
805        assert_eq!(fixed_binary.value(0), &[1, 2, 3, 4]);
806    }
807
808    #[test]
809    fn test_fixed_size_binary_builder_wrong_size_smaller() {
810        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
811        let result = builder.append_hana_value(&HdbValue::BINARY(vec![1, 2]));
812        assert!(result.is_err());
813        let err = result.unwrap_err();
814        assert!(err.is_value_conversion());
815        assert!(err.to_string().contains("expected 4 bytes"));
816    }
817
818    #[test]
819    fn test_fixed_size_binary_builder_wrong_size_larger() {
820        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
821        let result = builder.append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4, 5, 6]));
822        assert!(result.is_err());
823    }
824
825    #[test]
826    fn test_fixed_size_binary_builder_append_null() {
827        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
828        builder.append_null();
829
830        let array = builder.finish();
831        let fixed_binary = array
832            .as_any()
833            .downcast_ref::<FixedSizeBinaryArray>()
834            .unwrap();
835        assert!(fixed_binary.is_null(0));
836    }
837
838    #[test]
839    fn test_fixed_size_binary_builder_reject_non_binary() {
840        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
841        let result = builder.append_hana_value(&HdbValue::INT(42));
842        assert!(result.is_err());
843        assert!(result.unwrap_err().is_value_conversion());
844    }
845
846    #[test]
847    fn test_fixed_size_binary_builder_different_widths() {
848        // Test with byte_width = 8
849        let mut builder8 = FixedSizeBinaryBuilderWrapper::new(10, 8);
850        builder8
851            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4, 5, 6, 7, 8]))
852            .unwrap();
853        assert_eq!(builder8.len(), 1);
854
855        // Test with byte_width = 16
856        let mut builder16 = FixedSizeBinaryBuilderWrapper::new(10, 16);
857        builder16
858            .append_hana_value(&HdbValue::BINARY(vec![0; 16]))
859            .unwrap();
860        assert_eq!(builder16.len(), 1);
861    }
862}