@@ -767,102 +767,119 @@ class CometCastSuite extends CometTestBase with AdaptiveSparkPlanHelper {
767767 }
768768
769769// This is to pass the first `all cast combinations are covered`
770- ignore (" cast StringType to DecimalType(10,2)" ) {
770+ test (" cast StringType to DecimalType(10,2)" ) {
771771 val values = gen.generateStrings(dataSize, numericPattern, 12 ).toDF(" a" )
772772 castTest(values, DataTypes .createDecimalType(10 , 2 ), testAnsi = false )
773773 }
774774
775- test(" cast StringType to DecimalType(10,2) (does not support fullwidth unicode digits)" ) {
776- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
777- val values = gen.generateStrings(dataSize, numericPattern, 12 ).toDF(" a" )
778- Seq (true , false ).foreach(ansiEnabled =>
779- castTest(values, DataTypes .createDecimalType(10 , 2 ), testAnsi = ansiEnabled))
780- }
775+ test(" cast StringType to DecimalType(10,2) fuzz" ) {
776+ val values = gen.generateStrings(dataSize, numericPattern, 12 ).toDF(" a" )
777+ Seq (true , false ).foreach(ansiEnabled =>
778+ castTest(values, DataTypes .createDecimalType(10 , 2 ), testAnsi = ansiEnabled))
781779 }
782780
783781 test(" cast StringType to DecimalType(2,2)" ) {
784- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
785- val values = gen.generateStrings(dataSize, numericPattern, 12 ).toDF(" a" )
786- Seq (true , false ).foreach(ansiEnabled =>
787- castTest(values, DataTypes .createDecimalType(2 , 2 ), testAnsi = ansiEnabled))
788- }
782+ val values = gen.generateStrings(dataSize, numericPattern, 12 ).toDF(" a" )
783+ Seq (true , false ).foreach(ansiEnabled =>
784+ castTest(values, DataTypes .createDecimalType(2 , 2 ), testAnsi = ansiEnabled))
789785 }
790786
791787 test(" cast StringType to DecimalType check if right exception message is thrown" ) {
792- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
793- val values = Seq (" d11307\n " ).toDF(" a" )
794- Seq (true , false ).foreach(ansiEnabled =>
795- castTest(values, DataTypes .createDecimalType(2 , 2 ), testAnsi = ansiEnabled))
796- }
788+ val values = Seq (" d11307\n " ).toDF(" a" )
789+ Seq (true , false ).foreach(ansiEnabled =>
790+ castTest(values, DataTypes .createDecimalType(2 , 2 ), testAnsi = ansiEnabled))
797791 }
798792
799793 test(" cast StringType to DecimalType(2,2) check if right exception is being thrown" ) {
800- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
801- val values = gen.generateInts(10000 ).map(" " + _).toDF(" a" )
802- Seq (true , false ).foreach(ansiEnabled =>
803- castTest(values, DataTypes .createDecimalType(2 , 2 ), testAnsi = ansiEnabled))
804- }
794+ val values = gen.generateInts(10000 ).map(" " + _).toDF(" a" )
795+ Seq (true , false ).foreach(ansiEnabled =>
796+ castTest(values, DataTypes .createDecimalType(2 , 2 ), testAnsi = ansiEnabled))
805797 }
806798
807799 test(" cast StringType to DecimalType(38,10) high precision - check 0 mantissa" ) {
808- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
809- val values = Seq (" 0e31" , " 000e3375" , " 0e40" , " 0E+695" , " 0e5887677" ).toDF(" a" )
810- Seq (true , false ).foreach(ansiEnabled =>
811- castTest(values, DataTypes .createDecimalType(38 , 10 ), testAnsi = ansiEnabled))
812- }
800+ val values = Seq (" 0e31" , " 000e3375" , " 0e40" , " 0E+695" , " 0e5887677" ).toDF(" a" )
801+ Seq (true , false ).foreach(ansiEnabled =>
802+ castTest(values, DataTypes .createDecimalType(38 , 10 ), testAnsi = ansiEnabled))
813803 }
814804
815805 test(" cast StringType to DecimalType(38,10) high precision" ) {
816- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
817- val values = gen.generateStrings(dataSize, numericPattern, 38 ).toDF(" a" )
818- Seq (true , false ).foreach(ansiEnabled =>
819- castTest(values, DataTypes .createDecimalType(38 , 10 ), testAnsi = ansiEnabled))
820- }
806+ val values = gen.generateStrings(dataSize, numericPattern, 38 ).toDF(" a" )
807+ Seq (true , false ).foreach(ansiEnabled =>
808+ castTest(values, DataTypes .createDecimalType(38 , 10 ), testAnsi = ansiEnabled))
809+ }
810+
811+ test(" cast StringType to DecimalType - null bytes and fullwidth digits" ) {
812+ // Spark trims null bytes (\u0000) from both ends of a string before parsing,
813+ // matching its whitespace-trim behavior. Null bytes in the middle produce NULL.
814+ // Fullwidth digits (U+FF10-U+FF19) are treated as numeric equivalents to ASCII digits.
815+ val values = Seq (
816+ // null byte positions
817+ " 123\u0000 " ,
818+ " \u0000 123" ,
819+ " 12\u0000 3" ,
820+ " 1\u0000 2\u0000 3" ,
821+ " \u0000 " ,
822+ // null byte with decimal point
823+ " 12\u0000 .45" ,
824+ " 12.\u0000 45" ,
825+ // fullwidth digits (U+FF10-U+FF19)
826+ " 123.45" , // "123.45" in fullwidth
827+ " 123" ,
828+ " -123.45" ,
829+ " +123.45" ,
830+ " 123.45E2" ,
831+ // mixed fullwidth and ASCII
832+ " 123.45" ,
833+ null ).toDF(" a" )
834+ castTest(values, DataTypes .createDecimalType(10 , 2 ))
821835 }
822836
823837 test(" cast StringType to DecimalType(10,2) basic values" ) {
824- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
825- val values = Seq (
826- " 123.45" ,
827- " -67.89" ,
828- " -67.89" ,
829- " -67.895" ,
830- " 67.895" ,
831- " 0.001" ,
832- " 999.99" ,
833- " 123.456" ,
834- " 123.45D" ,
835- " .5" ,
836- " 5." ,
837- " +123.45" ,
838- " 123.45 " ,
839- " inf" ,
840- " " ,
841- " abc" ,
842- null ).toDF(" a" )
843- Seq (true , false ).foreach(ansiEnabled =>
844- castTest(values, DataTypes .createDecimalType(10 , 2 ), testAnsi = ansiEnabled))
845- }
838+ val values = Seq (
839+ " 123.45" ,
840+ " -67.89" ,
841+ " -67.89" ,
842+ " -67.895" ,
843+ " 67.895" ,
844+ " 0.001" ,
845+ " 999.99" ,
846+ " 123.456" ,
847+ " 123.45D" ,
848+ " .5" ,
849+ " 5." ,
850+ " +123.45" ,
851+ " 123.45 " ,
852+ " inf" ,
853+ " " ,
854+ " abc" ,
855+ // values from https://github.com/apache/datafusion-comet/issues/325
856+ " 0" ,
857+ " 1" ,
858+ " +1.0" ,
859+ " .34" ,
860+ " -10.0" ,
861+ " 4e7" ,
862+ null ).toDF(" a" )
863+ Seq (true , false ).foreach(ansiEnabled =>
864+ castTest(values, DataTypes .createDecimalType(10 , 2 ), testAnsi = ansiEnabled))
846865 }
847866
848867 test(" cast StringType to Decimal type scientific notation" ) {
849- withSQLConf(CometConf .getExprAllowIncompatConfigKey(classOf [Cast ]) -> " true" ) {
850- val values = Seq (
851- " 1.23E-5" ,
852- " 1.23e10" ,
853- " 1.23E+10" ,
854- " -1.23e-5" ,
855- " 1e5" ,
856- " 1E-2" ,
857- " -1.5e3" ,
858- " 1.23E0" ,
859- " 0e0" ,
860- " 1.23e" ,
861- " e5" ,
862- null ).toDF(" a" )
863- Seq (true , false ).foreach(ansiEnabled =>
864- castTest(values, DataTypes .createDecimalType(23 , 8 ), testAnsi = ansiEnabled))
865- }
868+ val values = Seq (
869+ " 1.23E-5" ,
870+ " 1.23e10" ,
871+ " 1.23E+10" ,
872+ " -1.23e-5" ,
873+ " 1e5" ,
874+ " 1E-2" ,
875+ " -1.5e3" ,
876+ " 1.23E0" ,
877+ " 0e0" ,
878+ " 1.23e" ,
879+ " e5" ,
880+ null ).toDF(" a" )
881+ Seq (true , false ).foreach(ansiEnabled =>
882+ castTest(values, DataTypes .createDecimalType(23 , 8 ), testAnsi = ansiEnabled))
866883 }
867884
868885 test(" cast StringType to BinaryType" ) {
@@ -1310,6 +1327,19 @@ class CometCastSuite extends CometTestBase with AdaptiveSparkPlanHelper {
13101327 castTest(generateDecimalsPrecision10Scale2(), DataTypes .createDecimalType(10 , 4 ))
13111328 }
13121329
1330+ test(" cast StringType to DecimalType with negative scale (allowNegativeScaleOfDecimal)" ) {
1331+ // With allowNegativeScaleOfDecimal=true, Spark allows DECIMAL(p, s) where s < 0.
1332+ // The value is rounded to the nearest 10^|s| — e.g. DECIMAL(10,-4) rounds to
1333+ // the nearest 10000. This requires the legacy SQL parser config to be enabled.
1334+ withSQLConf(" spark.sql.legacy.allowNegativeScaleOfDecimal" -> " true" ) {
1335+ val values =
1336+ Seq (" 12500" , " 15000" , " 99990000" , " -12500" , " 0" , " 0.001" , " abc" , null ).toDF(" a" )
1337+ // testTry=false: try_cast uses SQL string interpolation (toType.sql → "DECIMAL(10,-4)")
1338+ // which the SQL parser rejects regardless of allowNegativeScaleOfDecimal.
1339+ castTest(values, DataTypes .createDecimalType(10 , - 4 ), testTry = false )
1340+ }
1341+ }
1342+
13131343 test(" cast between decimals with negative precision" ) {
13141344 // cast to negative scale
13151345 checkSparkAnswerMaybeThrows(
0 commit comments