-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
planner: fix cast(col) = range couldn't build range when cast function doesn't contain any precision loss in some cases | tidb-test=pr/2206 #46303
Merged
Merged
Changes from 11 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
1736a74
fix cast(col) = range couldn't build range when cast doesn't contain …
AilinKid 371d38a
.
AilinKid 173ae4b
make fmt
AilinKid fbfff6c
address comment
AilinKid f3e40ea
refactor and move elimination logic of no-precision-loss cast out fro…
AilinKid 8ba81e7
clean code
AilinKid 527ea35
add function comment
AilinKid 70ded8b
.
AilinKid 6f37c0a
address kunqin's comment
AilinKid 92b47f1
make fmt
AilinKid 5a0e891
.
AilinKid ccd797b
address weizheng's comment
AilinKid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -707,6 +707,153 @@ | |
return newExprs, flag | ||
} | ||
|
||
// todo: consider more no precision-loss downcast cases. | ||
func noPrecisionLossCastCompatible(cast, argCol *types.FieldType) bool { | ||
// now only consider varchar type and integer. | ||
if !(types.IsTypeVarchar(cast.GetType()) && types.IsTypeVarchar(argCol.GetType())) && | ||
!(mysql.IsIntegerType(cast.GetType()) && mysql.IsIntegerType(argCol.GetType())) { | ||
// varchar type and integer on the storage layer is quite same, while the char type has its padding suffix. | ||
return false | ||
} | ||
if types.IsTypeVarchar(cast.GetType()) { | ||
// cast varchar function only bear the flen extension. | ||
if cast.GetFlen() < argCol.GetFlen() { | ||
return false | ||
} | ||
if !collate.CompatibleCollate(cast.GetCollate(), argCol.GetCollate()) { | ||
return false | ||
} | ||
} else { | ||
// For integers, we should ignore the potential display length represented by flen, using the default flen of the type. | ||
castFlen, _ := mysql.GetDefaultFieldLengthAndDecimal(cast.GetType()) | ||
originFlen, _ := mysql.GetDefaultFieldLengthAndDecimal(argCol.GetType()) | ||
// cast integer function only bear the flen extension and signed symbol unchanged. | ||
if castFlen < originFlen { | ||
return false | ||
} | ||
if mysql.HasUnsignedFlag(cast.GetFlag()) != mysql.HasUnsignedFlag(argCol.GetFlag()) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func unwrapCast(sctx sessionctx.Context, parentF *ScalarFunction, castOffset int) (Expression, bool) { | ||
_, collation := parentF.CharsetAndCollation() | ||
cast, ok := parentF.GetArgs()[castOffset].(*ScalarFunction) | ||
if !ok || cast.FuncName.L != ast.Cast { | ||
return parentF, false | ||
} | ||
// eg: if (cast(A) EQ const) with incompatible collation, even if cast is eliminated, the condition still can not be used to build range. | ||
if cast.RetType.EvalType() == types.ETString && !collate.CompatibleCollate(cast.RetType.GetCollate(), collation) { | ||
return parentF, false | ||
} | ||
// 1-castOffset should be constant | ||
if _, ok := parentF.GetArgs()[1-castOffset].(*Constant); !ok { | ||
return parentF, false | ||
} | ||
|
||
// the direct args of cast function should be column. | ||
c, ok := cast.GetArgs()[0].(*Column) | ||
if !ok { | ||
return parentF, false | ||
} | ||
|
||
// current only consider varchar and integer | ||
if !noPrecisionLossCastCompatible(cast.RetType, c.RetType) { | ||
return parentF, false | ||
} | ||
|
||
// the column is covered by indexes, deconstructing it out. | ||
if castOffset == 0 { | ||
return NewFunctionInternal(sctx, parentF.FuncName.L, parentF.RetType, c, parentF.GetArgs()[1]), true | ||
} | ||
return NewFunctionInternal(sctx, parentF.FuncName.L, parentF.RetType, parentF.GetArgs()[0], c), true | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove the empty line. |
||
} | ||
|
||
// eliminateCastFunction will detect the original arg before and the cast type after, once upon | ||
// there is no precision loss between them, current cast wrapper can be eliminated. For string | ||
// type, collation is also taken into consideration. (mainly used to build range or point) | ||
func eliminateCastFunction(sctx sessionctx.Context, expr Expression) (_ Expression, changed bool) { | ||
f, ok := expr.(*ScalarFunction) | ||
if !ok { | ||
return expr, false | ||
} | ||
_, collation := expr.CharsetAndCollation() | ||
switch f.FuncName.L { | ||
case ast.LogicOr: | ||
dnfItems := FlattenDNFConditions(f) | ||
rmCast := false | ||
rmCastItems := make([]Expression, len(dnfItems)) | ||
for i, dnfItem := range dnfItems { | ||
newExpr, curDowncast := eliminateCastFunction(sctx, dnfItem) | ||
rmCastItems[i] = newExpr | ||
if curDowncast { | ||
rmCast = true | ||
} | ||
} | ||
if rmCast { | ||
// compose the new DNF expression. | ||
return ComposeDNFCondition(sctx, rmCastItems...), true | ||
} | ||
return expr, false | ||
case ast.LogicAnd: | ||
cnfItems := FlattenCNFConditions(f) | ||
rmCast := false | ||
rmCastItems := make([]Expression, len(cnfItems)) | ||
for i, cnfItem := range cnfItems { | ||
newExpr, curDowncast := eliminateCastFunction(sctx, cnfItem) | ||
rmCastItems[i] = newExpr | ||
if curDowncast { | ||
rmCast = true | ||
} | ||
} | ||
if rmCast { | ||
// compose the new CNF expression. | ||
return ComposeCNFCondition(sctx, rmCastItems...), true | ||
} | ||
return expr, false | ||
case ast.EQ, ast.NullEQ, ast.LE, ast.GE, ast.LT, ast.GT: | ||
// for case: eq(cast(test.t2.a, varchar(100), "aaaaa"), once t2.a is covered by index or pk, try deconstructing it out. | ||
if newF, ok := unwrapCast(sctx, f, 0); ok { | ||
return newF, true | ||
} | ||
// for case: eq("aaaaa", cast(test.t2.a, varchar(100)), once t2.a is covered by index or pk, try deconstructing it out. | ||
if newF, ok := unwrapCast(sctx, f, 1); ok { | ||
return newF, true | ||
} | ||
case ast.In: | ||
// case for: cast(a<int> as bigint) in (1,2,3), we could deconstruct column 'a out directly. | ||
cast, ok := f.GetArgs()[0].(*ScalarFunction) | ||
if !ok || cast.FuncName.L != ast.Cast { | ||
return expr, false | ||
} | ||
// eg: if (cast(A) IN {const}) with incompatible collation, even if cast is eliminated, the condition still can not be used to build range. | ||
if cast.RetType.EvalType() == types.ETString && !collate.CompatibleCollate(cast.RetType.GetCollate(), collation) { | ||
return expr, false | ||
} | ||
for _, arg := range f.GetArgs()[1:] { | ||
if _, ok := arg.(*Constant); !ok { | ||
return expr, false | ||
} | ||
} | ||
// the direct args of cast function should be column. | ||
c, ok := cast.GetArgs()[0].(*Column) | ||
if !ok { | ||
return expr, false | ||
} | ||
// current only consider varchar and integer | ||
if !noPrecisionLossCastCompatible(cast.RetType, c.RetType) { | ||
return expr, false | ||
} | ||
newArgs := []Expression{c} | ||
newArgs = append(newArgs, f.GetArgs()[1:]...) | ||
return NewFunctionInternal(sctx, f.FuncName.L, f.RetType, newArgs...), true | ||
} | ||
return expr, false | ||
} | ||
|
||
// pushNotAcrossExpr try to eliminate the NOT expr in expression tree. | ||
// Input `not` indicates whether there's a `NOT` be pushed down. | ||
// Output `changed` indicates whether the output expression differs from the | ||
|
@@ -780,6 +927,15 @@ | |
return newExpr | ||
} | ||
|
||
// EliminateNoPrecisionLossCast remove the redundant cast function for range build convenience. | ||
// 1: deeper cast embedded in other complicated function will not be considered. | ||
// 2: cast args should be one for original base column and one for constant. | ||
// 3: some collation compatibility and precision loss will be considered when remove this cast func. | ||
func EliminateNoPrecisionLossCast(sctx sessionctx.Context, expr Expression) Expression { | ||
newExpr, _ := eliminateCastFunction(sctx, expr) | ||
return newExpr | ||
} | ||
|
||
// ContainOuterNot checks if there is an outer `not`. | ||
func ContainOuterNot(expr Expression) bool { | ||
return containOuterNot(expr, false) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice that only
BIGINT
is handled inGetDefaultFieldLengthAndDecimal
, other int types likeTINYINT
,SMALLINT
, etc. are not handled, and-1
will be returned for them.(Also, I didn't quite understand why the length of
TypeLonglong
is22
in this function)I didn't verify it, but looks like this will possibly cause bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In
GetDefaultFieldLengthAndDecimal
use maps like belowDid you cite the wrong function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I'm wrong. I took
defaultLengthAndDecimalForCast
as it.