Skip to content

Commit 0702c55

Browse files
author
Bhargava Vadlamani
committed
long_decimal_cast_native_support
1 parent 03c0626 commit 0702c55

5 files changed

Lines changed: 115 additions & 127 deletions

File tree

docs/source/user-guide/latest/compatibility.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ The following cast operations are generally compatible with Spark except for the
180180
| long | integer | |
181181
| long | float | |
182182
| long | double | |
183+
| long | decimal | |
183184
| long | string | |
184185
| float | boolean | |
185186
| float | byte | |
@@ -227,7 +228,6 @@ The following cast operations are not compatible with Spark for all inputs and a
227228
| From Type | To Type | Notes |
228229
|-|-|-|
229230
| integer | decimal | No overflow check |
230-
| long | decimal | No overflow check |
231231
| float | decimal | There can be rounding differences |
232232
| double | decimal | There can be rounding differences |
233233
| string | float | Does not support inputs ending with 'd' or 'f'. Does not support 'inf'. Does not support ANSI mode. |

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

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ use crate::utils::array_with_timezone;
1919
use crate::{timezone, BinaryOutputStyle};
2020
use crate::{EvalMode, SparkError, SparkResult};
2121
use arrow::array::builder::StringBuilder;
22-
use arrow::array::{DictionaryArray, GenericByteArray, StringArray, StructArray};
22+
use arrow::array::{
23+
ArrowNativeTypeOp, Decimal128Builder, DictionaryArray, GenericByteArray, StringArray,
24+
StructArray,
25+
};
2326
use arrow::compute::can_cast_types;
27+
use arrow::datatypes::DataType::Float32;
2428
use arrow::datatypes::{
2529
ArrowDictionaryKeyType, ArrowNativeType, DataType, GenericBinaryType, Schema,
2630
};
@@ -41,15 +45,17 @@ use arrow::{
4145
record_batch::RecordBatch,
4246
util::display::FormatOptions,
4347
};
48+
use base64::prelude::*;
4449
use chrono::{DateTime, NaiveDate, TimeZone, Timelike};
4550
use datafusion::common::{
4651
cast::as_generic_string_array, internal_err, DataFusionError, Result as DataFusionResult,
4752
ScalarValue,
4853
};
4954
use datafusion::physical_expr::PhysicalExpr;
5055
use datafusion::physical_plan::ColumnarValue;
56+
use futures::future::err;
5157
use num::{
52-
cast::AsPrimitive, integer::div_floor, traits::CheckedNeg, CheckedSub, Integer, Num,
58+
cast::AsPrimitive, integer::div_floor, traits::CheckedNeg, CheckedSub, Integer, Num, Signed,
5359
ToPrimitive,
5460
};
5561
use regex::Regex;
@@ -62,8 +68,6 @@ use std::{
6268
sync::Arc,
6369
};
6470

65-
use base64::prelude::*;
66-
6771
static TIMESTAMP_FORMAT: Option<&str> = Some("%Y-%m-%d %H:%M:%S%.f");
6872

6973
const MICROS_PER_SECOND: i64 = 1000000;
@@ -983,6 +987,9 @@ fn cast_array(
983987
{
984988
spark_cast_int_to_int(&array, eval_mode, from_type, to_type)
985989
}
990+
(Int8 | Int16 | Int32 | Int64, Decimal128(precision, scale)) => {
991+
cast_int_to_decimal128(&array, eval_mode, from_type, to_type, *precision, *scale)
992+
}
986993
(Utf8, Int8 | Int16 | Int32 | Int64) => {
987994
cast_string_to_int::<i32>(to_type, &array, eval_mode)
988995
}
@@ -1464,6 +1471,106 @@ where
14641471
cast_float_to_string!(from, _eval_mode, f32, Float32Array, OffsetSize)
14651472
}
14661473

1474+
fn cast_int_to_decimal128_internal<T>(
1475+
array: &PrimitiveArray<T>,
1476+
precision: u8,
1477+
scale: i8,
1478+
eval_mode: EvalMode,
1479+
) -> SparkResult<ArrayRef>
1480+
where
1481+
T: ArrowPrimitiveType,
1482+
T::Native: Into<i128>,
1483+
{
1484+
let mut builder = Decimal128Builder::with_capacity(array.len());
1485+
let multiplier = 10_i128.pow(scale as u32);
1486+
1487+
for i in 0..array.len() {
1488+
if array.is_null(i) {
1489+
builder.append_null();
1490+
} else {
1491+
let v = array.value(i).into();
1492+
let scaled = v.checked_mul(multiplier);
1493+
match scaled {
1494+
Some(scaled) => {
1495+
if !is_validate_decimal_precision(scaled, precision) {
1496+
match eval_mode {
1497+
EvalMode::Ansi => {
1498+
return Err(SparkError::NumericValueOutOfRange {
1499+
value: v.to_string(),
1500+
precision,
1501+
scale,
1502+
});
1503+
}
1504+
(EvalMode::Try | EvalMode::Legacy) => {
1505+
builder.append_null()
1506+
}
1507+
}
1508+
} else {
1509+
builder.append_value(scaled);
1510+
}
1511+
}
1512+
_ => {
1513+
return Err(SparkError::NumericValueOutOfRange {
1514+
value: v.to_string(),
1515+
precision,
1516+
scale,
1517+
})
1518+
}
1519+
}
1520+
}
1521+
}
1522+
Ok(Arc::new(
1523+
builder.with_precision_and_scale(precision, scale)?.finish(),
1524+
))
1525+
}
1526+
fn cast_int_to_decimal128(
1527+
array: &dyn Array,
1528+
eval_mode: EvalMode,
1529+
from_type: &DataType,
1530+
to_type: &DataType,
1531+
precision: u8,
1532+
scale: i8,
1533+
) -> SparkResult<ArrayRef> {
1534+
match (from_type, to_type) {
1535+
(DataType::Int8, DataType::Decimal128(p, s)) => {
1536+
cast_int_to_decimal128_internal::<Int8Type>(
1537+
array.as_primitive::<Int8Type>(),
1538+
precision,
1539+
scale,
1540+
eval_mode,
1541+
)
1542+
}
1543+
(DataType::Int16, DataType::Decimal128(p, s)) => {
1544+
cast_int_to_decimal128_internal::<Int16Type>(
1545+
array.as_primitive::<Int16Type>(),
1546+
precision,
1547+
scale,
1548+
eval_mode,
1549+
)
1550+
}
1551+
(DataType::Int32, DataType::Decimal128(p, s)) => {
1552+
cast_int_to_decimal128_internal::<Int32Type>(
1553+
array.as_primitive::<Int32Type>(),
1554+
precision,
1555+
scale,
1556+
eval_mode,
1557+
)
1558+
}
1559+
(DataType::Int64, DataType::Decimal128(p, s)) => {
1560+
cast_int_to_decimal128_internal::<Int64Type>(
1561+
array.as_primitive::<Int64Type>(),
1562+
precision,
1563+
scale,
1564+
eval_mode,
1565+
)
1566+
}
1567+
_ => Err(SparkError::Internal(format!(
1568+
"Unsupported cast from datatype : {}",
1569+
from_type
1570+
))),
1571+
}
1572+
}
1573+
14671574
fn spark_cast_int_to_int(
14681575
array: &dyn Array,
14691576
eval_mode: EvalMode,

native/spark-expr/src/math_funcs/div.rs

Lines changed: 0 additions & 115 deletions
This file was deleted.

spark/src/main/scala/org/apache/comet/expressions/CometCast.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ object CometCast extends CometExpressionSerde[Cast] with CometExprShim {
297297
case DataTypes.FloatType | DataTypes.DoubleType =>
298298
Compatible()
299299
case _: DecimalType =>
300-
Incompatible(Some("No overflow check"))
300+
Compatible()
301301
case _ =>
302302
unsupported(DataTypes.LongType, toType)
303303
}

spark/src/test/scala/org/apache/comet/CometCastSuite.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,15 @@
2020
package org.apache.comet
2121

2222
import java.io.File
23-
2423
import scala.util.Random
2524
import scala.util.matching.Regex
26-
2725
import org.apache.hadoop.fs.Path
2826
import org.apache.spark.sql.{CometTestBase, DataFrame, Row, SaveMode}
2927
import org.apache.spark.sql.catalyst.expressions.Cast
3028
import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanHelper
31-
import org.apache.spark.sql.functions.col
29+
import org.apache.spark.sql.functions.{col, lit}
3230
import org.apache.spark.sql.internal.SQLConf
3331
import org.apache.spark.sql.types.{DataType, DataTypes, DecimalType, StructField, StructType}
34-
3532
import org.apache.comet.CometSparkSessionExtensions.isSpark40Plus
3633
import org.apache.comet.expressions.{CometCast, CometEvalMode}
3734
import org.apache.comet.serde.Compatible
@@ -369,8 +366,7 @@ class CometCastSuite extends CometTestBase with AdaptiveSparkPlanHelper {
369366
castTest(generateLongs(), DataTypes.DoubleType)
370367
}
371368

372-
ignore("cast LongType to DecimalType(10,2)") {
373-
// Comet should have failed with [NUMERIC_VALUE_OUT_OF_RANGE] -1117686336 cannot be represented as Decimal(10, 2)
369+
test("cast LongType to DecimalType(10,2)") {
374370
castTest(generateLongs(), DataTypes.createDecimalType(10, 2))
375371
}
376372

0 commit comments

Comments
 (0)