From 1544a41f245cc2645e206977a010106bbf3661c3 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Fri, 3 Jul 2026 20:13:21 +0800 Subject: [PATCH 1/3] update --- pkg/sql/plan/function/func_cast.go | 189 ++++++++++++++++-- pkg/sql/plan/function/func_cast_test.go | 146 ++++++++++++++ .../cases/function/func_cast.result | 163 +++++++++++++++ .../distributed/cases/function/func_cast.test | 71 +++++++ 4 files changed, 555 insertions(+), 14 deletions(-) diff --git a/pkg/sql/plan/function/func_cast.go b/pkg/sql/plan/function/func_cast.go index 0c10dcc64aeca..62966542eba99 100644 --- a/pkg/sql/plan/function/func_cast.go +++ b/pkg/sql/plan/function/func_cast.go @@ -19,10 +19,12 @@ import ( "encoding/hex" "fmt" "math" + "math/big" "slices" "strconv" "strings" "time" + "unicode" "unicode/utf8" "unsafe" @@ -5056,11 +5058,7 @@ func strToSigned[T constraints.Signed]( s := strings.TrimSpace(convertByteSliceToString(v)) var r int64 var err error - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - r, err = strconv.ParseInt(s[2:], 16, bitSize) - } else { - r, err = strconv.ParseInt(s, 10, bitSize) - } + r, err = parseSignedCastString(s, bitSize) if err != nil { // XXX I'm not sure if we should return the int8 / int16 / int64 info. or // just return the int. the old code just return the int. too much bvt result needs to update. @@ -5079,6 +5077,117 @@ func strToSigned[T constraints.Signed]( return nil } +func splitCastNumericSign(s string) (trimmed string, body string, negative bool, hasSign bool) { + trimmed = strings.TrimSpace(s) + if trimmed == "" { + return trimmed, trimmed, false, false + } + switch trimmed[0] { + case '+': + return trimmed, trimmed[1:], false, true + case '-': + return trimmed, trimmed[1:], true, true + default: + return trimmed, trimmed, false, false + } +} + +type castNumericToken struct { + digits string + base int + negative bool +} + +func parseCastNumericToken(s string) (castNumericToken, error) { + _, body, negative, _ := splitCastNumericSign(s) + if body == "" { + return castNumericToken{}, strconv.ErrSyntax + } + if body[0] == '+' || body[0] == '-' { + return castNumericToken{}, strconv.ErrSyntax + } + if strings.IndexFunc(body, unicode.IsSpace) >= 0 { + return castNumericToken{}, strconv.ErrSyntax + } + + token := castNumericToken{ + digits: body, + base: 10, + negative: negative, + } + if len(body) >= 2 && body[0] == '0' { + switch body[1] { + case 'b', 'B': + token.base = 2 + token.digits = body[2:] + case 'o', 'O': + token.base = 8 + token.digits = body[2:] + case 'x', 'X': + token.base = 16 + token.digits = body[2:] + } + if token.digits == "" { + return castNumericToken{}, strconv.ErrSyntax + } + } + return token, nil +} + +func prefixedDigitsToDecimalString(digits string, base int) (string, error) { + if base == 10 { + return digits, nil + } + var value big.Int + if _, ok := value.SetString(digits, base); !ok { + return "", strconv.ErrSyntax + } + return value.String(), nil +} + +func parseSignedCastString(s string, bitSize int) (int64, error) { + token, err := parseCastNumericToken(s) + if err != nil { + return 0, err + } + value, err := strconv.ParseUint(token.digits, token.base, bitSize) + if err != nil { + return 0, err + } + maxAbsNegative := uint64(1) << uint(bitSize-1) + if token.negative { + if value > maxAbsNegative { + return 0, strconv.ErrRange + } + if value == maxAbsNegative { + if bitSize == 64 { + return math.MinInt64, nil + } + return -int64(maxAbsNegative), nil + } + return -int64(value), nil + } + if value >= maxAbsNegative { + return 0, strconv.ErrRange + } + return int64(value), nil +} + +func parseUnsignedCastString(s string, bitSize int) (uint64, error) { + token, err := parseCastNumericToken(s) + if err != nil { + return 0, err + } + val, err := strconv.ParseUint(token.digits, token.base, bitSize) + if err != nil { + return 0, err + } + if token.negative && val != 0 { + return 0, strconv.ErrSyntax + } + return val, nil +} + func strToUnsigned[T constraints.Unsigned]( ctx context.Context, from vector.FunctionParameterWrapper[types.Varlena], @@ -5105,12 +5214,7 @@ func strToUnsigned[T constraints.Unsigned]( } else { s := strings.TrimSpace(convertByteSliceToString(v)) res = &s - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - val, tErr = strconv.ParseUint(s[2:], 16, bitSize) - } else { - parseStr := strings.TrimPrefix(s, "+") - val, tErr = strconv.ParseUint(parseStr, 10, bitSize) - } + val, tErr = parseUnsignedCastString(s, bitSize) } if tErr != nil { if strings.Contains(tErr.Error(), "value out of range") { @@ -5208,7 +5312,7 @@ func strToDecimal64( } else { s := convertByteSliceToString(v) if !isb { - result, err := types.ParseDecimal64(s, totype.Width, totype.Scale) + result, err := parseDecimal64CastString(s, totype.Width, totype.Scale) if err != nil { return err } @@ -5229,6 +5333,63 @@ func strToDecimal64( return nil } +func parseDecimal64CastString(s string, width, scale int32) (types.Decimal64, error) { + token, err := parseCastNumericToken(s) + if err != nil { + return 0, err + } + parseStr, err := prefixedDigitsToDecimalString(token.digits, token.base) + if err != nil { + return 0, err + } + result, err := types.ParseDecimal64(parseStr, width, scale) + if err != nil { + return 0, err + } + if token.negative { + result = result.Minus() + } + return result, nil +} + +func parseDecimal128CastString(s string, width, scale int32) (types.Decimal128, error) { + token, err := parseCastNumericToken(s) + if err != nil { + return types.Decimal128{}, err + } + parseStr, err := prefixedDigitsToDecimalString(token.digits, token.base) + if err != nil { + return types.Decimal128{}, err + } + result, err := types.ParseDecimal128(parseStr, width, scale) + if err != nil { + return types.Decimal128{}, err + } + if token.negative { + result = result.Minus() + } + return result, nil +} + +func parseDecimal256CastString(s string, width, scale int32) (types.Decimal256, error) { + token, err := parseCastNumericToken(s) + if err != nil { + return types.Decimal256{}, err + } + parseStr, err := prefixedDigitsToDecimalString(token.digits, token.base) + if err != nil { + return types.Decimal256{}, err + } + result, err := types.ParseDecimal256(parseStr, width, scale) + if err != nil { + return types.Decimal256{}, err + } + if token.negative { + result = result.Minus() + } + return result, nil +} + func strToDecimal128( from vector.FunctionParameterWrapper[types.Varlena], to *vector.FunctionResult[types.Decimal128], length int, selectList *FunctionSelectList, @@ -5247,7 +5408,7 @@ func strToDecimal128( } else { s := convertByteSliceToString(v) if !isb { - result, err := types.ParseDecimal128(s, totype.Width, totype.Scale) + result, err := parseDecimal128CastString(s, totype.Width, totype.Scale) if err != nil { return err } @@ -5286,7 +5447,7 @@ func strToDecimal256( } else { s := convertByteSliceToString(v) if !isb { - result, err := types.ParseDecimal256(s, totype.Width, totype.Scale) + result, err := parseDecimal256CastString(s, totype.Width, totype.Scale) if err != nil { return err } diff --git a/pkg/sql/plan/function/func_cast_test.go b/pkg/sql/plan/function/func_cast_test.go index ab881c6504a65..d1d1d37f27f02 100644 --- a/pkg/sql/plan/function/func_cast_test.go +++ b/pkg/sql/plan/function/func_cast_test.go @@ -97,6 +97,152 @@ func Test_StringToFloatInvalidConversion(t *testing.T) { } } +func TestCastSignedStringNumericSign(t *testing.T) { + proc := testutil.NewProcess(t) + + testCases := []tcTemp{ + { + info: "signed string cast applies leading sign after parsing numeric body", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"+0x10", "-0x10", " +0012 "}, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{}, nil), + }, + expect: NewFunctionTestResult(types.T_int64.ToType(), false, + []int64{16, -16, 12}, nil), + }, + } + + for _, tc := range testCases { + fcTC := NewFunctionTestCase(proc, tc.inputs, tc.expect, NewCast) + s, info := fcTC.Run() + require.True(t, s, fmt.Sprintf("case is '%s', err info is '%s'", tc.info, info)) + } +} + +func TestCastUnsignedStringNumericSign(t *testing.T) { + proc := testutil.NewProcess(t) + + testCases := []tcTemp{ + { + info: "unsigned string cast allows signed zero and applies plus sign to numeric body", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"-0", "-000", "+000", " +0012 ", "+0x10", "-0x0"}, nil), + NewFunctionTestInput(types.T_uint64.ToType(), []uint64{}, nil), + }, + expect: NewFunctionTestResult(types.T_uint64.ToType(), false, + []uint64{0, 0, 0, 12, 16, 0}, nil), + }, + } + + for _, tc := range testCases { + fcTC := NewFunctionTestCase(proc, tc.inputs, tc.expect, NewCast) + s, info := fcTC.Run() + require.True(t, s, fmt.Sprintf("case is '%s', err info is '%s'", tc.info, info)) + } +} + +func TestCastDecimal64StringNumericSign(t *testing.T) { + proc := testutil.NewProcess(t) + + decimalScale2Type := types.New(types.T_decimal64, 6, 2) + decimalScale0Type := types.New(types.T_decimal64, 6, 0) + + testCases := []tcTemp{ + { + info: "decimal64 string cast applies leading sign after parsing decimal body", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"+000", "+0012", " +0012 "}, nil), + NewFunctionTestInput(decimalScale2Type, []types.Decimal64{}, nil), + }, + expect: NewFunctionTestResult(decimalScale2Type, false, + []types.Decimal64{0, 1200, 1200}, nil), + }, + { + info: "decimal64 string cast applies leading sign after parsing hex numeric body", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"+0x10", "-0x10"}, nil), + NewFunctionTestInput(decimalScale0Type, []types.Decimal64{}, nil), + }, + expect: NewFunctionTestResult(decimalScale0Type, false, + []types.Decimal64{16, types.Decimal64(16).Minus()}, nil), + }, + } + + for _, tc := range testCases { + fcTC := NewFunctionTestCase(proc, tc.inputs, tc.expect, NewCast) + s, info := fcTC.Run() + require.True(t, s, fmt.Sprintf("case is '%s', err info is '%s'", tc.info, info)) + } +} + +func TestCastStringNumericRadixPrefixes(t *testing.T) { + proc := testutil.NewProcess(t) + + decimalScale2Type := types.New(types.T_decimal64, 6, 2) + + testCases := []tcTemp{ + { + info: "signed string cast parses binary, octal, hex, and decimal prefixes", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"0b1010", "+0B1010", "-0b1010", "0o17", "+0O17", "-0o17", "0x123", "+0X123", "-0x123", "0123"}, nil), + NewFunctionTestInput(types.T_int64.ToType(), []int64{}, nil), + }, + expect: NewFunctionTestResult(types.T_int64.ToType(), false, + []int64{10, 10, -10, 15, 15, -15, 291, 291, -291, 123}, nil), + }, + { + info: "unsigned string cast allows negative zero but rejects negative nonzero values", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"+0b1010", "-0b0", "+0o17", "-0o0", "+0x123", "-0x0", "0123"}, nil), + NewFunctionTestInput(types.T_uint64.ToType(), []uint64{}, nil), + }, + expect: NewFunctionTestResult(types.T_uint64.ToType(), false, + []uint64{10, 0, 15, 0, 291, 0, 123}, nil), + }, + { + info: "decimal string cast parses radix prefixes and applies the final sign", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"+0b1010", "-0b1010", "+0o17", "-0o17", "+0x123", "-0x123", "+0123", "-0123"}, nil), + NewFunctionTestInput(decimalScale2Type, []types.Decimal64{}, nil), + }, + expect: NewFunctionTestResult(decimalScale2Type, false, + []types.Decimal64{1000, types.Decimal64(1000).Minus(), 1500, types.Decimal64(1500).Minus(), 29100, types.Decimal64(29100).Minus(), 12300, types.Decimal64(12300).Minus()}, nil), + }, + } + + for _, tc := range testCases { + fcTC := NewFunctionTestCase(proc, tc.inputs, tc.expect, NewCast) + s, info := fcTC.Run() + require.True(t, s, fmt.Sprintf("case is '%s', err info is '%s'", tc.info, info)) + } +} + +func TestCastStringNumericSignRejectsInvalidBodies(t *testing.T) { + invalidInputs := []string{"+-1", "++1", "--1", "- 1", "+ 1", "+", "-", "0b", "0b102", "0o", "0o18", "0x", "0x12g"} + + for _, input := range invalidInputs { + t.Run(input+"_signed", func(t *testing.T) { + _, err := parseSignedCastString(input, 64) + require.Error(t, err) + }) + t.Run(input+"_unsigned", func(t *testing.T) { + _, err := parseUnsignedCastString(input, 64) + require.Error(t, err) + }) + t.Run(input+"_decimal64", func(t *testing.T) { + _, err := parseDecimal64CastString(input, 6, 2) + require.Error(t, err) + }) + } +} + func Test_BinaryToFloatInvalidConversion(t *testing.T) { // Test binary to float conversion with invalid hex values // Should return 0, not error (MySQL non-strict mode behavior) diff --git a/test/distributed/cases/function/func_cast.result b/test/distributed/cases/function/func_cast.result index d397fd82b55cf..15a67c7e2c32f 100644 --- a/test/distributed/cases/function/func_cast.result +++ b/test/distributed/cases/function/func_cast.result @@ -22,6 +22,169 @@ select cast('+0012' as unsigned) as r; 12 select CAST('10x' as unsigned integer); invalid argument cast to uint64, bad value 10x +select cast('+123' as signed); +➤ cast(+123 as signed)[-5,64,0] 𝄀 +123 +select cast('-123' as signed); +➤ cast(-123 as signed)[-5,64,0] 𝄀 +-123 +select cast('0123' as signed); +➤ cast(0123 as signed)[-5,64,0] 𝄀 +123 +select cast('+0123' as signed); +➤ cast(+0123 as signed)[-5,64,0] 𝄀 +123 +select cast('-0123' as signed); +➤ cast(-0123 as signed)[-5,64,0] 𝄀 +-123 +select cast('+123' as unsigned); +➤ cast(+123 as unsigned)[-5,64,0] 𝄀 +123 +select cast('0123' as unsigned); +➤ cast(0123 as unsigned)[-5,64,0] 𝄀 +123 +select cast('-0' as unsigned); +➤ cast(-0 as unsigned)[-5,64,0] 𝄀 +0 +select cast('-000' as unsigned); +➤ cast(-000 as unsigned)[-5,64,0] 𝄀 +0 +select cast('-123' as unsigned); +invalid argument cast to uint64, bad value -123 +select cast('+000' as decimal(6,2)); +➤ cast(+000 as decimal(6, 2))[3,6,2] 𝄀 +0.00 +select cast('-000' as decimal(6,2)); +➤ cast(-000 as decimal(6, 2))[3,6,2] 𝄀 +0.00 +select cast('+0123' as decimal(6,2)); +➤ cast(+0123 as decimal(6, 2))[3,6,2] 𝄀 +123.00 +select cast('-0123' as decimal(6,2)); +➤ cast(-0123 as decimal(6, 2))[3,6,2] 𝄀 +-123.00 +select cast('0b1010' as signed); +➤ cast(0b1010 as signed)[-5,64,0] 𝄀 +10 +select cast('+0b1010' as signed); +➤ cast(+0b1010 as signed)[-5,64,0] 𝄀 +10 +select cast('-0b1010' as signed); +➤ cast(-0b1010 as signed)[-5,64,0] 𝄀 +-10 +select cast('0B1010' as signed); +➤ cast(0B1010 as signed)[-5,64,0] 𝄀 +10 +select cast('+0b1010' as unsigned); +➤ cast(+0b1010 as unsigned)[-5,64,0] 𝄀 +10 +select cast('-0b0' as unsigned); +➤ cast(-0b0 as unsigned)[-5,64,0] 𝄀 +0 +select cast('-0b1010' as unsigned); +invalid argument cast to uint64, bad value -0b1010 +select cast('+0b1010' as decimal(6,2)); +➤ cast(+0b1010 as decimal(6, 2))[3,6,2] 𝄀 +10.00 +select cast('-0b1010' as decimal(6,2)); +➤ cast(-0b1010 as decimal(6, 2))[3,6,2] 𝄀 +-10.00 +select cast('0o17' as signed); +➤ cast(0o17 as signed)[-5,64,0] 𝄀 +15 +select cast('+0o17' as signed); +➤ cast(+0o17 as signed)[-5,64,0] 𝄀 +15 +select cast('-0o17' as signed); +➤ cast(-0o17 as signed)[-5,64,0] 𝄀 +-15 +select cast('0O17' as signed); +➤ cast(0O17 as signed)[-5,64,0] 𝄀 +15 +select cast('+0o17' as unsigned); +➤ cast(+0o17 as unsigned)[-5,64,0] 𝄀 +15 +select cast('-0o0' as unsigned); +➤ cast(-0o0 as unsigned)[-5,64,0] 𝄀 +0 +select cast('-0o17' as unsigned); +invalid argument cast to uint64, bad value -0o17 +select cast('+0o17' as decimal(6,2)); +➤ cast(+0o17 as decimal(6, 2))[3,6,2] 𝄀 +15.00 +select cast('-0o17' as decimal(6,2)); +➤ cast(-0o17 as decimal(6, 2))[3,6,2] 𝄀 +-15.00 +select cast('0x123' as signed); +➤ cast(0x123 as signed)[-5,64,0] 𝄀 +291 +select cast('+0x123' as signed); +➤ cast(+0x123 as signed)[-5,64,0] 𝄀 +291 +select cast('-0x123' as signed); +➤ cast(-0x123 as signed)[-5,64,0] 𝄀 +-291 +select cast('0X123' as signed); +➤ cast(0X123 as signed)[-5,64,0] 𝄀 +291 +select cast('+0x123' as unsigned); +➤ cast(+0x123 as unsigned)[-5,64,0] 𝄀 +291 +select cast('-0x0' as unsigned); +➤ cast(-0x0 as unsigned)[-5,64,0] 𝄀 +0 +select cast('-0x123' as unsigned); +invalid argument cast to uint64, bad value -0x123 +select cast('+0x123' as decimal(6,2)); +➤ cast(+0x123 as decimal(6, 2))[3,6,2] 𝄀 +291.00 +select cast('-0x123' as decimal(6,2)); +➤ cast(-0x123 as decimal(6, 2))[3,6,2] 𝄀 +-291.00 +select cast(' +123 ' as signed); +➤ cast( +123 as signed)[-5,64,0] 𝄀 +123 +select cast(' -0b1010 ' as signed); +➤ cast( -0b1010 as signed)[-5,64,0] 𝄀 +-10 +select cast(' +0o17 ' as signed); +➤ cast( +0o17 as signed)[-5,64,0] 𝄀 +15 +select cast(' -0x123 ' as signed); +➤ cast( -0x123 as signed)[-5,64,0] 𝄀 +-291 +select cast('+-1' as signed); +invalid argument cast to int, bad value +-1 +select cast('++1' as signed); +invalid argument cast to int, bad value ++1 +select cast('--1' as signed); +invalid argument cast to int, bad value --1 +select cast('- 1' as signed); +invalid argument cast to int, bad value - 1 +select cast('+ 1' as signed); +invalid argument cast to int, bad value + 1 +select cast('+' as signed); +invalid argument cast to int, bad value + +select cast('-' as signed); +invalid argument cast to int, bad value - +select cast('0b' as signed); +invalid argument cast to int, bad value 0b +select cast('0b102' as signed); +invalid argument cast to int, bad value 0b102 +select cast('0o' as signed); +invalid argument cast to int, bad value 0o +select cast('0o18' as signed); +invalid argument cast to int, bad value 0o18 +select cast('0x' as signed); +invalid argument cast to int, bad value 0x +select cast('0x12g' as signed); +invalid argument cast to int, bad value 0x12g +select cast('x123' as signed); +invalid argument cast to int, bad value x123 +select cast('abc' as signed); +invalid argument cast to int, bad value abc +select cast('1a' as signed); +invalid argument cast to int, bad value 1a select cast('7e0' as double), cast('7e0' as float), cast(' +7e0' as double), diff --git a/test/distributed/cases/function/func_cast.test b/test/distributed/cases/function/func_cast.test index d32ccf6ea5e1b..b414576b4d9c0 100644 --- a/test/distributed/cases/function/func_cast.test +++ b/test/distributed/cases/function/func_cast.test @@ -7,6 +7,77 @@ select cast('7e0 ' as double), cast('-7.5e1 ' as float); select cast('1e-3 ' as decimal(8,4)); select cast('+0012' as unsigned) as r; select CAST('10x' as unsigned integer); + +# String numeric cast with sign and radix prefixes. +# 0b/0B is binary, 0o/0O is octal, 0x/0X is hexadecimal, and no prefix is decimal. +# Leading zero without a radix prefix is still decimal. +select cast('+123' as signed); +select cast('-123' as signed); +select cast('0123' as signed); +select cast('+0123' as signed); +select cast('-0123' as signed); +select cast('+123' as unsigned); +select cast('0123' as unsigned); +select cast('-0' as unsigned); +select cast('-000' as unsigned); +select cast('-123' as unsigned); +select cast('+000' as decimal(6,2)); +select cast('-000' as decimal(6,2)); +select cast('+0123' as decimal(6,2)); +select cast('-0123' as decimal(6,2)); + +select cast('0b1010' as signed); +select cast('+0b1010' as signed); +select cast('-0b1010' as signed); +select cast('0B1010' as signed); +select cast('+0b1010' as unsigned); +select cast('-0b0' as unsigned); +select cast('-0b1010' as unsigned); +select cast('+0b1010' as decimal(6,2)); +select cast('-0b1010' as decimal(6,2)); + +select cast('0o17' as signed); +select cast('+0o17' as signed); +select cast('-0o17' as signed); +select cast('0O17' as signed); +select cast('+0o17' as unsigned); +select cast('-0o0' as unsigned); +select cast('-0o17' as unsigned); +select cast('+0o17' as decimal(6,2)); +select cast('-0o17' as decimal(6,2)); + +select cast('0x123' as signed); +select cast('+0x123' as signed); +select cast('-0x123' as signed); +select cast('0X123' as signed); +select cast('+0x123' as unsigned); +select cast('-0x0' as unsigned); +select cast('-0x123' as unsigned); +select cast('+0x123' as decimal(6,2)); +select cast('-0x123' as decimal(6,2)); + +select cast(' +123 ' as signed); +select cast(' -0b1010 ' as signed); +select cast(' +0o17 ' as signed); +select cast(' -0x123 ' as signed); + +select cast('+-1' as signed); +select cast('++1' as signed); +select cast('--1' as signed); +select cast('- 1' as signed); +select cast('+ 1' as signed); +select cast('+' as signed); +select cast('-' as signed); +select cast('0b' as signed); +select cast('0b102' as signed); +select cast('0o' as signed); +select cast('0o18' as signed); +select cast('0x' as signed); +select cast('0x12g' as signed); +select cast('x123' as signed); +select cast('abc' as signed); +select cast('1a' as signed); + select cast('7e0' as double), cast('7e0' as float), cast(' +7e0' as double), From 30e254c401baa0d4555124e03ea5279ac023b572 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Mon, 6 Jul 2026 12:16:15 +0800 Subject: [PATCH 2/3] update: --- test/distributed/cases/dtype/decimal.result | 41 +++++---------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/test/distributed/cases/dtype/decimal.result b/test/distributed/cases/dtype/decimal.result index e49fbf8cfb0c3..f05b9a2da5b3b 100644 --- a/test/distributed/cases/dtype/decimal.result +++ b/test/distributed/cases/dtype/decimal.result @@ -135,9 +135,9 @@ INSERT INTO t1 (id,a) VALUES (4,CAST(0xFFFFFFFFFFFFFFFF AS UNSIGNED)); UPDATE t1 SET b = a; select distinct * from t1 where ((a = '2147483647') and (b = '2147483647')); -➤ id[4,32,0] ¦ a[3,21,0] ¦ b[12,-1,0] +[unknown result because it is related to issue#4383] select a,count(a) from t1 group by a having count(a)>=2; -➤ a[3,21,0] ¦ count(a)[-5,64,0] +[unknown result because it is related to issue#4383] CREATE TABLE t_decimal(id decimal(10,5)); INSERT INTO t_decimal VALUES (1), (2),(1.099999999),(2.20000000001); select * from t_decimal; @@ -214,17 +214,11 @@ drop table t1; CREATE TABLE t1 (a decimal(2,1)); INSERT INTO t1 VALUES (1),(0.8999),(0.9); SELECT * FROM t1 WHERE coalesce(a) BETWEEN 0 and 0.9; -➤ a[3,2,1] 𝄀 -0.9 𝄀 -0.9 +[unknown result because it is related to issue#3185] SELECT * FROM t1 WHERE coalesce(a)=0.9; -➤ a[3,2,1] 𝄀 -0.9 𝄀 -0.9 +[unknown result because it is related to issue#3185] SELECT * FROM t1 WHERE coalesce(a) in (0.8,0.9); -➤ a[3,2,1] 𝄀 -0.9 𝄀 -0.9 +[unknown result because it is related to issue#3185] SELECT * FROM t1 WHERE a BETWEEN 0 AND 0.9; ➤ a[3,2,1] 𝄀 0.9 𝄀 @@ -1236,26 +1230,11 @@ SELECT -0.487794599999999999999999999999999945451154 * col2 FROM decimal18; -4.8779459999999999999999999999999994550 𝄀 4.5765056404090315799999999999999994887 SELECT col1 * col2 FROM decimal18; -➤ col1 * col2[3,38,37] 𝄀 -0.1221212134567890987654321333546543213 𝄀 -0.1484484651187895121879845615156784548 𝄀 -0E-37 𝄀 -3.7283784274376200000000000000000000000 𝄀 -3.4979314916658955850103159923577553319 +[unknown result because it is related to issue#8516] SELECT col3 * col2 FROM decimal18; -➤ col3 * col2[8,54,0] 𝄀 -1278945.25 𝄀 -229.3758562273983 𝄀 -0.0 𝄀 -0.0 𝄀 -3.0945498936745635E12 +[unknown result because it is related to issue#8516] SELECT col2 * col4 FROM decimal18; -➤ col2 * col4[8,53,16] 𝄀 -7.815317849845612E7 𝄀 -130.61903846394287 𝄀 -0.0 𝄀 --4544.9845 𝄀 --0.0 +[unknown result because it is related to issue#8516] SELECT col1 / col2 FROM decimal18; ➤ col1 / col2[3,38,37] 𝄀 0.1221212134567890987654321333546543213 𝄀 @@ -1308,7 +1287,7 @@ SELECT 1e18 * CAST('1.2' as decimal(3,2)); ➤ 1e18 * cast(1.2 as decimal(3, 2))[8,54,0] 𝄀 1.2E18 SELECT CAST(CAST('1.234221421' AS decimal(3,2)) AS signed); -➤ cast(cast(1.234221421 as decimal(3, 2)) as as)[-5,64,0] 𝄀 +➤ cast(cast(1.234221421 as decimal(3, 2)) as signed)[-5,64,0] 𝄀 1 set @v1=null; SELECT CAST(@v1 as decimal(22, 2)); @@ -1332,7 +1311,7 @@ SELECT CAST('0101010' AS decimal); ➤ cast(0101010 as decimal(38))[3,39,0] 𝄀 101010 SELECT CAST('' AS decimal); -invalid input: can't cast empty string to Decimal128 +invalid syntax DROP TABLE IF EXISTS decimal20; CREATE TABLE decimal20(v varchar(40), tt tinytext, t text, mt mediumtext, lt longtext); INSERT INTO decimal20 VALUES('1.01415', '2.02115', '-3.03', '444.04', '4515.05'); From e4fed64f2a60322843a9c64d10b4d94a8a742658 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Mon, 6 Jul 2026 14:24:35 +0800 Subject: [PATCH 3/3] update error --- pkg/sql/plan/function/func_cast.go | 12 ++++++------ pkg/sql/plan/function/func_cast_test.go | 14 ++++++++++++++ test/distributed/cases/dtype/decimal.result | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pkg/sql/plan/function/func_cast.go b/pkg/sql/plan/function/func_cast.go index 62966542eba99..3a5162070f3e7 100644 --- a/pkg/sql/plan/function/func_cast.go +++ b/pkg/sql/plan/function/func_cast.go @@ -5099,15 +5099,15 @@ type castNumericToken struct { } func parseCastNumericToken(s string) (castNumericToken, error) { - _, body, negative, _ := splitCastNumericSign(s) + trimmed, body, negative, _ := splitCastNumericSign(s) if body == "" { - return castNumericToken{}, strconv.ErrSyntax + return castNumericToken{}, moerr.NewInvalidInputNoCtxf("%q is invalid numeric string", trimmed) } if body[0] == '+' || body[0] == '-' { - return castNumericToken{}, strconv.ErrSyntax + return castNumericToken{}, moerr.NewInvalidInputNoCtxf("%q is invalid numeric string", trimmed) } if strings.IndexFunc(body, unicode.IsSpace) >= 0 { - return castNumericToken{}, strconv.ErrSyntax + return castNumericToken{}, moerr.NewInvalidInputNoCtxf("%q is invalid numeric string", trimmed) } token := castNumericToken{ @@ -5128,7 +5128,7 @@ func parseCastNumericToken(s string) (castNumericToken, error) { token.digits = body[2:] } if token.digits == "" { - return castNumericToken{}, strconv.ErrSyntax + return castNumericToken{}, moerr.NewInvalidInputNoCtxf("%q is invalid numeric string", trimmed) } } return token, nil @@ -5140,7 +5140,7 @@ func prefixedDigitsToDecimalString(digits string, base int) (string, error) { } var value big.Int if _, ok := value.SetString(digits, base); !ok { - return "", strconv.ErrSyntax + return "", moerr.NewInvalidInputNoCtxf("%q is invalid numeric string", digits) } return value.String(), nil } diff --git a/pkg/sql/plan/function/func_cast_test.go b/pkg/sql/plan/function/func_cast_test.go index d1d1d37f27f02..8c7dc0734e3b8 100644 --- a/pkg/sql/plan/function/func_cast_test.go +++ b/pkg/sql/plan/function/func_cast_test.go @@ -3711,3 +3711,17 @@ func TestDecimal64ToDecimal128FastPaths(t *testing.T) { require.True(t, s, fmt.Sprintf("case is '%s', err info is '%s'", tc.info, info)) } } + +func TestCastNumericTokenInvalidInputErrors(t *testing.T) { + _, err := parseDecimal128CastString("", 38, 0) + require.ErrorContains(t, err, "invalid input:") + require.ErrorContains(t, err, "invalid numeric string") + + _, err = parseCastNumericToken("++1") + require.ErrorContains(t, err, "invalid input:") + require.ErrorContains(t, err, "invalid numeric string") + + _, err = prefixedDigitsToDecimalString("2", 2) + require.ErrorContains(t, err, "invalid input:") + require.ErrorContains(t, err, "invalid numeric string") +} diff --git a/test/distributed/cases/dtype/decimal.result b/test/distributed/cases/dtype/decimal.result index f05b9a2da5b3b..ec570f88141a4 100644 --- a/test/distributed/cases/dtype/decimal.result +++ b/test/distributed/cases/dtype/decimal.result @@ -1311,7 +1311,7 @@ SELECT CAST('0101010' AS decimal); ➤ cast(0101010 as decimal(38))[3,39,0] 𝄀 101010 SELECT CAST('' AS decimal); -invalid syntax +invalid input: "" is invalid numeric string DROP TABLE IF EXISTS decimal20; CREATE TABLE decimal20(v varchar(40), tt tinytext, t text, mt mediumtext, lt longtext); INSERT INTO decimal20 VALUES('1.01415', '2.02115', '-3.03', '444.04', '4515.05');