-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
evalengine: Support built-in MySQL function for CONV function #11566
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,11 @@ package evalengine | |
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"math" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"vitess.io/vitess/go/mysql/collations" | ||
"vitess.io/vitess/go/sqltypes" | ||
|
@@ -265,3 +270,109 @@ func (builtinRepeat) typeof(env *ExpressionEnv, args []Expr) (sqltypes.Type, fla | |
|
||
return sqltypes.VarChar, f1 | ||
} | ||
|
||
type builtinConv struct { | ||
} | ||
|
||
func (builtinConv) call(env *ExpressionEnv, args []EvalResult, result *EvalResult) { | ||
const MaxUint = 18446744073709551615 | ||
var fromNum uint64 | ||
var fromNumNeg uint64 | ||
var isNeg bool | ||
var rawString string | ||
inarg := &args[0] | ||
inarg2 := &args[1] | ||
inarg3 := &args[2] | ||
fromBase := inarg2.int64() | ||
toBase := inarg3.int64() | ||
fromNum = 0 | ||
t := inarg.typeof() | ||
|
||
if inarg.isNull() || inarg2.isNull() || inarg3.isNull() || fromBase < 2 || fromBase > 36 || toBase < 2 || toBase > 36 { | ||
result.setNull() | ||
return | ||
} | ||
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. I don't think the checks |
||
|
||
rawString = string(inarg.toRawBytes()) | ||
rawString = strings.ToLower(rawString) | ||
|
||
if t == sqltypes.Float64 { | ||
for i, c := range rawString { | ||
if c == '-' { | ||
continue | ||
} | ||
if (fromBase <= 9 && c >= '0' && c <= rune('0'+fromBase)) || (fromBase > 9 && ((c >= '0' && c <= '9') || (c >= 'a' && c <= rune('a'+fromBase-9)))) { | ||
continue | ||
} else { | ||
rawString = rawString[:i] | ||
break | ||
} | ||
} | ||
} | ||
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. Is there any specific reason why you need the float type as a separate case? |
||
|
||
re, _ := regexp.Compile(`[+-]?[0-9.x]+[a-vA-Vx]*`) | ||
for _, num := range re.FindAllString(rawString, -1) { | ||
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. Why do you need all substring matches? Don't you only need the first one? In what case would have more than 1 matches? |
||
isNeg = false | ||
reFindDot, _ := regexp.Compile(`\.`) | ||
reFindNeg, _ := regexp.Compile(`^-[0-9.]+[a-vA-V]*`) | ||
if reFindDot.MatchString(num) { | ||
temp, _ := strconv.ParseFloat(num, 64) | ||
temp = math.Trunc(temp) | ||
num = fmt.Sprint(int64(temp)) | ||
} | ||
if reFindNeg.MatchString(num) { | ||
isNeg = true | ||
num = num[1:] | ||
} | ||
|
||
if transNum, err := strconv.ParseUint(num, int(fromBase), 64); err == nil { | ||
if isNeg { | ||
fromNumNeg = fromNumNeg + transNum | ||
} else { | ||
fromNum = fromNum + transNum | ||
} | ||
} else if strings.Contains(err.Error(), "value out of range") { | ||
if isNeg { | ||
fromNumNeg = MaxUint | ||
|
||
} else { | ||
fromNum = MaxUint | ||
} | ||
} else { | ||
fromNum = 0 | ||
break | ||
} | ||
} | ||
|
||
if fromNumNeg < fromNum { | ||
fromNum = fromNum - fromNumNeg | ||
} else if fromNumNeg == MaxUint { | ||
fromNum = 0 | ||
} else { | ||
fromNum = fromNumNeg - fromNum | ||
} | ||
var toNum string | ||
if isNeg { | ||
temp := strconv.FormatUint(uint64(-fromNum), int(toBase)) | ||
toNum = strings.ToUpper(temp) | ||
} else { | ||
temp := strconv.FormatUint(fromNum, int(toBase)) | ||
toNum = strings.ToUpper(temp) | ||
} | ||
|
||
inarg.makeTextualAndConvert(env.DefaultCollation) | ||
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. From what I can see, there really is no need to convert inarg to the environment's collation. You can just extract the collation from inarg and change the collation and then type cast the result directly using that. However, I don't know what coercability or repotoire we want. Is there some way to verify from MySQL what it does? 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.
I have no idea how to do in this way. |
||
result.setString(toNum, inarg.collation()) | ||
} | ||
|
||
func (builtinConv) typeof(env *ExpressionEnv, args []Expr) (sqltypes.Type, flag) { | ||
if len(args) != 3 { | ||
throwArgError("CONV") | ||
} | ||
_, f1 := args[0].typeof(env) | ||
_, f2 := args[1].typeof(env) | ||
// typecheck the right-hand argument but ignore its flags | ||
args[1].typeof(env) | ||
args[2].typeof(env) | ||
|
||
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. Missing typecheck for |
||
return sqltypes.VarChar, f1 & f2 | ||
} |
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.
These still aren't strong enough cases. What happens if we have a
+
in the beginning? What if we have 2 of those in the beginning? What if it is a string with a + and not a number?