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, LazyLock};
7
8use arrow_array::ArrayRef;
9use arrow_array::builder::Decimal128Builder;
10use num_bigint::BigInt;
11
12use crate::Result;
13use crate::traits::builder::HanaCompatibleBuilder;
14use crate::traits::sealed::private::Sealed;
15use crate::types::hana::{DecimalPrecision, DecimalScale};
16
17// Pre-computed powers of 10 as i128 for O(1) lookup in fast path.
18// Covers 10^0 through 10^38 (39 elements), sufficient for max DECIMAL precision.
19const POWERS_OF_10_I128: [i128; 39] = [
20    1,                                                   // 10^0
21    10,                                                  // 10^1
22    100,                                                 // 10^2
23    1_000,                                               // 10^3
24    10_000,                                              // 10^4
25    100_000,                                             // 10^5
26    1_000_000,                                           // 10^6
27    10_000_000,                                          // 10^7
28    100_000_000,                                         // 10^8
29    1_000_000_000,                                       // 10^9
30    10_000_000_000,                                      // 10^10
31    100_000_000_000,                                     // 10^11
32    1_000_000_000_000,                                   // 10^12
33    10_000_000_000_000,                                  // 10^13
34    100_000_000_000_000,                                 // 10^14
35    1_000_000_000_000_000,                               // 10^15
36    10_000_000_000_000_000,                              // 10^16
37    100_000_000_000_000_000,                             // 10^17
38    1_000_000_000_000_000_000,                           // 10^18
39    10_000_000_000_000_000_000,                          // 10^19
40    100_000_000_000_000_000_000,                         // 10^20
41    1_000_000_000_000_000_000_000,                       // 10^21
42    10_000_000_000_000_000_000_000,                      // 10^22
43    100_000_000_000_000_000_000_000,                     // 10^23
44    1_000_000_000_000_000_000_000_000,                   // 10^24
45    10_000_000_000_000_000_000_000_000,                  // 10^25
46    100_000_000_000_000_000_000_000_000,                 // 10^26
47    1_000_000_000_000_000_000_000_000_000,               // 10^27
48    10_000_000_000_000_000_000_000_000_000,              // 10^28
49    100_000_000_000_000_000_000_000_000_000,             // 10^29
50    1_000_000_000_000_000_000_000_000_000_000,           // 10^30
51    10_000_000_000_000_000_000_000_000_000_000,          // 10^31
52    100_000_000_000_000_000_000_000_000_000_000,         // 10^32
53    1_000_000_000_000_000_000_000_000_000_000_000,       // 10^33
54    10_000_000_000_000_000_000_000_000_000_000_000,      // 10^34
55    100_000_000_000_000_000_000_000_000_000_000_000,     // 10^35
56    1_000_000_000_000_000_000_000_000_000_000_000_000,   // 10^36
57    10_000_000_000_000_000_000_000_000_000_000_000_000,  // 10^37
58    100_000_000_000_000_000_000_000_000_000_000_000_000, // 10^38
59];
60
61// Pre-computed powers of 10 as BigInt for slow path fallback.
62// Initialized lazily on first access, thread-safe via LazyLock.
63static POWERS_OF_10_BIGINT: LazyLock<[BigInt; 39]> =
64    LazyLock::new(|| std::array::from_fn(|i| BigInt::from(POWERS_OF_10_I128[i])));
65
66/// Fast path: multiply mantissa by power of 10 using i128 arithmetic.
67/// Returns `None` if `scale_diff` is out of range or multiplication overflows.
68#[inline]
69fn scale_up_i128(mantissa: i128, scale_diff: i64) -> Option<i128> {
70    let idx = usize::try_from(scale_diff).ok()?;
71    let multiplier = POWERS_OF_10_I128.get(idx)?;
72    mantissa.checked_mul(*multiplier)
73}
74
75/// Fast path: divide mantissa by power of 10 using i128 arithmetic.
76/// Returns `None` if `scale_diff` is out of range.
77#[inline]
78fn scale_down_i128(mantissa: i128, scale_diff: i64) -> Option<i128> {
79    let idx = usize::try_from(scale_diff).ok()?;
80    let divisor = POWERS_OF_10_I128.get(idx)?;
81    Some(mantissa / divisor)
82}
83
84/// Slow path: scale up using `BigInt` arithmetic with pre-computed powers.
85fn scale_up_bigint(mantissa: &BigInt, scale_diff: i64) -> BigInt {
86    let exp = usize::try_from(scale_diff).unwrap_or(0).min(38);
87    mantissa * &POWERS_OF_10_BIGINT[exp]
88}
89
90/// Slow path: scale down using `BigInt` arithmetic with pre-computed powers.
91fn scale_down_bigint(mantissa: &BigInt, scale_diff: i64) -> BigInt {
92    let exp = usize::try_from(scale_diff).unwrap_or(0).min(38);
93    mantissa / &POWERS_OF_10_BIGINT[exp]
94}
95
96/// Validated decimal configuration.
97///
98/// Ensures precision and scale are valid at construction time.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct DecimalConfig {
101    precision: DecimalPrecision,
102    scale: DecimalScale,
103}
104
105impl DecimalConfig {
106    /// Create a new decimal configuration.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if precision or scale are invalid.
111    pub fn new(precision: u8, scale: i8) -> Result<Self> {
112        let prec = DecimalPrecision::new(precision)?;
113        let scl = DecimalScale::new(scale, prec)?;
114        Ok(Self {
115            precision: prec,
116            scale: scl,
117        })
118    }
119
120    /// Returns the precision value.
121    #[must_use]
122    pub const fn precision(&self) -> u8 {
123        self.precision.value()
124    }
125
126    /// Returns the scale value.
127    #[must_use]
128    pub const fn scale(&self) -> i8 {
129        self.scale.value()
130    }
131}
132
133/// Builder for Arrow Decimal128 arrays.
134///
135/// Maintains precision and scale configuration for proper HANA DECIMAL handling.
136#[derive(Debug)]
137pub struct Decimal128BuilderWrapper {
138    builder: Decimal128Builder,
139    config: DecimalConfig,
140    len: usize,
141}
142
143impl Decimal128BuilderWrapper {
144    /// Create a new decimal builder with validated configuration.
145    ///
146    /// # Arguments
147    ///
148    /// * `capacity` - Number of decimal values to pre-allocate
149    /// * `precision` - Decimal precision (1-38)
150    /// * `scale` - Decimal scale (0 ≤ scale ≤ precision)
151    ///
152    /// # Panics
153    ///
154    /// Panics if precision or scale are invalid (should be validated before calling).
155    #[must_use]
156    pub fn new(capacity: usize, precision: u8, scale: i8) -> Self {
157        let config = DecimalConfig::new(precision, scale)
158            .expect("decimal config should be validated before builder creation");
159
160        let builder = Decimal128Builder::with_capacity(capacity)
161            .with_data_type(arrow_schema::DataType::Decimal128(precision, scale));
162
163        Self {
164            builder,
165            config,
166            len: 0,
167        }
168    }
169
170    /// Create from validated config.
171    #[must_use]
172    pub fn from_config(capacity: usize, config: DecimalConfig) -> Self {
173        let builder = Decimal128Builder::with_capacity(capacity).with_data_type(
174            arrow_schema::DataType::Decimal128(config.precision(), config.scale()),
175        );
176
177        Self {
178            builder,
179            config,
180            len: 0,
181        }
182    }
183
184    /// Slow path: `BigInt` arithmetic with pre-computed powers lookup.
185    /// Used when mantissa exceeds i128 range or fast path overflows.
186    fn convert_decimal_slow(&self, mantissa: &BigInt, scale_diff: i64) -> Result<i128> {
187        use num_traits::ToPrimitive;
188
189        let scaled_value = if scale_diff >= 0 {
190            scale_up_bigint(mantissa, scale_diff)
191        } else {
192            scale_down_bigint(mantissa, -scale_diff)
193        };
194
195        scaled_value.to_i128().ok_or_else(|| {
196            crate::ArrowConversionError::decimal_overflow(
197                self.config.precision(),
198                self.config.scale(),
199            )
200        })
201    }
202
203    /// Convert a HANA decimal value to i128 with proper scaling.
204    ///
205    /// Uses fast path (i128 arithmetic) when possible, falling back to
206    /// `BigInt` arithmetic for edge cases (mantissa exceeds i128 or overflow).
207    ///
208    /// # Errors
209    ///
210    /// Returns error if value cannot be represented in Decimal128.
211    fn convert_decimal(&self, value: &hdbconnect::HdbValue) -> Result<i128> {
212        use hdbconnect::HdbValue;
213        use num_traits::ToPrimitive;
214
215        match value {
216            HdbValue::DECIMAL(decimal) => {
217                // Zero-copy mantissa extraction via Cow::Borrowed.
218                // as_bigint_and_scale() returns (Cow<'_, BigInt>, i64) which borrows
219                // the mantissa instead of cloning it. Cow<BigInt> automatically derefs
220                // to &BigInt when needed, providing transparent zero-cost abstraction.
221                let (mantissa, exponent) = decimal.as_bigint_and_scale();
222                let target_scale = i64::from(self.config.scale());
223                let scale_diff = target_scale - exponent;
224
225                // Fast path: try i128 arithmetic first (covers >99% of cases)
226                if let Some(m) = mantissa.to_i128() {
227                    if scale_diff >= 0 {
228                        if let Some(result) = scale_up_i128(m, scale_diff) {
229                            return Ok(result);
230                        }
231                    } else if let Some(result) = scale_down_i128(m, -scale_diff) {
232                        return Ok(result);
233                    }
234                }
235
236                // Slow path: BigInt arithmetic with cached powers
237                // Cow<BigInt> derefs to &BigInt automatically
238                self.convert_decimal_slow(&mantissa, scale_diff)
239            }
240            other => Err(crate::ArrowConversionError::value_conversion(
241                "decimal",
242                format!("expected DECIMAL, got {:?}", std::mem::discriminant(other)),
243            )),
244        }
245    }
246}
247
248impl Sealed for Decimal128BuilderWrapper {}
249
250impl HanaCompatibleBuilder for Decimal128BuilderWrapper {
251    fn append_hana_value(&mut self, value: &hdbconnect::HdbValue) -> Result<()> {
252        let i128_val = self.convert_decimal(value)?;
253        self.builder.append_value(i128_val);
254        self.len += 1;
255        Ok(())
256    }
257
258    fn append_null(&mut self) {
259        self.builder.append_null();
260        self.len += 1;
261    }
262
263    fn finish(&mut self) -> ArrayRef {
264        self.len = 0;
265        Arc::new(self.builder.finish())
266    }
267
268    fn len(&self) -> usize {
269        self.len
270    }
271
272    fn capacity(&self) -> Option<usize> {
273        Some(self.builder.capacity())
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use arrow_array::Array;
280
281    use super::*;
282
283    // ═══════════════════════════════════════════════════════════════════════════
284    // DecimalConfig Tests
285    // ═══════════════════════════════════════════════════════════════════════════
286
287    #[test]
288    fn test_decimal_config_valid() {
289        let config = DecimalConfig::new(18, 2).unwrap();
290        assert_eq!(config.precision(), 18);
291        assert_eq!(config.scale(), 2);
292    }
293
294    #[test]
295    fn test_decimal_config_invalid_precision() {
296        assert!(DecimalConfig::new(0, 0).is_err());
297        assert!(DecimalConfig::new(39, 0).is_err());
298    }
299
300    #[test]
301    fn test_decimal_config_invalid_scale() {
302        assert!(DecimalConfig::new(18, -1).is_err());
303        assert!(DecimalConfig::new(18, 20).is_err());
304    }
305
306    #[test]
307    fn test_decimal_config_min_precision() {
308        let config = DecimalConfig::new(1, 0).unwrap();
309        assert_eq!(config.precision(), 1);
310        assert_eq!(config.scale(), 0);
311    }
312
313    #[test]
314    fn test_decimal_config_max_precision() {
315        let config = DecimalConfig::new(38, 10).unwrap();
316        assert_eq!(config.precision(), 38);
317        assert_eq!(config.scale(), 10);
318    }
319
320    #[test]
321    fn test_decimal_config_scale_equals_precision() {
322        let config = DecimalConfig::new(5, 5).unwrap();
323        assert_eq!(config.precision(), 5);
324        assert_eq!(config.scale(), 5);
325    }
326
327    #[test]
328    fn test_decimal_config_zero_scale() {
329        let config = DecimalConfig::new(10, 0).unwrap();
330        assert_eq!(config.precision(), 10);
331        assert_eq!(config.scale(), 0);
332    }
333
334    #[test]
335    fn test_decimal_config_equality() {
336        let config1 = DecimalConfig::new(18, 2).unwrap();
337        let config2 = DecimalConfig::new(18, 2).unwrap();
338        let config3 = DecimalConfig::new(18, 3).unwrap();
339        assert_eq!(config1, config2);
340        assert_ne!(config1, config3);
341    }
342
343    #[test]
344    fn test_decimal_config_copy() {
345        let config1 = DecimalConfig::new(18, 2).unwrap();
346        let config2 = config1;
347        assert_eq!(config1, config2);
348    }
349
350    // ═══════════════════════════════════════════════════════════════════════════
351    // Decimal128BuilderWrapper Creation Tests
352    // ═══════════════════════════════════════════════════════════════════════════
353
354    #[test]
355    fn test_decimal_builder_creation() {
356        let builder = Decimal128BuilderWrapper::new(100, 18, 2);
357        assert_eq!(builder.len(), 0);
358        assert_eq!(builder.config.precision(), 18);
359        assert_eq!(builder.config.scale(), 2);
360    }
361
362    #[test]
363    fn test_decimal_builder_from_config() {
364        let config = DecimalConfig::new(10, 4).unwrap();
365        let builder = Decimal128BuilderWrapper::from_config(50, config);
366        assert_eq!(builder.len(), 0);
367        assert_eq!(builder.config.precision(), 10);
368        assert_eq!(builder.config.scale(), 4);
369    }
370
371    #[test]
372    fn test_decimal_builder_capacity() {
373        let builder = Decimal128BuilderWrapper::new(100, 18, 2);
374        assert!(builder.capacity().is_some());
375    }
376
377    #[test]
378    fn test_decimal_builder_is_empty() {
379        let builder = Decimal128BuilderWrapper::new(10, 18, 2);
380        assert!(builder.is_empty());
381    }
382
383    // ═══════════════════════════════════════════════════════════════════════════
384    // Null Handling Tests
385    // ═══════════════════════════════════════════════════════════════════════════
386
387    #[test]
388    fn test_decimal_builder_append_null() {
389        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
390        builder.append_null();
391        assert_eq!(builder.len(), 1);
392
393        let array = builder.finish();
394        assert!(array.is_null(0));
395    }
396
397    #[test]
398    fn test_decimal_builder_multiple_nulls() {
399        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
400        builder.append_null();
401        builder.append_null();
402        builder.append_null();
403        assert_eq!(builder.len(), 3);
404
405        let array = builder.finish();
406        assert_eq!(array.len(), 3);
407        assert!(array.is_null(0));
408        assert!(array.is_null(1));
409        assert!(array.is_null(2));
410    }
411
412    // ═══════════════════════════════════════════════════════════════════════════
413    // Finish and Reset Tests
414    // ═══════════════════════════════════════════════════════════════════════════
415
416    #[test]
417    fn test_decimal_builder_finish_resets_len() {
418        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
419        builder.append_null();
420        builder.append_null();
421        assert_eq!(builder.len(), 2);
422
423        let _ = builder.finish();
424        assert_eq!(builder.len(), 0);
425    }
426
427    #[test]
428    fn test_decimal_builder_finish_empty() {
429        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
430        let array = builder.finish();
431        assert_eq!(array.len(), 0);
432    }
433
434    #[test]
435    fn test_decimal_builder_reuse_after_finish() {
436        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
437        builder.append_null();
438        let array1 = builder.finish();
439        assert_eq!(array1.len(), 1);
440
441        builder.append_null();
442        builder.append_null();
443        let array2 = builder.finish();
444        assert_eq!(array2.len(), 2);
445    }
446
447    // ═══════════════════════════════════════════════════════════════════════════
448    // Different Precision/Scale Combinations
449    // ═══════════════════════════════════════════════════════════════════════════
450
451    #[test]
452    fn test_decimal_builder_high_precision() {
453        let builder = Decimal128BuilderWrapper::new(10, 38, 10);
454        assert_eq!(builder.config.precision(), 38);
455        assert_eq!(builder.config.scale(), 10);
456    }
457
458    #[test]
459    fn test_decimal_builder_low_precision() {
460        let builder = Decimal128BuilderWrapper::new(10, 1, 0);
461        assert_eq!(builder.config.precision(), 1);
462        assert_eq!(builder.config.scale(), 0);
463    }
464
465    #[test]
466    fn test_decimal_builder_zero_scale() {
467        let builder = Decimal128BuilderWrapper::new(10, 10, 0);
468        assert_eq!(builder.config.precision(), 10);
469        assert_eq!(builder.config.scale(), 0);
470    }
471
472    #[test]
473    fn test_decimal_builder_scale_equals_precision() {
474        let builder = Decimal128BuilderWrapper::new(10, 5, 5);
475        assert_eq!(builder.config.precision(), 5);
476        assert_eq!(builder.config.scale(), 5);
477    }
478
479    // ═══════════════════════════════════════════════════════════════════════════
480    // HanaCompatibleBuilder trait Tests
481    // ═══════════════════════════════════════════════════════════════════════════
482
483    #[test]
484    fn test_decimal_builder_len_increments() {
485        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
486        assert_eq!(builder.len(), 0);
487        builder.append_null();
488        assert_eq!(builder.len(), 1);
489        builder.append_null();
490        assert_eq!(builder.len(), 2);
491    }
492
493    #[test]
494    fn test_decimal_builder_reset() {
495        let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
496        builder.append_null();
497        builder.append_null();
498        assert_eq!(builder.len(), 2);
499
500        builder.reset();
501        assert_eq!(builder.len(), 0);
502        assert!(builder.is_empty());
503    }
504
505    // ═══════════════════════════════════════════════════════════════════════════
506    // Powers of 10 Lookup Table Tests
507    // ═══════════════════════════════════════════════════════════════════════════
508
509    #[test]
510    fn test_powers_of_10_i128_correctness() {
511        for (i, &power) in POWERS_OF_10_I128.iter().enumerate() {
512            let expected = 10_i128.pow(i as u32);
513            assert_eq!(power, expected, "POWERS_OF_10_I128[{i}] mismatch");
514        }
515    }
516
517    #[test]
518    fn test_powers_of_10_bigint_correctness() {
519        for (i, power) in POWERS_OF_10_BIGINT.iter().enumerate() {
520            let expected = BigInt::from(10_i128).pow(i as u32);
521            assert_eq!(power, &expected, "POWERS_OF_10_BIGINT[{i}] mismatch");
522        }
523    }
524
525    #[test]
526    fn test_powers_of_10_i128_boundary_values() {
527        assert_eq!(POWERS_OF_10_I128[0], 1);
528        assert_eq!(
529            POWERS_OF_10_I128[38],
530            100_000_000_000_000_000_000_000_000_000_000_000_000
531        );
532    }
533
534    // ═══════════════════════════════════════════════════════════════════════════
535    // Fast Path Method Tests
536    // ═══════════════════════════════════════════════════════════════════════════
537
538    #[test]
539    fn test_scale_up_i128_simple() {
540        assert_eq!(scale_up_i128(100, 2), Some(10000));
541    }
542
543    #[test]
544    fn test_scale_up_i128_zero_scale_diff() {
545        assert_eq!(scale_up_i128(12345, 0), Some(12345));
546    }
547
548    #[test]
549    fn test_scale_up_i128_max_scale_diff() {
550        assert_eq!(scale_up_i128(1, 38), Some(POWERS_OF_10_I128[38]));
551    }
552
553    #[test]
554    fn test_scale_up_i128_overflow() {
555        // i128::MAX * 10 would overflow
556        assert_eq!(scale_up_i128(i128::MAX, 1), None);
557    }
558
559    #[test]
560    fn test_scale_up_i128_negative_scale_diff() {
561        assert_eq!(scale_up_i128(100, -1), None);
562    }
563
564    #[test]
565    fn test_scale_up_i128_out_of_range_scale_diff() {
566        assert_eq!(scale_up_i128(1, 39), None);
567    }
568
569    #[test]
570    fn test_scale_down_i128_simple() {
571        assert_eq!(scale_down_i128(12345, 2), Some(123));
572    }
573
574    #[test]
575    fn test_scale_down_i128_zero_scale_diff() {
576        assert_eq!(scale_down_i128(12345, 0), Some(12345));
577    }
578
579    #[test]
580    fn test_scale_down_i128_truncation() {
581        assert_eq!(scale_down_i128(12399, 2), Some(123));
582    }
583
584    #[test]
585    fn test_scale_down_i128_negative_scale_diff() {
586        assert_eq!(scale_down_i128(100, -1), None);
587    }
588
589    #[test]
590    fn test_scale_down_i128_out_of_range() {
591        assert_eq!(scale_down_i128(1, 39), None);
592    }
593
594    // ═══════════════════════════════════════════════════════════════════════════
595    // Slow Path Method Tests
596    // ═══════════════════════════════════════════════════════════════════════════
597
598    #[test]
599    fn test_convert_decimal_slow_scale_up() {
600        let builder = Decimal128BuilderWrapper::new(10, 18, 2);
601        let mantissa = BigInt::from(100_i128);
602        assert_eq!(builder.convert_decimal_slow(&mantissa, 2).unwrap(), 10000);
603    }
604
605    #[test]
606    fn test_convert_decimal_slow_scale_down() {
607        let builder = Decimal128BuilderWrapper::new(10, 18, 0);
608        let mantissa = BigInt::from(12345_i128);
609        assert_eq!(builder.convert_decimal_slow(&mantissa, -2).unwrap(), 123);
610    }
611
612    #[test]
613    fn test_convert_decimal_slow_large_mantissa() {
614        let builder = Decimal128BuilderWrapper::new(10, 38, 0);
615        // Value that fits in i128 but tests slow path
616        let mantissa = BigInt::from(i128::MAX);
617        assert_eq!(
618            builder.convert_decimal_slow(&mantissa, 0).unwrap(),
619            i128::MAX
620        );
621    }
622
623    #[test]
624    fn test_convert_decimal_slow_overflow() {
625        let builder = Decimal128BuilderWrapper::new(10, 38, 0);
626        // Value exceeding i128::MAX
627        let mantissa = BigInt::from(i128::MAX) * 10;
628        assert!(builder.convert_decimal_slow(&mantissa, 0).is_err());
629    }
630
631    // ═══════════════════════════════════════════════════════════════════════════
632    // convert_decimal Tests (CRIT-001)
633    // ═══════════════════════════════════════════════════════════════════════════
634
635    #[cfg(feature = "test-utils")]
636    mod convert_decimal_tests {
637        use arrow_array::Decimal128Array;
638
639        use super::*;
640        use crate::traits::row::MockRowBuilder;
641
642        /// Helper to extract i128 value from builder after appending decimal
643        fn convert_and_get_value(precision: u8, scale: i8, decimal_str: &str) -> i128 {
644            let mut builder = Decimal128BuilderWrapper::new(10, precision, scale);
645            let row = MockRowBuilder::new().decimal_str(decimal_str).build();
646            builder.append_hana_value(&row[0]).unwrap();
647            let array = builder.finish();
648            let decimal_array = array.as_any().downcast_ref::<Decimal128Array>().unwrap();
649            decimal_array.value(0)
650        }
651
652        // ───────────────────────────────────────────────────────────────────────
653        // Basic decimal conversion tests
654        // ───────────────────────────────────────────────────────────────────────
655
656        #[test]
657        fn test_convert_decimal_simple_value() {
658            // 123.45 with scale=2 should store as 12345
659            let value = convert_and_get_value(18, 2, "123.45");
660            assert_eq!(value, 12345);
661        }
662
663        #[test]
664        fn test_convert_decimal_integer_value() {
665            // 100 with scale=0 should store as 100
666            let value = convert_and_get_value(10, 0, "100");
667            assert_eq!(value, 100);
668        }
669
670        #[test]
671        fn test_convert_decimal_large_value() {
672            // 9999999.99 with scale=2 should store as 999999999
673            let value = convert_and_get_value(18, 2, "9999999.99");
674            assert_eq!(value, 999999999);
675        }
676
677        // ───────────────────────────────────────────────────────────────────────
678        // Scale-up tests (target_scale > source_scale)
679        // ───────────────────────────────────────────────────────────────────────
680
681        #[test]
682        fn test_convert_decimal_scale_up_integer_to_decimal() {
683            // 100 (scale=0) to scale=2 should become 10000 (100.00)
684            let value = convert_and_get_value(10, 2, "100");
685            assert_eq!(value, 10000);
686        }
687
688        #[test]
689        fn test_convert_decimal_scale_up_by_one() {
690            // 123.4 (scale=1) to scale=2 should become 12340 (123.40)
691            let value = convert_and_get_value(10, 2, "123.4");
692            assert_eq!(value, 12340);
693        }
694
695        #[test]
696        fn test_convert_decimal_scale_up_by_multiple() {
697            // 5 (scale=0) to scale=4 should become 50000 (5.0000)
698            let value = convert_and_get_value(10, 4, "5");
699            assert_eq!(value, 50000);
700        }
701
702        // ───────────────────────────────────────────────────────────────────────
703        // Scale-down tests (target_scale < source_scale, may lose precision)
704        // ───────────────────────────────────────────────────────────────────────
705
706        #[test]
707        fn test_convert_decimal_scale_down_truncate() {
708            // 123.456 (scale=3) to scale=2 should become 12345 (123.45, truncated)
709            let value = convert_and_get_value(10, 2, "123.456");
710            assert_eq!(value, 12345);
711        }
712
713        #[test]
714        fn test_convert_decimal_scale_down_to_integer() {
715            // 123.99 (scale=2) to scale=0 should become 123 (truncated)
716            let value = convert_and_get_value(10, 0, "123.99");
717            assert_eq!(value, 123);
718        }
719
720        #[test]
721        fn test_convert_decimal_scale_down_multiple_places() {
722            // 1.23456789 (scale=8) to scale=2 should become 123 (1.23)
723            let value = convert_and_get_value(18, 2, "1.23456789");
724            assert_eq!(value, 123);
725        }
726
727        // ───────────────────────────────────────────────────────────────────────
728        // Scale-match tests (target_scale == source_scale)
729        // ───────────────────────────────────────────────────────────────────────
730
731        #[test]
732        fn test_convert_decimal_scale_match_exact() {
733            // 123.45 (scale=2) to scale=2 should be 12345 (no change)
734            let value = convert_and_get_value(18, 2, "123.45");
735            assert_eq!(value, 12345);
736        }
737
738        #[test]
739        fn test_convert_decimal_scale_match_high_scale() {
740            // 1.234567890123456789 (scale=18) to scale=18
741            let value = convert_and_get_value(38, 18, "1.234567890123456789");
742            assert_eq!(value, 1_234_567_890_123_456_789_i128);
743        }
744
745        // ───────────────────────────────────────────────────────────────────────
746        // Negative value tests
747        // ───────────────────────────────────────────────────────────────────────
748
749        #[test]
750        fn test_convert_decimal_negative_simple() {
751            // -123.45 with scale=2 should store as -12345
752            let value = convert_and_get_value(18, 2, "-123.45");
753            assert_eq!(value, -12345);
754        }
755
756        #[test]
757        fn test_convert_decimal_negative_scale_up() {
758            // -100 (scale=0) to scale=2 should become -10000
759            let value = convert_and_get_value(10, 2, "-100");
760            assert_eq!(value, -10000);
761        }
762
763        #[test]
764        fn test_convert_decimal_negative_scale_down() {
765            // -123.456 (scale=3) to scale=2 should become -12345 (truncated toward zero)
766            let value = convert_and_get_value(10, 2, "-123.456");
767            assert_eq!(value, -12345);
768        }
769
770        #[test]
771        fn test_convert_decimal_negative_large() {
772            // -9999999.99 with scale=2
773            let value = convert_and_get_value(18, 2, "-9999999.99");
774            assert_eq!(value, -999999999);
775        }
776
777        // ───────────────────────────────────────────────────────────────────────
778        // Zero value tests
779        // ───────────────────────────────────────────────────────────────────────
780
781        #[test]
782        fn test_convert_decimal_zero() {
783            let value = convert_and_get_value(10, 2, "0");
784            assert_eq!(value, 0);
785        }
786
787        #[test]
788        fn test_convert_decimal_zero_with_scale() {
789            // 0.00 with scale=2 should store as 0
790            let value = convert_and_get_value(10, 2, "0.00");
791            assert_eq!(value, 0);
792        }
793
794        #[test]
795        fn test_convert_decimal_negative_zero() {
796            // -0 should still be 0
797            let value = convert_and_get_value(10, 2, "-0");
798            assert_eq!(value, 0);
799        }
800
801        // ───────────────────────────────────────────────────────────────────────
802        // Maximum precision (38-digit) tests
803        // ───────────────────────────────────────────────────────────────────────
804
805        #[test]
806        fn test_convert_decimal_max_precision_integer() {
807            // Maximum 38-digit integer that fits in i128 (precision=38, scale=0)
808            // i128::MAX is 170141183460469231731687303715884105727 (39 digits)
809            // We use a 38-digit number
810            let value = convert_and_get_value(38, 0, "99999999999999999999999999999999999999");
811            assert_eq!(
812                value,
813                99_999_999_999_999_999_999_999_999_999_999_999_999_i128
814            );
815        }
816
817        #[test]
818        fn test_convert_decimal_max_precision_with_scale() {
819            // 38-digit decimal with scale: 9999999999999999999999999999999999.9999
820            // stored as 99999999999999999999999999999999999999 with scale=4
821            let value = convert_and_get_value(38, 4, "9999999999999999999999999999999999.9999");
822            assert_eq!(
823                value,
824                99_999_999_999_999_999_999_999_999_999_999_999_999_i128
825            );
826        }
827
828        #[test]
829        fn test_convert_decimal_i128_max_boundary() {
830            // Test value close to i128::MAX
831            // i128::MAX = 170141183460469231731687303715884105727
832            let value = convert_and_get_value(38, 0, "170141183460469231731687303715884105727");
833            assert_eq!(value, i128::MAX);
834        }
835
836        #[test]
837        fn test_convert_decimal_i128_min_boundary() {
838            // Test value close to i128::MIN
839            // i128::MIN = -170141183460469231731687303715884105728
840            let value = convert_and_get_value(38, 0, "-170141183460469231731687303715884105728");
841            assert_eq!(value, i128::MIN);
842        }
843
844        // ───────────────────────────────────────────────────────────────────────
845        // Overflow detection tests
846        // ───────────────────────────────────────────────────────────────────────
847
848        #[test]
849        fn test_convert_decimal_overflow_scale_up() {
850            // Scale up operation that causes overflow:
851            // i128::MAX with additional scaling would overflow
852            let mut builder = Decimal128BuilderWrapper::new(10, 38, 10);
853            // Create a value that when scaled up by 10^10, exceeds i128::MAX
854            let row = MockRowBuilder::new()
855                .decimal_str("17014118346046923173168730371588410573")
856                .build();
857            let result = builder.append_hana_value(&row[0]);
858            assert!(result.is_err());
859        }
860
861        #[test]
862        fn test_convert_decimal_overflow_large_value() {
863            // Value already exceeding i128 range after scaling
864            let mut builder = Decimal128BuilderWrapper::new(10, 38, 0);
865            // This is larger than i128::MAX
866            let row = MockRowBuilder::new()
867                .decimal_str("999999999999999999999999999999999999999")
868                .build();
869            let result = builder.append_hana_value(&row[0]);
870            assert!(result.is_err());
871        }
872
873        // ───────────────────────────────────────────────────────────────────────
874        // Wrong type error handling tests
875        // ───────────────────────────────────────────────────────────────────────
876
877        #[test]
878        fn test_convert_decimal_wrong_type_int() {
879            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
880            let row = MockRowBuilder::new().int(42).build();
881            let result = builder.append_hana_value(&row[0]);
882            assert!(result.is_err());
883        }
884
885        #[test]
886        fn test_convert_decimal_wrong_type_string() {
887            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
888            let row = MockRowBuilder::new().string("123.45").build();
889            let result = builder.append_hana_value(&row[0]);
890            assert!(result.is_err());
891        }
892
893        #[test]
894        fn test_convert_decimal_wrong_type_null() {
895            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
896            let row = MockRowBuilder::new().null().build();
897            let result = builder.append_hana_value(&row[0]);
898            // NULL should be handled via append_null, not convert_decimal
899            // append_hana_value with NULL should fail
900            assert!(result.is_err());
901        }
902
903        #[test]
904        fn test_convert_decimal_wrong_type_double() {
905            let mut builder = Decimal128BuilderWrapper::new(10, 18, 2);
906            let row = MockRowBuilder::new().double(123.45).build();
907            let result = builder.append_hana_value(&row[0]);
908            assert!(result.is_err());
909        }
910
911        // ───────────────────────────────────────────────────────────────────────
912        // Additional edge cases
913        // ───────────────────────────────────────────────────────────────────────
914
915        #[test]
916        fn test_convert_decimal_very_small_value() {
917            // 0.00000001 with scale=8 should be 1
918            let value = convert_and_get_value(18, 8, "0.00000001");
919            assert_eq!(value, 1);
920        }
921
922        #[test]
923        fn test_convert_decimal_leading_zeros() {
924            // 00123.45 should be same as 123.45
925            let value = convert_and_get_value(18, 2, "00123.45");
926            assert_eq!(value, 12345);
927        }
928
929        #[test]
930        fn test_convert_decimal_trailing_zeros() {
931            // 123.450 should be same as 123.45 when target scale is 2
932            let value = convert_and_get_value(18, 2, "123.450");
933            assert_eq!(value, 12345);
934        }
935
936        #[test]
937        fn test_convert_decimal_scientific_notation() {
938            // 1.23e2 = 123 with scale=0
939            let value = convert_and_get_value(10, 0, "1.23e2");
940            assert_eq!(value, 123);
941        }
942
943        #[test]
944        fn test_convert_decimal_scientific_notation_negative_exp() {
945            // 12300e-2 = 123 with scale=0
946            let value = convert_and_get_value(10, 0, "12300e-2");
947            assert_eq!(value, 123);
948        }
949
950        // ───────────────────────────────────────────────────────────────────────
951        // Fast path boundary tests
952        // ───────────────────────────────────────────────────────────────────────
953
954        #[test]
955        fn test_fast_path_boundary_i128_max() {
956            // i128::MAX with scale=0 should use fast path (no scaling needed)
957            let value = convert_and_get_value(38, 0, "170141183460469231731687303715884105727");
958            assert_eq!(value, i128::MAX);
959        }
960
961        #[test]
962        fn test_fast_path_boundary_i128_min() {
963            // i128::MIN with scale=0 should use fast path
964            let value = convert_and_get_value(38, 0, "-170141183460469231731687303715884105728");
965            assert_eq!(value, i128::MIN);
966        }
967
968        #[test]
969        fn test_fast_path_boundary_scale_38() {
970            // Maximum scale difference (38)
971            let value = convert_and_get_value(38, 38, "1");
972            assert_eq!(value, POWERS_OF_10_I128[38]);
973        }
974
975        // ───────────────────────────────────────────────────────────────────────
976        // Slow path fallback tests
977        // ───────────────────────────────────────────────────────────────────────
978
979        #[test]
980        fn test_slow_path_fallback_overflow_triggers_slow_path() {
981            // Large value that fits in i128 but scaling causes fast path overflow
982            // 10^37 * 10 = 10^38 which fits in i128
983            let value = convert_and_get_value(38, 1, "10000000000000000000000000000000000000");
984            assert_eq!(value, POWERS_OF_10_I128[38]);
985        }
986
987        #[test]
988        fn test_slow_path_mantissa_exceeds_i128() {
989            // Mantissa that exceeds i128 but result fits after scale down
990            // This tests that slow path is triggered when mantissa.to_i128() returns None
991            let mut builder = Decimal128BuilderWrapper::new(10, 38, 0);
992            // 10^39 / 10 = 10^38 which fits
993            let row = MockRowBuilder::new()
994                .decimal_str("1000000000000000000000000000000000000000")
995                .build();
996            // Scale down from source scale to 0 should result in truncation
997            // BigDecimal "1000000000000000000000000000000000000000" has 40 digits
998            let result = builder.append_hana_value(&row[0]);
999            // This exceeds i128::MAX, so should error
1000            assert!(result.is_err());
1001        }
1002    }
1003}