Skip to content

Commit e531ace

Browse files
author
B Vadlamani
committed
fix_issue
1 parent a9b9ccd commit e531ace

2 files changed

Lines changed: 83 additions & 18 deletions

File tree

native/spark-expr/benches/cast_from_string.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,27 @@ fn criterion_benchmark(c: &mut Criterion) {
6868
b.iter(|| cast_to_i64.evaluate(&decimal_batch).unwrap());
6969
});
7070
group.finish();
71+
72+
// str -> decimal benchmark
73+
let decimal_string_batch = create_decimal_cast_string_batch();
74+
for (mode, mode_name) in [
75+
(EvalMode::Legacy, "legacy"),
76+
(EvalMode::Ansi, "ansi"),
77+
(EvalMode::Try, "try"),
78+
] {
79+
let spark_cast_options = SparkCastOptions::new(mode, "", false);
80+
let cast_to_decimal_38_10 = Cast::new(
81+
expr.clone(),
82+
DataType::Decimal128(38, 10),
83+
spark_cast_options,
84+
);
85+
86+
let mut group = c.benchmark_group(format!("cast_string_to_decimal/{}", mode_name));
87+
group.bench_function("decimal_38_10", |b| {
88+
b.iter(|| cast_to_decimal_38_10.evaluate(&decimal_string_batch).unwrap());
89+
});
90+
group.finish();
91+
}
7192
}
7293

7394
/// Create batch with small integer strings that fit in i8 range (for i8/i16 benchmarks)
@@ -118,6 +139,51 @@ fn create_decimal_string_batch() -> RecordBatch {
118139
RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
119140
}
120141

142+
/// Create batch with decimal strings for string-to-decimal cast perf evaluation
143+
fn create_decimal_cast_string_batch() -> RecordBatch {
144+
let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8, true)]));
145+
let mut b = StringBuilder::new();
146+
for i in 0..1000 {
147+
if i % 10 == 0 {
148+
b.append_null();
149+
} else {
150+
// Generate various decimal formats
151+
match i % 5 {
152+
0 => {
153+
// gen simple decimals (ex : "123.45"
154+
let int_part: u32 = rand::random::<u32>() % 1000000;
155+
let dec_part: u32 = rand::random::<u32>() % 100000;
156+
b.append_value(format!("{}.{}", int_part, dec_part));
157+
}
158+
1 => {
159+
// gen scientific notation like "123e5"
160+
let mantissa: u32 = rand::random::<u32>() % 1000;
161+
let exp: i8 = (rand::random::<i8>() % 10).abs();
162+
b.append_value(format!("{}.{}E{}", mantissa / 100, mantissa % 100, exp));
163+
}
164+
2 => {
165+
// Negative numbers
166+
let int_part: u32 = rand::random::<u32>() % 1000000;
167+
let dec_part: u32 = rand::random::<u32>() % 100000;
168+
b.append_value(format!("-{}.{}", int_part, dec_part));
169+
}
170+
3 => {
171+
// Ints only
172+
let val: i32 = rand::random::<i32>() % 1000000;
173+
b.append_value(format!("{}", val));
174+
}
175+
_ => {
176+
// Small decimals (ex : 0.001)
177+
let dec_part: u32 = rand::random::<u32>() % 100000;
178+
b.append_value(format!("0.{:05}", dec_part));
179+
}
180+
}
181+
}
182+
}
183+
let array = b.finish();
184+
RecordBatch::try_new(schema, vec![Arc::new(array)]).unwrap()
185+
}
186+
121187
fn config() -> Criterion {
122188
Criterion::default()
123189
}

native/spark-expr/src/conversion_funcs/cast.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,12 +2348,7 @@ fn parse_string_to_decimal(input_str: &str, precision: u8, scale: i8) -> SparkRe
23482348
}
23492349

23502350
// validate and parse mantissa and exponent or bubble up the error
2351-
let (mantissa, exponent) = parse_decimal_str(
2352-
trimmed,
2353-
input_str,
2354-
"STRING",
2355-
&format!("DECIMAL({},{})", precision, scale),
2356-
)?;
2351+
let (mantissa, exponent) = parse_decimal_str(trimmed, input_str, precision, scale)?;
23572352

23582353
// Early return mantissa 0, Spark checks if it fits digits and throw error in ansi
23592354
if mantissa == 0 {
@@ -2429,16 +2424,20 @@ fn parse_string_to_decimal(input_str: &str, precision: u8, scale: i8) -> SparkRe
24292424
}
24302425
}
24312426

2427+
fn invalid_decimal_cast(value: &str, precision: u8, scale: i8) -> SparkError {
2428+
invalid_value(value,"STRING", &format!("DECIMAL({},{})", precision, scale))
2429+
}
2430+
24322431
/// Parse a decimal string into mantissa and scale
24332432
/// e.g., "123.45" -> (12345, 2), "-0.001" -> (-1, 3) , 0e50 -> (0,50) etc
24342433
fn parse_decimal_str(
24352434
s: &str,
24362435
original_str: &str,
2437-
from_type: &str,
2438-
to_type: &str,
2436+
precision: u8,
2437+
scale: i8,
24392438
) -> SparkResult<(i128, i32)> {
24402439
if s.is_empty() {
2441-
return Err(invalid_value(original_str, from_type, to_type));
2440+
return Err(invalid_decimal_cast(original_str, precision, scale));
24422441
}
24432442

24442443
let (mantissa_str, exponent) = if let Some(e_pos) = s.find(|c| ['e', 'E'].contains(&c)) {
@@ -2447,7 +2446,7 @@ fn parse_decimal_str(
24472446
// Parse exponent
24482447
let exp: i32 = exponent_part
24492448
.parse()
2450-
.map_err(|_| invalid_value(original_str, from_type, to_type))?;
2449+
.map_err(|_| invalid_decimal_cast(original_str, precision, scale))?;
24512450

24522451
(mantissa_part, exp)
24532452
} else {
@@ -2462,29 +2461,29 @@ fn parse_decimal_str(
24622461
};
24632462

24642463
if mantissa_str.starts_with('+') || mantissa_str.starts_with('-') {
2465-
return Err(invalid_value(original_str, from_type, to_type));
2464+
return Err(invalid_decimal_cast(original_str, precision, scale));
24662465
}
24672466

24682467
let (integral_part, fractional_part) = match mantissa_str.find('.') {
24692468
Some(dot_pos) => {
24702469
if mantissa_str[dot_pos + 1..].contains('.') {
2471-
return Err(invalid_value(original_str, from_type, to_type));
2470+
return Err(invalid_decimal_cast(original_str, precision, scale));
24722471
}
24732472
(&mantissa_str[..dot_pos], &mantissa_str[dot_pos + 1..])
24742473
}
24752474
None => (mantissa_str, ""),
24762475
};
24772476

24782477
if integral_part.is_empty() && fractional_part.is_empty() {
2479-
return Err(invalid_value(original_str, from_type, to_type));
2478+
return Err(invalid_decimal_cast(original_str, precision, scale));
24802479
}
24812480

24822481
if !integral_part.is_empty() && !integral_part.bytes().all(|b| b.is_ascii_digit()) {
2483-
return Err(invalid_value(original_str, from_type, to_type));
2482+
return Err(invalid_decimal_cast(original_str, precision, scale));
24842483
}
24852484

24862485
if !fractional_part.is_empty() && !fractional_part.bytes().all(|b| b.is_ascii_digit()) {
2487-
return Err(invalid_value(original_str, from_type, to_type));
2486+
return Err(invalid_decimal_cast(original_str, precision, scale));
24882487
}
24892488

24902489
// Parse integral part
@@ -2494,7 +2493,7 @@ fn parse_decimal_str(
24942493
} else {
24952494
integral_part
24962495
.parse()
2497-
.map_err(|_| invalid_value(original_str, from_type, to_type))?
2496+
.map_err(|_| invalid_decimal_cast(original_str, precision, scale))?
24982497
};
24992498

25002499
// Parse fractional part
@@ -2504,14 +2503,14 @@ fn parse_decimal_str(
25042503
} else {
25052504
fractional_part
25062505
.parse()
2507-
.map_err(|_| invalid_value(original_str, from_type, to_type))?
2506+
.map_err(|_| invalid_decimal_cast(original_str, precision, scale))?
25082507
};
25092508

25102509
// Combine: value = integral * 10^fractional_scale + fractional
25112510
let mantissa = integral_value
25122511
.checked_mul(10_i128.pow(fractional_scale as u32))
25132512
.and_then(|v| v.checked_add(fractional_value))
2514-
.ok_or_else(|| invalid_value(original_str, from_type, to_type))?;
2513+
.ok_or_else(|| invalid_decimal_cast(original_str, precision, scale))?;
25152514

25162515
let final_mantissa = if negative { -mantissa } else { mantissa };
25172516
// final scale = fractional_scale - exponent

0 commit comments

Comments
 (0)