Skip to content

Commit d9e2cee

Browse files
YangKeaoti-chi-bot
authored andcommitted
This is an automated cherry-pick of pingcap#61476
Signed-off-by: ti-chi-bot <[email protected]>
1 parent c5997c3 commit d9e2cee

File tree

9 files changed

+358
-26
lines changed

9 files changed

+358
-26
lines changed

pkg/expression/builtin_cast.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/pingcap/tidb/pkg/sessionctx/variable"
4141
"github.com/pingcap/tidb/pkg/types"
4242
"github.com/pingcap/tidb/pkg/util/chunk"
43+
"github.com/pingcap/tidb/pkg/util/intest"
4344
"github.com/pingcap/tipb/go-tipb"
4445
)
4546

@@ -111,6 +112,18 @@ var (
111112
_ builtinFunc = &builtinCastJSONAsJSONSig{}
112113
)
113114

115+
const (
116+
maxTinyBlobSize = 255
117+
maxBlobSize = 65535
118+
maxMediumBlobSize = 16777215
119+
maxLongBlobSize = 4294967295
120+
// These two are magic numbers to be compatible with MySQL.
121+
// They are `MaxBlobSize * 4` and `MaxMediumBlobSize * 4`, but multiply by 4 (mblen) is not necessary here. However
122+
// a bigger number is always safer to avoid truncation, so they are kept as is.
123+
castBlobFlen = maxBlobSize * 4
124+
castMediumBlobFlen = maxMediumBlobSize * 4
125+
)
126+
114127
type castAsIntFunctionClass struct {
115128
baseFunctionClass
116129

@@ -299,6 +312,7 @@ func (c *castAsStringFunctionClass) getFunction(ctx sessionctx.Context, args []E
299312
tp.AddFlag(mysql.BinaryFlag)
300313
args[0] = BuildCastFunction(ctx, args[0], tp)
301314
}
315+
<<<<<<< HEAD
302316
argTp := args[0].GetType().EvalType()
303317
switch argTp {
304318
case types.ETInt:
@@ -319,6 +333,13 @@ func (c *castAsStringFunctionClass) getFunction(ctx sessionctx.Context, args []E
319333
bf.tp.SetFlen(args[0].GetType().GetFlen())
320334
}
321335
}
336+
=======
337+
argFt := args[0].GetType(ctx.GetEvalCtx())
338+
adjustRetFtForCastString(bf.tp, argFt)
339+
340+
switch argFt.EvalType() {
341+
case types.ETInt:
342+
>>>>>>> 3c2dc46853f (expression: fix the length of casting from INT/REAL/DECIMAL/.... to string (#61476))
322343
sig = &builtinCastIntAsStringSig{bf}
323344
sig.setPbCode(tipb.ScalarFuncSig_CastIntAsString)
324345
case types.ETReal:
@@ -343,11 +364,160 @@ func (c *castAsStringFunctionClass) getFunction(ctx sessionctx.Context, args []E
343364
sig = &builtinCastStringAsStringSig{bf}
344365
sig.setPbCode(tipb.ScalarFuncSig_CastStringAsString)
345366
default:
367+
<<<<<<< HEAD
346368
panic("unsupported types.EvalType in castAsStringFunctionClass")
369+
=======
370+
return nil, errors.Errorf("cannot cast from %s to %s", argFt.EvalType(), "String")
371+
>>>>>>> 3c2dc46853f (expression: fix the length of casting from INT/REAL/DECIMAL/.... to string (#61476))
347372
}
348373
return sig, nil
349374
}
350375

376+
func adjustRetFtForCastString(retFt, argFt *types.FieldType) {
377+
originalFlen := retFt.GetFlen()
378+
379+
// Only estimate the length for variable length string types, because different length for fixed
380+
// length string types will have different behaviors and may cause compatibility issues.
381+
if retFt.GetType() == mysql.TypeString {
382+
return
383+
}
384+
385+
if argFt.GetType() == mysql.TypeNull {
386+
return
387+
}
388+
389+
argTp := argFt.EvalType()
390+
switch argTp {
391+
case types.ETInt:
392+
if originalFlen == types.UnspecifiedLength {
393+
// check https://github.com/pingcap/tidb/issues/44786
394+
// set flen from integers may truncate integers, e.g. char(1) can not display -1[int(1)]
395+
switch argFt.GetType() {
396+
case mysql.TypeTiny:
397+
if mysql.HasUnsignedFlag(argFt.GetFlag()) {
398+
retFt.SetFlen(3)
399+
} else {
400+
retFt.SetFlen(4)
401+
}
402+
case mysql.TypeShort:
403+
if mysql.HasUnsignedFlag(argFt.GetFlag()) {
404+
retFt.SetFlen(5)
405+
} else {
406+
retFt.SetFlen(6)
407+
}
408+
case mysql.TypeInt24:
409+
if mysql.HasUnsignedFlag(argFt.GetFlag()) {
410+
retFt.SetFlen(8)
411+
} else {
412+
retFt.SetFlen(9)
413+
}
414+
case mysql.TypeLong:
415+
if mysql.HasUnsignedFlag(argFt.GetFlag()) {
416+
retFt.SetFlen(10)
417+
} else {
418+
retFt.SetFlen(11)
419+
}
420+
case mysql.TypeLonglong:
421+
// the length of BIGINT is always 20 without considering the unsigned flag, because the
422+
// bigint range from -9223372036854775808 to 9223372036854775807, and unsigned bigint range
423+
// from 0 to 18446744073709551615, they are all 20 characters long.
424+
retFt.SetFlen(20)
425+
case mysql.TypeYear:
426+
retFt.SetFlen(4)
427+
case mysql.TypeBit:
428+
retFt.SetFlen(argFt.GetFlen())
429+
case mysql.TypeEnum:
430+
intest.Assert(false, "cast Enum to String should not set mysql.EnumSetAsIntFlag")
431+
return
432+
case mysql.TypeSet:
433+
intest.Assert(false, "cast Set to String should not set mysql.EnumSetAsIntFlag")
434+
return
435+
default:
436+
intest.Assert(false, "unknown type %d for INT", argFt.GetType())
437+
return
438+
}
439+
}
440+
case types.ETReal:
441+
// MySQL used 12/22 for float/double, it's because MySQL turns float/double into scientific notation
442+
// in some situations. TiDB choose to use 'f' format for all the cases, so TiDB needs much longer length
443+
// for float/double.
444+
//
445+
// The largest float/double value is around `3.40e38`/`1.79e308`, and the smallest positive float/double value
446+
// is around `1.40e-45`/`4.94e-324`. Therefore, we need at least `1 (sign) + 1 (integer) + 1 (dot) + (45 + 39) (decimal) = 87`
447+
// for float and `1 (sign) + 1 (integer) + 1 (dot) + (324 + 43) (decimal) = 370` for double.
448+
//
449+
// Actually, the golang will usually generate a much smaller string. It used ryu algorithm to generate the shortest
450+
// decimal representation. It's not necessary to keep all decimals. Ref:
451+
// - https://github.com/ulfjack/ryu
452+
// - https://dl.acm.org/doi/10.1145/93548.93559
453+
// So maybe 48/327 is enough for float/double, but we still set 87/370 for safety.
454+
if originalFlen == types.UnspecifiedLength {
455+
if argFt.GetType() == mysql.TypeFloat {
456+
retFt.SetFlen(87)
457+
} else if argFt.GetType() == mysql.TypeDouble {
458+
retFt.SetFlen(370)
459+
}
460+
}
461+
case types.ETDecimal:
462+
if originalFlen == types.UnspecifiedLength {
463+
retFt.SetFlen(decimalPrecisionToLength(argFt))
464+
}
465+
case types.ETDatetime, types.ETTimestamp:
466+
if originalFlen == types.UnspecifiedLength {
467+
if argFt.GetType() == mysql.TypeDate {
468+
retFt.SetFlen(mysql.MaxDateWidth)
469+
} else {
470+
retFt.SetFlen(mysql.MaxDatetimeWidthNoFsp)
471+
}
472+
473+
// Theoretically, the decimal of `DATE` will never be greater than 0.
474+
decimal := argFt.GetDecimal()
475+
if decimal > 0 {
476+
// If the type is datetime or timestamp with fractional seconds, we need to set the length to
477+
// accommodate the fractional seconds part.
478+
retFt.SetFlen(retFt.GetFlen() + 1 + decimal)
479+
}
480+
}
481+
case types.ETDuration:
482+
if originalFlen == types.UnspecifiedLength {
483+
retFt.SetFlen(mysql.MaxDurationWidthNoFsp)
484+
decimal := argFt.GetDecimal()
485+
if decimal > 0 {
486+
// If the type is time with fractional seconds, we need to set the length to
487+
// accommodate the fractional seconds part.
488+
retFt.SetFlen(retFt.GetFlen() + 1 + decimal)
489+
}
490+
}
491+
case types.ETJson:
492+
if originalFlen == types.UnspecifiedLength {
493+
retFt.SetFlen(mysql.MaxLongBlobWidth)
494+
retFt.SetType(mysql.TypeLongBlob)
495+
}
496+
case types.ETVectorFloat32:
497+
498+
case types.ETString:
499+
if originalFlen == types.UnspecifiedLength {
500+
switch argFt.GetType() {
501+
case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString:
502+
if argFt.GetFlen() > 0 {
503+
retFt.SetFlen(argFt.GetFlen())
504+
}
505+
case mysql.TypeTinyBlob:
506+
retFt.SetFlen(maxTinyBlobSize)
507+
case mysql.TypeBlob:
508+
retFt.SetFlen(castBlobFlen)
509+
case mysql.TypeMediumBlob:
510+
retFt.SetFlen(castMediumBlobFlen)
511+
case mysql.TypeLongBlob:
512+
retFt.SetFlen(maxLongBlobSize)
513+
default:
514+
intest.Assert(false, "unknown type %d for String", argFt.GetType())
515+
return
516+
}
517+
}
518+
}
519+
}
520+
351521
type castAsTimeFunctionClass struct {
352522
baseFunctionClass
353523

@@ -1056,7 +1226,12 @@ func (b *builtinCastRealAsStringSig) evalString(row chunk.Row) (res string, isNu
10561226
// If we strconv.FormatFloat the value with 64bits, the result is incorrect!
10571227
bits = 32
10581228
}
1229+
<<<<<<< HEAD
10591230
res, err = types.ProduceStrWithSpecifiedTp(strconv.FormatFloat(val, 'f', -1, bits), b.tp, b.ctx.GetSessionVars().StmtCtx, false)
1231+
=======
1232+
1233+
res, err = types.ProduceStrWithSpecifiedTp(strconv.FormatFloat(val, 'f', -1, bits), b.tp, typeCtx(ctx), false)
1234+
>>>>>>> 3c2dc46853f (expression: fix the length of casting from INT/REAL/DECIMAL/.... to string (#61476))
10601235
if err != nil {
10611236
return res, false, err
10621237
}
@@ -2442,3 +2617,27 @@ func TryPushCastIntoControlFunctionForHybridType(ctx sessionctx.Context, expr Ex
24422617
}
24432618
return expr
24442619
}
2620+
2621+
func decimalPrecisionToLength(ft *types.FieldType) int {
2622+
precision := ft.GetFlen()
2623+
scale := ft.GetDecimal()
2624+
unsigned := mysql.HasUnsignedFlag(ft.GetFlag())
2625+
2626+
if precision == types.UnspecifiedLength || scale == types.UnspecifiedLength {
2627+
return types.UnspecifiedLength
2628+
}
2629+
2630+
ret := precision
2631+
if scale > 0 {
2632+
ret++
2633+
}
2634+
2635+
if !unsigned && precision > 0 {
2636+
ret++ // for negative sign
2637+
}
2638+
2639+
if ret == 0 {
2640+
return 1
2641+
}
2642+
return ret
2643+
}

0 commit comments

Comments
 (0)