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