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#[derive(Debug)]
95pub struct LargeStringBuilderWrapper {
96    builder: LargeStringBuilder,
97    len: usize,
98}
99
100impl LargeStringBuilderWrapper {
101    /// Create a new large string builder.
102    #[must_use]
103    pub fn new(capacity: usize, data_capacity: usize) -> Self {
104        Self {
105            builder: LargeStringBuilder::with_capacity(capacity, data_capacity),
106            len: 0,
107        }
108    }
109
110    /// Create with default capacities.
111    #[must_use]
112    pub fn default_capacity() -> Self {
113        Self::new(1024, 1024 * 1024) // 1MB default for LOBs
114    }
115}
116
117impl Sealed for LargeStringBuilderWrapper {}
118
119impl HanaCompatibleBuilder for LargeStringBuilderWrapper {
120    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
121        use hdbconnect::HdbValue;
122
123        match value {
124            HdbValue::STRING(s) => {
125                self.builder.append_value(s);
126            }
127            // LOBs are handled differently in hdbconnect 0.32+
128            // They require async reading which is beyond this sync builder
129            other => {
130                self.builder.append_value(format!("{other:?}"));
131            }
132        }
133        self.len += 1;
134        Ok(())
135    }
136
137    fn append_null(&mut self) {
138        self.builder.append_null();
139        self.len += 1;
140    }
141
142    fn finish(&mut self) -> ArrayRef {
143        self.len = 0;
144        Arc::new(self.builder.finish())
145    }
146
147    fn len(&self) -> usize {
148        self.len
149    }
150
151    fn capacity(&self) -> Option<usize> {
152        None
153    }
154}
155
156// ═══════════════════════════════════════════════════════════════════════════
157// Binary Builders
158// ═══════════════════════════════════════════════════════════════════════════
159
160/// Builder for Arrow Binary arrays (BINARY).
161#[derive(Debug)]
162pub struct BinaryBuilderWrapper {
163    builder: BinaryBuilder,
164    len: usize,
165}
166
167impl BinaryBuilderWrapper {
168    /// Create a new binary builder.
169    #[must_use]
170    pub fn new(capacity: usize, data_capacity: usize) -> Self {
171        Self {
172            builder: BinaryBuilder::with_capacity(capacity, data_capacity),
173            len: 0,
174        }
175    }
176
177    /// Create with default capacities.
178    #[must_use]
179    pub fn default_capacity() -> Self {
180        Self::new(1024, 64 * 1024) // 64KB default
181    }
182}
183
184impl Sealed for BinaryBuilderWrapper {}
185
186impl HanaCompatibleBuilder for BinaryBuilderWrapper {
187    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
188        use hdbconnect::HdbValue;
189
190        match value {
191            // Binary and spatial types as WKB
192            HdbValue::BINARY(bytes) | HdbValue::GEOMETRY(bytes) | HdbValue::POINT(bytes) => {
193                self.builder.append_value(bytes);
194            }
195            other => {
196                return Err(crate::ArrowConversionError::value_conversion(
197                    "binary",
198                    format!("cannot convert {other:?} to binary"),
199                ));
200            }
201        }
202        self.len += 1;
203        Ok(())
204    }
205
206    fn append_null(&mut self) {
207        self.builder.append_null();
208        self.len += 1;
209    }
210
211    fn finish(&mut self) -> ArrayRef {
212        self.len = 0;
213        Arc::new(self.builder.finish())
214    }
215
216    fn len(&self) -> usize {
217        self.len
218    }
219
220    fn capacity(&self) -> Option<usize> {
221        None
222    }
223}
224
225/// Builder for Arrow `LargeBinary` arrays (BLOB).
226#[derive(Debug)]
227pub struct LargeBinaryBuilderWrapper {
228    builder: LargeBinaryBuilder,
229    len: usize,
230}
231
232impl LargeBinaryBuilderWrapper {
233    /// Create a new large binary builder.
234    #[must_use]
235    pub fn new(capacity: usize, data_capacity: usize) -> Self {
236        Self {
237            builder: LargeBinaryBuilder::with_capacity(capacity, data_capacity),
238            len: 0,
239        }
240    }
241
242    /// Create with default capacities.
243    #[must_use]
244    pub fn default_capacity() -> Self {
245        Self::new(1024, 1024 * 1024) // 1MB default for BLOBs
246    }
247}
248
249impl Sealed for LargeBinaryBuilderWrapper {}
250
251impl HanaCompatibleBuilder for LargeBinaryBuilderWrapper {
252    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
253        use hdbconnect::HdbValue;
254
255        match value {
256            HdbValue::BINARY(bytes) => {
257                self.builder.append_value(bytes);
258            }
259            // BLOBs are handled differently in hdbconnect 0.32+
260            other => {
261                return Err(crate::ArrowConversionError::value_conversion(
262                    "large_binary",
263                    format!("cannot convert {other:?} to binary"),
264                ));
265            }
266        }
267        self.len += 1;
268        Ok(())
269    }
270
271    fn append_null(&mut self) {
272        self.builder.append_null();
273        self.len += 1;
274    }
275
276    fn finish(&mut self) -> ArrayRef {
277        self.len = 0;
278        Arc::new(self.builder.finish())
279    }
280
281    fn len(&self) -> usize {
282        self.len
283    }
284
285    fn capacity(&self) -> Option<usize> {
286        None
287    }
288}
289
290/// Builder for Arrow `FixedSizeBinary` arrays (FIXED8, FIXED12, FIXED16).
291#[derive(Debug)]
292pub struct FixedSizeBinaryBuilderWrapper {
293    builder: FixedSizeBinaryBuilder,
294    byte_width: i32,
295    len: usize,
296}
297
298impl FixedSizeBinaryBuilderWrapper {
299    /// Create a new fixed-size binary builder.
300    ///
301    /// # Arguments
302    ///
303    /// * `capacity` - Number of fixed-size binary values to pre-allocate
304    /// * `byte_width` - Size of each binary value in bytes
305    #[must_use]
306    pub fn new(capacity: usize, byte_width: i32) -> Self {
307        Self {
308            builder: FixedSizeBinaryBuilder::with_capacity(capacity, byte_width),
309            byte_width,
310            len: 0,
311        }
312    }
313}
314
315impl Sealed for FixedSizeBinaryBuilderWrapper {}
316
317impl HanaCompatibleBuilder for FixedSizeBinaryBuilderWrapper {
318    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
319        use hdbconnect::HdbValue;
320
321        match value {
322            HdbValue::BINARY(bytes) => {
323                #[allow(clippy::cast_sign_loss)]
324                if bytes.len() != self.byte_width as usize {
325                    return Err(crate::ArrowConversionError::value_conversion(
326                        "fixed_size_binary",
327                        format!("expected {} bytes, got {}", self.byte_width, bytes.len()),
328                    ));
329                }
330                self.builder.append_value(bytes).map_err(|e| {
331                    crate::ArrowConversionError::value_conversion(
332                        "fixed_size_binary",
333                        e.to_string(),
334                    )
335                })?;
336            }
337            other => {
338                return Err(crate::ArrowConversionError::value_conversion(
339                    "fixed_size_binary",
340                    format!("cannot convert {other:?} to fixed-size binary"),
341                ));
342            }
343        }
344        self.len += 1;
345        Ok(())
346    }
347
348    fn append_null(&mut self) {
349        self.builder.append_null();
350        self.len += 1;
351    }
352
353    fn finish(&mut self) -> ArrayRef {
354        self.len = 0;
355        Arc::new(self.builder.finish())
356    }
357
358    fn len(&self) -> usize {
359        self.len
360    }
361
362    fn capacity(&self) -> Option<usize> {
363        None
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use arrow_array::{
370        Array, BinaryArray, FixedSizeBinaryArray, LargeBinaryArray, LargeStringArray, StringArray,
371    };
372    use hdbconnect::HdbValue;
373
374    use super::*;
375
376    // ═══════════════════════════════════════════════════════════════════════════
377    // StringBuilderWrapper Tests
378    // ═══════════════════════════════════════════════════════════════════════════
379
380    #[test]
381    fn test_string_builder_new() {
382        let builder = StringBuilderWrapper::new(10, 100);
383        assert_eq!(builder.len(), 0);
384        assert!(builder.capacity().is_none());
385    }
386
387    #[test]
388    fn test_string_builder_default_capacity() {
389        let builder = StringBuilderWrapper::default_capacity();
390        assert_eq!(builder.len(), 0);
391    }
392
393    #[test]
394    fn test_string_builder_append_string() {
395        let mut builder = StringBuilderWrapper::new(10, 100);
396        builder
397            .append_hana_value(&HdbValue::STRING("hello".to_string()))
398            .unwrap();
399        assert_eq!(builder.len(), 1);
400    }
401
402    #[test]
403    fn test_string_builder_append_null() {
404        let mut builder = StringBuilderWrapper::new(10, 100);
405        builder.append_null();
406        assert_eq!(builder.len(), 1);
407
408        let array = builder.finish();
409        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
410        assert!(string_array.is_null(0));
411    }
412
413    #[test]
414    fn test_string_builder_append_non_string_type() {
415        let mut builder = StringBuilderWrapper::new(10, 100);
416        builder.append_hana_value(&HdbValue::INT(42)).unwrap();
417        assert_eq!(builder.len(), 1);
418
419        let array = builder.finish();
420        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
421        assert!(string_array.value(0).contains("INT"));
422    }
423
424    #[test]
425    fn test_string_builder_finish_and_reuse() {
426        let mut builder = StringBuilderWrapper::new(10, 100);
427        builder
428            .append_hana_value(&HdbValue::STRING("first".to_string()))
429            .unwrap();
430        let _array1 = builder.finish();
431        assert_eq!(builder.len(), 0);
432
433        builder
434            .append_hana_value(&HdbValue::STRING("second".to_string()))
435            .unwrap();
436        let array2 = builder.finish();
437        assert_eq!(array2.len(), 1);
438    }
439
440    #[test]
441    fn test_string_builder_empty_string() {
442        let mut builder = StringBuilderWrapper::new(10, 100);
443        builder
444            .append_hana_value(&HdbValue::STRING(String::new()))
445            .unwrap();
446
447        let array = builder.finish();
448        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
449        assert_eq!(string_array.value(0), "");
450    }
451
452    #[test]
453    fn test_string_builder_unicode() {
454        let mut builder = StringBuilderWrapper::new(10, 1000);
455        builder
456            .append_hana_value(&HdbValue::STRING("日本語テスト".to_string()))
457            .unwrap();
458        builder
459            .append_hana_value(&HdbValue::STRING("émojis: 🚀🎉".to_string()))
460            .unwrap();
461
462        let array = builder.finish();
463        let string_array = array.as_any().downcast_ref::<StringArray>().unwrap();
464        assert_eq!(string_array.value(0), "日本語テスト");
465        assert_eq!(string_array.value(1), "émojis: 🚀🎉");
466    }
467
468    // ═══════════════════════════════════════════════════════════════════════════
469    // LargeStringBuilderWrapper Tests
470    // ═══════════════════════════════════════════════════════════════════════════
471
472    #[test]
473    fn test_large_string_builder_new() {
474        let builder = LargeStringBuilderWrapper::new(10, 1000);
475        assert_eq!(builder.len(), 0);
476    }
477
478    #[test]
479    fn test_large_string_builder_default_capacity() {
480        let builder = LargeStringBuilderWrapper::default_capacity();
481        assert_eq!(builder.len(), 0);
482    }
483
484    #[test]
485    fn test_large_string_builder_append_string() {
486        let mut builder = LargeStringBuilderWrapper::new(10, 1000);
487        builder
488            .append_hana_value(&HdbValue::STRING("large text".to_string()))
489            .unwrap();
490
491        let array = builder.finish();
492        let large_string_array = array.as_any().downcast_ref::<LargeStringArray>().unwrap();
493        assert_eq!(large_string_array.value(0), "large text");
494    }
495
496    #[test]
497    fn test_large_string_builder_append_null() {
498        let mut builder = LargeStringBuilderWrapper::new(10, 1000);
499        builder.append_null();
500
501        let array = builder.finish();
502        let large_string_array = array.as_any().downcast_ref::<LargeStringArray>().unwrap();
503        assert!(large_string_array.is_null(0));
504    }
505
506    #[test]
507    fn test_large_string_builder_append_non_string_type() {
508        let mut builder = LargeStringBuilderWrapper::new(10, 1000);
509        builder
510            .append_hana_value(&HdbValue::BIGINT(123456789))
511            .unwrap();
512
513        let array = builder.finish();
514        let large_string_array = array.as_any().downcast_ref::<LargeStringArray>().unwrap();
515        assert!(large_string_array.value(0).contains("BIGINT"));
516    }
517
518    // ═══════════════════════════════════════════════════════════════════════════
519    // BinaryBuilderWrapper Tests
520    // ═══════════════════════════════════════════════════════════════════════════
521
522    #[test]
523    fn test_binary_builder_new() {
524        let builder = BinaryBuilderWrapper::new(10, 100);
525        assert_eq!(builder.len(), 0);
526    }
527
528    #[test]
529    fn test_binary_builder_default_capacity() {
530        let builder = BinaryBuilderWrapper::default_capacity();
531        assert_eq!(builder.len(), 0);
532    }
533
534    #[test]
535    fn test_binary_builder_append_binary() {
536        let mut builder = BinaryBuilderWrapper::new(10, 100);
537        builder
538            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3]))
539            .unwrap();
540
541        let array = builder.finish();
542        let binary_array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
543        assert_eq!(binary_array.value(0), &[1, 2, 3]);
544    }
545
546    #[test]
547    fn test_binary_builder_append_null() {
548        let mut builder = BinaryBuilderWrapper::new(10, 100);
549        builder.append_null();
550
551        let array = builder.finish();
552        let binary_array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
553        assert!(binary_array.is_null(0));
554    }
555
556    #[test]
557    fn test_binary_builder_append_geometry() {
558        let mut builder = BinaryBuilderWrapper::new(10, 100);
559        builder
560            .append_hana_value(&HdbValue::GEOMETRY(vec![0x00, 0x01, 0x02]))
561            .unwrap();
562
563        let array = builder.finish();
564        assert_eq!(array.len(), 1);
565    }
566
567    #[test]
568    fn test_binary_builder_append_point() {
569        let mut builder = BinaryBuilderWrapper::new(10, 100);
570        builder
571            .append_hana_value(&HdbValue::POINT(vec![0xAB, 0xCD]))
572            .unwrap();
573
574        let array = builder.finish();
575        assert_eq!(array.len(), 1);
576    }
577
578    #[test]
579    fn test_binary_builder_reject_string() {
580        let mut builder = BinaryBuilderWrapper::new(10, 100);
581        let result = builder.append_hana_value(&HdbValue::STRING("text".to_string()));
582        assert!(result.is_err());
583        assert!(result.unwrap_err().is_value_conversion());
584    }
585
586    #[test]
587    fn test_binary_builder_empty_binary() {
588        let mut builder = BinaryBuilderWrapper::new(10, 100);
589        builder
590            .append_hana_value(&HdbValue::BINARY(vec![]))
591            .unwrap();
592
593        let array = builder.finish();
594        let binary_array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
595        assert!(binary_array.value(0).is_empty());
596    }
597
598    // ═══════════════════════════════════════════════════════════════════════════
599    // LargeBinaryBuilderWrapper Tests
600    // ═══════════════════════════════════════════════════════════════════════════
601
602    #[test]
603    fn test_large_binary_builder_new() {
604        let builder = LargeBinaryBuilderWrapper::new(10, 1000);
605        assert_eq!(builder.len(), 0);
606    }
607
608    #[test]
609    fn test_large_binary_builder_default_capacity() {
610        let builder = LargeBinaryBuilderWrapper::default_capacity();
611        assert_eq!(builder.len(), 0);
612    }
613
614    #[test]
615    fn test_large_binary_builder_append_binary() {
616        let mut builder = LargeBinaryBuilderWrapper::new(10, 1000);
617        builder
618            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4, 5]))
619            .unwrap();
620
621        let array = builder.finish();
622        let large_binary_array = array.as_any().downcast_ref::<LargeBinaryArray>().unwrap();
623        assert_eq!(large_binary_array.value(0), &[1, 2, 3, 4, 5]);
624    }
625
626    #[test]
627    fn test_large_binary_builder_append_null() {
628        let mut builder = LargeBinaryBuilderWrapper::new(10, 1000);
629        builder.append_null();
630
631        let array = builder.finish();
632        let large_binary_array = array.as_any().downcast_ref::<LargeBinaryArray>().unwrap();
633        assert!(large_binary_array.is_null(0));
634    }
635
636    #[test]
637    fn test_large_binary_builder_reject_string() {
638        let mut builder = LargeBinaryBuilderWrapper::new(10, 1000);
639        let result = builder.append_hana_value(&HdbValue::STRING("text".to_string()));
640        assert!(result.is_err());
641        assert!(result.unwrap_err().is_value_conversion());
642    }
643
644    // ═══════════════════════════════════════════════════════════════════════════
645    // FixedSizeBinaryBuilderWrapper Tests
646    // ═══════════════════════════════════════════════════════════════════════════
647
648    #[test]
649    fn test_fixed_size_binary_builder_new() {
650        let builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
651        assert_eq!(builder.len(), 0);
652        assert_eq!(builder.byte_width, 4);
653    }
654
655    #[test]
656    fn test_fixed_size_binary_builder_correct_size() {
657        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
658        builder
659            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4]))
660            .unwrap();
661
662        let array = builder.finish();
663        let fixed_binary = array
664            .as_any()
665            .downcast_ref::<FixedSizeBinaryArray>()
666            .unwrap();
667        assert_eq!(fixed_binary.value(0), &[1, 2, 3, 4]);
668    }
669
670    #[test]
671    fn test_fixed_size_binary_builder_wrong_size_smaller() {
672        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
673        let result = builder.append_hana_value(&HdbValue::BINARY(vec![1, 2]));
674        assert!(result.is_err());
675        let err = result.unwrap_err();
676        assert!(err.is_value_conversion());
677        assert!(err.to_string().contains("expected 4 bytes"));
678    }
679
680    #[test]
681    fn test_fixed_size_binary_builder_wrong_size_larger() {
682        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
683        let result = builder.append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4, 5, 6]));
684        assert!(result.is_err());
685    }
686
687    #[test]
688    fn test_fixed_size_binary_builder_append_null() {
689        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
690        builder.append_null();
691
692        let array = builder.finish();
693        let fixed_binary = array
694            .as_any()
695            .downcast_ref::<FixedSizeBinaryArray>()
696            .unwrap();
697        assert!(fixed_binary.is_null(0));
698    }
699
700    #[test]
701    fn test_fixed_size_binary_builder_reject_non_binary() {
702        let mut builder = FixedSizeBinaryBuilderWrapper::new(10, 4);
703        let result = builder.append_hana_value(&HdbValue::INT(42));
704        assert!(result.is_err());
705        assert!(result.unwrap_err().is_value_conversion());
706    }
707
708    #[test]
709    fn test_fixed_size_binary_builder_different_widths() {
710        // Test with byte_width = 8
711        let mut builder8 = FixedSizeBinaryBuilderWrapper::new(10, 8);
712        builder8
713            .append_hana_value(&HdbValue::BINARY(vec![1, 2, 3, 4, 5, 6, 7, 8]))
714            .unwrap();
715        assert_eq!(builder8.len(), 1);
716
717        // Test with byte_width = 16
718        let mut builder16 = FixedSizeBinaryBuilderWrapper::new(10, 16);
719        builder16
720            .append_hana_value(&HdbValue::BINARY(vec![0; 16]))
721            .unwrap();
722        assert_eq!(builder16.len(), 1);
723    }
724}