Skip to main content

hdbconnect_arrow/builders/
decimal.rs

1//! Decimal128 builder with precision and scale validation.
2//!
3//! Handles HANA DECIMAL and SMALLDECIMAL types with proper precision/scale
4//! preservation using Arrow Decimal128 arrays.
5
6use std::sync::Arc;
7
8use arrow_array::ArrayRef;
9use arrow_array::builder::Decimal128Builder;
10
11use crate::Result;
12use crate::traits::builder::HanaCompatibleBuilder;
13use crate::traits::sealed::private::Sealed;
14use crate::types::hana::{DecimalPrecision, DecimalScale};
15
16/// Validated decimal configuration.
17///
18/// Ensures precision and scale are valid at construction time.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct DecimalConfig {
21    precision: DecimalPrecision,
22    scale: DecimalScale,
23}
24
25impl DecimalConfig {
26    /// Create a new decimal configuration.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if precision or scale are invalid.
31    pub fn new(precision: u8, scale: i8) -> Result<Self> {
32        let prec = DecimalPrecision::new(precision)?;
33        let scl = DecimalScale::new(scale, prec)?;
34        Ok(Self {
35            precision: prec,
36            scale: scl,
37        })
38    }
39
40    /// Returns the precision value.
41    #[must_use]
42    pub const fn precision(&self) -> u8 {
43        self.precision.value()
44    }
45
46    /// Returns the scale value.
47    #[must_use]
48    pub const fn scale(&self) -> i8 {
49        self.scale.value()
50    }
51}
52
53/// Builder for Arrow Decimal128 arrays.
54///
55/// Maintains precision and scale configuration for proper HANA DECIMAL handling.
56#[derive(Debug)]
57pub struct Decimal128BuilderWrapper {
58    builder: Decimal128Builder,
59    config: DecimalConfig,
60    len: usize,
61}
62
63impl Decimal128BuilderWrapper {
64    /// Create a new decimal builder with validated configuration.
65    ///
66    /// # Arguments
67    ///
68    /// * `capacity` - Number of decimal values to pre-allocate
69    /// * `precision` - Decimal precision (1-38)
70    /// * `scale` - Decimal scale (0 ≤ scale ≤ precision)
71    ///
72    /// # Panics
73    ///
74    /// Panics if precision or scale are invalid (should be validated before calling).
75    #[must_use]
76    pub fn new(capacity: usize, precision: u8, scale: i8) -> Self {
77        let config = DecimalConfig::new(precision, scale)
78            .expect("decimal config should be validated before builder creation");
79
80        let builder = Decimal128Builder::with_capacity(capacity)
81            .with_data_type(arrow_schema::DataType::Decimal128(precision, scale));
82
83        Self {
84            builder,
85            config,
86            len: 0,
87        }
88    }
89
90    /// Create from validated config.
91    #[must_use]
92    pub fn from_config(capacity: usize, config: DecimalConfig) -> Self {
93        let builder = Decimal128Builder::with_capacity(capacity).with_data_type(
94            arrow_schema::DataType::Decimal128(config.precision(), config.scale()),
95        );
96
97        Self {
98            builder,
99            config,
100            len: 0,
101        }
102    }
103
104    /// Convert a HANA decimal value to i128 with proper scaling.
105    ///
106    /// Uses direct `BigDecimal` arithmetic via `as_bigint_and_exponent()` to avoid
107    /// heap allocations from string parsing.
108    ///
109    /// # Implementation Note
110    ///
111    /// HANA DECIMAL values are represented as `BigDecimal` in hdbconnect.
112    /// We need to:
113    /// 1. Extract mantissa and exponent using `as_bigint_and_exponent()`
114    /// 2. Scale to match Arrow Decimal128 scale
115    /// 3. Convert to i128
116    ///
117    /// # Errors
118    ///
119    /// Returns error if value cannot be represented in Decimal128.
120    fn convert_decimal(&self, value: &hdbconnect::HdbValue) -> Result<i128> {
121        use hdbconnect::HdbValue;
122        use num_bigint::BigInt;
123        use num_traits::ToPrimitive;
124
125        match value {
126            HdbValue::DECIMAL(decimal) => {
127                let (mantissa, exponent) = decimal.as_bigint_and_exponent();
128                let target_scale = i64::from(self.config.scale());
129
130                // BigDecimal::as_bigint_and_exponent() returns (mantissa, scale) where:
131                // - mantissa is the unscaled integer representation
132                // - scale is POSITIVE for fractional parts: 123.45 -> (12345, 2) meaning:
133                //   actual_value = mantissa / 10^scale
134                // We need to rescale to target_scale for Arrow Decimal128
135                let scale_diff = target_scale - exponent;
136
137                let scaled_value = if scale_diff >= 0 {
138                    // Need to multiply by 10^scale_diff
139                    // scale_diff is bounded by max precision (38) + exponent range, fits in u32
140                    let exp = u32::try_from(scale_diff).map_err(|_| {
141                        crate::ArrowConversionError::decimal_overflow(
142                            self.config.precision(),
143                            self.config.scale(),
144                        )
145                    })?;
146                    let multiplier = BigInt::from(10_i128).pow(exp);
147                    &mantissa * &multiplier
148                } else {
149                    // Need to divide by 10^(-scale_diff) - may lose precision
150                    let exp = u32::try_from(-scale_diff).map_err(|_| {
151                        crate::ArrowConversionError::decimal_overflow(
152                            self.config.precision(),
153                            self.config.scale(),
154                        )
155                    })?;
156                    let divisor = BigInt::from(10_i128).pow(exp);
157                    &mantissa / &divisor
158                };
159
160                scaled_value.to_i128().ok_or_else(|| {
161                    crate::ArrowConversionError::decimal_overflow(
162                        self.config.precision(),
163                        self.config.scale(),
164                    )
165                })
166            }
167            other => Err(crate::ArrowConversionError::value_conversion(
168                "decimal",
169                format!("expected DECIMAL, got {:?}", std::mem::discriminant(other)),
170            )),
171        }
172    }
173}
174
175impl Sealed for Decimal128BuilderWrapper {}
176
177impl HanaCompatibleBuilder for Decimal128BuilderWrapper {
178    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
179        let i128_val = self.convert_decimal(value)?;
180        self.builder.append_value(i128_val);
181        self.len += 1;
182        Ok(())
183    }
184
185    fn append_null(&mut self) {
186        self.builder.append_null();
187        self.len += 1;
188    }
189
190    fn finish(&mut self) -> ArrayRef {
191        self.len = 0;
192        Arc::new(self.builder.finish())
193    }
194
195    fn len(&self) -> usize {
196        self.len
197    }
198
199    fn capacity(&self) -> Option<usize> {
200        Some(self.builder.capacity())
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use arrow_array::Array;
207
208    use super::*;
209
210    // ═══════════════════════════════════════════════════════════════════════════
211    // DecimalConfig Tests
212    // ═══════════════════════════════════════════════════════════════════════════
213
214    #[test]
215    fn test_decimal_config_valid() {
216        let config = DecimalConfig::new(18, 2).unwrap();
217        assert_eq!(config.precision(), 18);
218        assert_eq!(config.scale(), 2);
219    }
220
221    #[test]
222    fn test_decimal_config_invalid_precision() {
223        assert!(DecimalConfig::new(0, 0).is_err());
224        assert!(DecimalConfig::new(39, 0).is_err());
225    }
226
227    #[test]
228    fn test_decimal_config_invalid_scale() {
229        assert!(DecimalConfig::new(18, -1).is_err());
230        assert!(DecimalConfig::new(18, 20).is_err());
231    }
232
233    #[test]
234    fn test_decimal_config_min_precision() {
235        let config = DecimalConfig::new(1, 0).unwrap();
236        assert_eq!(config.precision(), 1);
237        assert_eq!(config.scale(), 0);
238    }
239
240    #[test]
241    fn test_decimal_config_max_precision() {
242        let config = DecimalConfig::new(38, 10).unwrap();
243        assert_eq!(config.precision(), 38);
244        assert_eq!(config.scale(), 10);
245    }
246
247    #[test]
248    fn test_decimal_config_scale_equals_precision() {
249        let config = DecimalConfig::new(5, 5).unwrap();
250        assert_eq!(config.precision(), 5);
251        assert_eq!(config.scale(), 5);
252    }
253
254    #[test]
255    fn test_decimal_config_zero_scale() {
256        let config = DecimalConfig::new(10, 0).unwrap();
257        assert_eq!(config.precision(), 10);
258        assert_eq!(config.scale(), 0);
259    }
260
261    #[test]
262    fn test_decimal_config_equality() {
263        let config1 = DecimalConfig::new(18, 2).unwrap();
264        let config2 = DecimalConfig::new(18, 2).unwrap();
265        let config3 = DecimalConfig::new(18, 3).unwrap();
266        assert_eq!(config1, config2);
267        assert_ne!(config1, config3);
268    }
269
270    #[test]
271    fn test_decimal_config_copy() {
272        let config1 = DecimalConfig::new(18, 2).unwrap();
273        let config2 = config1;
274        assert_eq!(config1, config2);
275    }
276
277    // ═══════════════════════════════════════════════════════════════════════════
278    // Decimal128BuilderWrapper Creation Tests
279    // ═══════════════════════════════════════════════════════════════════════════
280
281    #[test]
282    fn test_decimal_builder_creation() {
283        let builder = Decimal128BuilderWrapper::new(100, 18, 2);
284        assert_eq!(builder.len(), 0);
285        assert_eq!(builder.config.precision(), 18);
286        assert_eq!(builder.config.scale(), 2);
287    }
288
289    #[test]
290    fn test_decimal_builder_from_config() {
291        let config = DecimalConfig::new(10, 4).unwrap();
292        let builder = Decimal128BuilderWrapper::from_config(50, config);
293        assert_eq!(builder.len(), 0);
294        assert_eq!(builder.config.precision(), 10);
295        assert_eq!(builder.config.scale(), 4);
296    }
297
298    #[test]
299    fn test_decimal_builder_capacity() {
300        let builder = Decimal128BuilderWrapper::new(100, 18, 2);
301        assert!(builder.capacity().is_some());
302    }
303
304    #[test]
305    fn test_decimal_builder_is_empty() {
306        let builder = Decimal128BuilderWrapper::new(10, 18, 2);
307        assert!(builder.is_empty());
308    }
309
310    // ═══════════════════════════════════════════════════════════════════════════
311    // Null Handling Tests
312    // ═══════════════════════════════════════════════════════════════════════════
313
314    #[test]
315    fn test_decimal_builder_append_null() {
316        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
317        builder.append_null();
318        assert_eq!(builder.len(), 1);
319
320        let array = builder.finish();
321        assert!(array.is_null(0));
322    }
323
324    #[test]
325    fn test_decimal_builder_multiple_nulls() {
326        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
327        builder.append_null();
328        builder.append_null();
329        builder.append_null();
330        assert_eq!(builder.len(), 3);
331
332        let array = builder.finish();
333        assert_eq!(array.len(), 3);
334        assert!(array.is_null(0));
335        assert!(array.is_null(1));
336        assert!(array.is_null(2));
337    }
338
339    // ═══════════════════════════════════════════════════════════════════════════
340    // Finish and Reset Tests
341    // ═══════════════════════════════════════════════════════════════════════════
342
343    #[test]
344    fn test_decimal_builder_finish_resets_len() {
345        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
346        builder.append_null();
347        builder.append_null();
348        assert_eq!(builder.len(), 2);
349
350        let _ = builder.finish();
351        assert_eq!(builder.len(), 0);
352    }
353
354    #[test]
355    fn test_decimal_builder_finish_empty() {
356        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
357        let array = builder.finish();
358        assert_eq!(array.len(), 0);
359    }
360
361    #[test]
362    fn test_decimal_builder_reuse_after_finish() {
363        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
364        builder.append_null();
365        let array1 = builder.finish();
366        assert_eq!(array1.len(), 1);
367
368        builder.append_null();
369        builder.append_null();
370        let array2 = builder.finish();
371        assert_eq!(array2.len(), 2);
372    }
373
374    // ═══════════════════════════════════════════════════════════════════════════
375    // Different Precision/Scale Combinations
376    // ═══════════════════════════════════════════════════════════════════════════
377
378    #[test]
379    fn test_decimal_builder_high_precision() {
380        let builder = Decimal128BuilderWrapper::new(10, 38, 10);
381        assert_eq!(builder.config.precision(), 38);
382        assert_eq!(builder.config.scale(), 10);
383    }
384
385    #[test]
386    fn test_decimal_builder_low_precision() {
387        let builder = Decimal128BuilderWrapper::new(10, 1, 0);
388        assert_eq!(builder.config.precision(), 1);
389        assert_eq!(builder.config.scale(), 0);
390    }
391
392    #[test]
393    fn test_decimal_builder_zero_scale() {
394        let builder = Decimal128BuilderWrapper::new(10, 10, 0);
395        assert_eq!(builder.config.precision(), 10);
396        assert_eq!(builder.config.scale(), 0);
397    }
398
399    #[test]
400    fn test_decimal_builder_scale_equals_precision() {
401        let builder = Decimal128BuilderWrapper::new(10, 5, 5);
402        assert_eq!(builder.config.precision(), 5);
403        assert_eq!(builder.config.scale(), 5);
404    }
405
406    // ═══════════════════════════════════════════════════════════════════════════
407    // HanaCompatibleBuilder trait Tests
408    // ═══════════════════════════════════════════════════════════════════════════
409
410    #[test]
411    fn test_decimal_builder_len_increments() {
412        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
413        assert_eq!(builder.len(), 0);
414        builder.append_null();
415        assert_eq!(builder.len(), 1);
416        builder.append_null();
417        assert_eq!(builder.len(), 2);
418    }
419
420    #[test]
421    fn test_decimal_builder_reset() {
422        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
423        builder.append_null();
424        builder.append_null();
425        assert_eq!(builder.len(), 2);
426
427        builder.reset();
428        assert_eq!(builder.len(), 0);
429        assert!(builder.is_empty());
430    }
431
432    // ═══════════════════════════════════════════════════════════════════════════
433    // convert_decimal Tests (CRIT-001)
434    // ═══════════════════════════════════════════════════════════════════════════
435
436    #[cfg(feature = "test-utils")]
437    mod convert_decimal_tests {
438        use arrow_array::Decimal128Array;
439
440        use super::*;
441        use crate::traits::row::MockRowBuilder;
442
443        /// Helper to extract i128 value from builder after appending decimal
444        fn convert_and_get_value(precision: u8, scale: i8, decimal_str: &str) -> i128 {
445            let mut builder = Decimal128BuilderWrapper::new(10, precision, scale);
446            let row = MockRowBuilder::new().decimal_str(decimal_str).build();
447            builder.append_hana_value(&row[0]).unwrap();
448            let array = builder.finish();
449            let decimal_array = array.as_any().downcast_ref::<Decimal128Array>().unwrap();
450            decimal_array.value(0)
451        }
452
453        // ───────────────────────────────────────────────────────────────────────
454        // Basic decimal conversion tests
455        // ───────────────────────────────────────────────────────────────────────
456
457        #[test]
458        fn test_convert_decimal_simple_value() {
459            // 123.45 with scale=2 should store as 12345
460            let value = convert_and_get_value(18, 2, "123.45");
461            assert_eq!(value, 12345);
462        }
463
464        #[test]
465        fn test_convert_decimal_integer_value() {
466            // 100 with scale=0 should store as 100
467            let value = convert_and_get_value(10, 0, "100");
468            assert_eq!(value, 100);
469        }
470
471        #[test]
472        fn test_convert_decimal_large_value() {
473            // 9999999.99 with scale=2 should store as 999999999
474            let value = convert_and_get_value(18, 2, "9999999.99");
475            assert_eq!(value, 999999999);
476        }
477
478        // ───────────────────────────────────────────────────────────────────────
479        // Scale-up tests (target_scale > source_scale)
480        // ───────────────────────────────────────────────────────────────────────
481
482        #[test]
483        fn test_convert_decimal_scale_up_integer_to_decimal() {
484            // 100 (scale=0) to scale=2 should become 10000 (100.00)
485            let value = convert_and_get_value(10, 2, "100");
486            assert_eq!(value, 10000);
487        }
488
489        #[test]
490        fn test_convert_decimal_scale_up_by_one() {
491            // 123.4 (scale=1) to scale=2 should become 12340 (123.40)
492            let value = convert_and_get_value(10, 2, "123.4");
493            assert_eq!(value, 12340);
494        }
495
496        #[test]
497        fn test_convert_decimal_scale_up_by_multiple() {
498            // 5 (scale=0) to scale=4 should become 50000 (5.0000)
499            let value = convert_and_get_value(10, 4, "5");
500            assert_eq!(value, 50000);
501        }
502
503        // ───────────────────────────────────────────────────────────────────────
504        // Scale-down tests (target_scale < source_scale, may lose precision)
505        // ───────────────────────────────────────────────────────────────────────
506
507        #[test]
508        fn test_convert_decimal_scale_down_truncate() {
509            // 123.456 (scale=3) to scale=2 should become 12345 (123.45, truncated)
510            let value = convert_and_get_value(10, 2, "123.456");
511            assert_eq!(value, 12345);
512        }
513
514        #[test]
515        fn test_convert_decimal_scale_down_to_integer() {
516            // 123.99 (scale=2) to scale=0 should become 123 (truncated)
517            let value = convert_and_get_value(10, 0, "123.99");
518            assert_eq!(value, 123);
519        }
520
521        #[test]
522        fn test_convert_decimal_scale_down_multiple_places() {
523            // 1.23456789 (scale=8) to scale=2 should become 123 (1.23)
524            let value = convert_and_get_value(18, 2, "1.23456789");
525            assert_eq!(value, 123);
526        }
527
528        // ───────────────────────────────────────────────────────────────────────
529        // Scale-match tests (target_scale == source_scale)
530        // ───────────────────────────────────────────────────────────────────────
531
532        #[test]
533        fn test_convert_decimal_scale_match_exact() {
534            // 123.45 (scale=2) to scale=2 should be 12345 (no change)
535            let value = convert_and_get_value(18, 2, "123.45");
536            assert_eq!(value, 12345);
537        }
538
539        #[test]
540        fn test_convert_decimal_scale_match_high_scale() {
541            // 1.234567890123456789 (scale=18) to scale=18
542            let value = convert_and_get_value(38, 18, "1.234567890123456789");
543            assert_eq!(value, 1_234_567_890_123_456_789_i128);
544        }
545
546        // ───────────────────────────────────────────────────────────────────────
547        // Negative value tests
548        // ───────────────────────────────────────────────────────────────────────
549
550        #[test]
551        fn test_convert_decimal_negative_simple() {
552            // -123.45 with scale=2 should store as -12345
553            let value = convert_and_get_value(18, 2, "-123.45");
554            assert_eq!(value, -12345);
555        }
556
557        #[test]
558        fn test_convert_decimal_negative_scale_up() {
559            // -100 (scale=0) to scale=2 should become -10000
560            let value = convert_and_get_value(10, 2, "-100");
561            assert_eq!(value, -10000);
562        }
563
564        #[test]
565        fn test_convert_decimal_negative_scale_down() {
566            // -123.456 (scale=3) to scale=2 should become -12345 (truncated toward zero)
567            let value = convert_and_get_value(10, 2, "-123.456");
568            assert_eq!(value, -12345);
569        }
570
571        #[test]
572        fn test_convert_decimal_negative_large() {
573            // -9999999.99 with scale=2
574            let value = convert_and_get_value(18, 2, "-9999999.99");
575            assert_eq!(value, -999999999);
576        }
577
578        // ───────────────────────────────────────────────────────────────────────
579        // Zero value tests
580        // ───────────────────────────────────────────────────────────────────────
581
582        #[test]
583        fn test_convert_decimal_zero() {
584            let value = convert_and_get_value(10, 2, "0");
585            assert_eq!(value, 0);
586        }
587
588        #[test]
589        fn test_convert_decimal_zero_with_scale() {
590            // 0.00 with scale=2 should store as 0
591            let value = convert_and_get_value(10, 2, "0.00");
592            assert_eq!(value, 0);
593        }
594
595        #[test]
596        fn test_convert_decimal_negative_zero() {
597            // -0 should still be 0
598            let value = convert_and_get_value(10, 2, "-0");
599            assert_eq!(value, 0);
600        }
601
602        // ───────────────────────────────────────────────────────────────────────
603        // Maximum precision (38-digit) tests
604        // ───────────────────────────────────────────────────────────────────────
605
606        #[test]
607        fn test_convert_decimal_max_precision_integer() {
608            // Maximum 38-digit integer that fits in i128 (precision=38, scale=0)
609            // i128::MAX is 170141183460469231731687303715884105727 (39 digits)
610            // We use a 38-digit number
611            let value = convert_and_get_value(38, 0, "99999999999999999999999999999999999999");
612            assert_eq!(
613                value,
614                99_999_999_999_999_999_999_999_999_999_999_999_999_i128
615            );
616        }
617
618        #[test]
619        fn test_convert_decimal_max_precision_with_scale() {
620            // 38-digit decimal with scale: 9999999999999999999999999999999999.9999
621            // stored as 99999999999999999999999999999999999999 with scale=4
622            let value = convert_and_get_value(38, 4, "9999999999999999999999999999999999.9999");
623            assert_eq!(
624                value,
625                99_999_999_999_999_999_999_999_999_999_999_999_999_i128
626            );
627        }
628
629        #[test]
630        fn test_convert_decimal_i128_max_boundary() {
631            // Test value close to i128::MAX
632            // i128::MAX = 170141183460469231731687303715884105727
633            let value = convert_and_get_value(38, 0, "170141183460469231731687303715884105727");
634            assert_eq!(value, i128::MAX);
635        }
636
637        #[test]
638        fn test_convert_decimal_i128_min_boundary() {
639            // Test value close to i128::MIN
640            // i128::MIN = -170141183460469231731687303715884105728
641            let value = convert_and_get_value(38, 0, "-170141183460469231731687303715884105728");
642            assert_eq!(value, i128::MIN);
643        }
644
645        // ───────────────────────────────────────────────────────────────────────
646        // Overflow detection tests
647        // ───────────────────────────────────────────────────────────────────────
648
649        #[test]
650        fn test_convert_decimal_overflow_scale_up() {
651            // Scale up operation that causes overflow:
652            // i128::MAX with additional scaling would overflow
653            let mut builder = Decimal128BuilderWrapper::new(10, 38, 10);
654            // Create a value that when scaled up by 10^10, exceeds i128::MAX
655            let row = MockRowBuilder::new()
656                .decimal_str("17014118346046923173168730371588410573")
657                .build();
658            let result = builder.append_hana_value(&row[0]);
659            assert!(result.is_err());
660        }
661
662        #[test]
663        fn test_convert_decimal_overflow_large_value() {
664            // Value already exceeding i128 range after scaling
665            let mut builder = Decimal128BuilderWrapper::new(10, 38, 0);
666            // This is larger than i128::MAX
667            let row = MockRowBuilder::new()
668                .decimal_str("999999999999999999999999999999999999999")
669                .build();
670            let result = builder.append_hana_value(&row[0]);
671            assert!(result.is_err());
672        }
673
674        // ───────────────────────────────────────────────────────────────────────
675        // Wrong type error handling tests
676        // ───────────────────────────────────────────────────────────────────────
677
678        #[test]
679        fn test_convert_decimal_wrong_type_int() {
680            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
681            let row = MockRowBuilder::new().int(42).build();
682            let result = builder.append_hana_value(&row[0]);
683            assert!(result.is_err());
684        }
685
686        #[test]
687        fn test_convert_decimal_wrong_type_string() {
688            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
689            let row = MockRowBuilder::new().string("123.45").build();
690            let result = builder.append_hana_value(&row[0]);
691            assert!(result.is_err());
692        }
693
694        #[test]
695        fn test_convert_decimal_wrong_type_null() {
696            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
697            let row = MockRowBuilder::new().null().build();
698            let result = builder.append_hana_value(&row[0]);
699            // NULL should be handled via append_null, not convert_decimal
700            // append_hana_value with NULL should fail
701            assert!(result.is_err());
702        }
703
704        #[test]
705        fn test_convert_decimal_wrong_type_double() {
706            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
707            let row = MockRowBuilder::new().double(123.45).build();
708            let result = builder.append_hana_value(&row[0]);
709            assert!(result.is_err());
710        }
711
712        // ───────────────────────────────────────────────────────────────────────
713        // Additional edge cases
714        // ───────────────────────────────────────────────────────────────────────
715
716        #[test]
717        fn test_convert_decimal_very_small_value() {
718            // 0.00000001 with scale=8 should be 1
719            let value = convert_and_get_value(18, 8, "0.00000001");
720            assert_eq!(value, 1);
721        }
722
723        #[test]
724        fn test_convert_decimal_leading_zeros() {
725            // 00123.45 should be same as 123.45
726            let value = convert_and_get_value(18, 2, "00123.45");
727            assert_eq!(value, 12345);
728        }
729
730        #[test]
731        fn test_convert_decimal_trailing_zeros() {
732            // 123.450 should be same as 123.45 when target scale is 2
733            let value = convert_and_get_value(18, 2, "123.450");
734            assert_eq!(value, 12345);
735        }
736
737        #[test]
738        fn test_convert_decimal_scientific_notation() {
739            // 1.23e2 = 123 with scale=0
740            let value = convert_and_get_value(10, 0, "1.23e2");
741            assert_eq!(value, 123);
742        }
743
744        #[test]
745        fn test_convert_decimal_scientific_notation_negative_exp() {
746            // 12300e-2 = 123 with scale=0
747            let value = convert_and_get_value(10, 0, "12300e-2");
748            assert_eq!(value, 123);
749        }
750    }
751}