6 月 22 日,TiDB 发布了一篇如何十分钟成为 TiDB Contributor 系列的第二篇文章,向大家介绍如何为 TiDB 重构 built-in 函数。截止到目前,得到了来自社区的积极支持与热情反馈,TiDB 参考社区 contributors 的建议,对计算框架进行了部分修改以降低社区同学参与的难度。
本文完成以下2 项工作,希望帮助社区更好的参与进 TiDB 的项目中来:
-
对尚未重写的 built-in 函数进行陈列
-
对继上篇文章后,计算框架所进行的修改,进行详细介绍
一. 尚未重写的 built-in 函数陈列如下:
共计 165 个
在 expression 目录下运行grep -rn "^\tbaseBuiltinFunc$" -B 1 * | grep "Sig struct {" | awk -F "Sig" '{print $1}' | awk -F "builtin" '{print $3}' > ~/Desktop/func.txt命令可以获得所有未实现的 built-in 函数
-
Coalesce
-
Greatest
-
Least
-
Interval
-
CaseWhen
-
If
-
IfNull
-
NullIf
-
AesDecrypt
-
AesEncrypt
-
Compress
-
Decode
-
DesDecrypt
-
DesEncrypt
-
Encode
-
Encrypt
-
OldPassword
-
RandomBytes
-
SHA1
-
SHA2
-
Uncompress
-
UncompressedLength
-
ValidatePasswordStrength
-
Database
-
FoundRows
-
CurrentUser
-
User
-
ConnectionID
-
LastInsertID
-
Version
-
Benchmark
-
Charset
-
Coercibility
-
Collation
-
RowCount
-
Regexp
-
Abs
-
Ceil
-
Floor
-
Log
-
Log10
-
Rand
-
Pow
-
Round
-
Conv
-
CRC32
-
Sqrt
-
Arithmetic
-
Acos
-
Asin
-
Atan
-
Cot
-
Exp
-
PI
-
Radians
-
Truncate
-
Sleep
-
Lock
-
ReleaseLock
-
AnyValue
-
Default
-
InetAton
-
InetNtoa
-
Inet6Aton
-
Inet6Ntoa
-
IsFreeLock
-
IsIPv4
-
IsIPv4Prefixed
-
IsIPv6
-
IsUsedLock
-
MasterPosWait
-
NameConst
-
ReleaseAllLocks
-
UUID
-
UUIDShort
-
AndAnd
-
OrOr
-
LogicXor
-
BitOp
-
IsTrueOp
-
UnaryOp
-
IsNull
-
In
-
Row
-
SetVar
-
GetVar
-
Values
-
BitCount
-
Reverse
-
Convert
-
Substring
-
SubstringIndex
-
Locate
-
Hex
-
UnHex
-
Trim
-
LTrim
-
RTrim
-
Rpad
-
BitLength
-
Char
-
CharLength
-
FindInSet
-
Field
-
MakeSet
-
Oct
-
Quote
-
Bin
-
Elt
-
ExportSet
-
Format
-
FromBase64
-
InsertFunc
-
Instr
-
LoadFile
-
Lpad
-
Date
-
DateDiff
-
TimeDiff
-
DateFormat
-
FromDays
-
Hour
-
Minute
-
Second
-
MicroSecond
-
Month
-
MonthName
-
Now
-
DayName
-
DayOfMonth
-
DayOfWeek
-
DayOfYear
-
Week
-
WeekDay
-
WeekOfYear
-
Year
-
YearWeek
-
FromUnixTime
-
GetFormat
-
StrToDate
-
SysDate
-
CurrentDate
-
CurrentTime
-
Time
-
UTCDate
-
UTCTimestamp
-
Extract
-
DateArith
-
TimestampDiff
-
UnixTimestamp
-
Timestamp
-
AddTime
-
ConvertTz
-
MakeTime
-
PeriodAdd
-
PeriodDiff
-
Quarter
-
SecToTime
-
SubTime
-
TimeFormat
-
TimeToSec
-
TimestampAdd
-
ToDays
-
ToSeconds
-
UTCTim
二. 计算框架进行的修改:
此处依然使用 Length 函数( expression/builtin_string.go )为例进行说明,与前文采取相同目录结构:
1. expression/builtin_string.go
(1)lengthFunctionClass.getFunction() 方法:简化类型推导实现
getFunction 方法用来生成 built-in 函数对应的函数签名,在构造 ScalarFunction 时被调用
-
func(c*lengthFunctionClass) getFunction(args[]Expression, ctx context.Context)(builtinFunc, error){
-
// 此处简化类型推导过程,对 newBaseBuiltinFuncWithTp() 实现进行修改,新的实现中,传入 Length 返回值类型 tpInt 表示返回值类型为 int,参数类型 tpString 表示返回值类型为 string
-
bf, err := newBaseBuiltinFuncWithTp(args, ctx, tpInt, tpString)
-
if err !=nil{
-
returnnil, errors.Trace(err)
-
}
-
// 此处参考 MySQL 实现,设置返回值长度为 10(character length)
-
// 对于 int/double/decimal/time/duration 类型返回值,已在 newBaseBuiltinFuncWithTp() 中默认调用 types.setBinChsClnFlag() 方法,此处无需再进行设置
-
bf.tp.Flen=10
-
sig:=&builtinLengthSig{baseIntBuiltinFunc{bf}}
-
return sig.setSelf(sig), errors.Trace(c.verifyArgs(args))
-
}
注:
a. 对于返回值类型为 string 的函数,需要,注意参考 MySQL 行为设置 bf.tp.[charset | collate | flag]
查看 MySQL 行为可以通过在终端启动$ mysql -uroot \-\-column-type-info,这样对于每一个查询语句,可以查看每一列详细的 metadata
对于返回值类型为 string 的函数,以concat 为例,当存在类型为 string 且包含 binary flag 的参数时,其返回值也应设置 binary flag
b. 对于返回值类型为 Time 的函数,需要注意,根据函数行为,设置
bf.tp.Tp = [ TypeDate | TypeDatetime | TypeTimestamp ] ,若为 TypeDate/ TypeDatetime,还需注意推导 bf.tp.Decimal (即小数位数)
c. 不确定性的函数:
-
Rand
-
ConnectionID
-
CurrentUser
-
User
-
Database
-
Schema
-
FoundRows
-
LastInsertId
-
Version
-
Sleep
-
GetVar
-
SetVar
-
Values
-
SessionUser
-
SystemUser
-
RowCount
-
UUID
(2)实现 builtinLengthSig.evalInt() 方法:保持不变,此处请注意修改该函数的注释 (s/ eval/ evalXXX)
2. expression/builtin_string_test.go
-
func(s*testEvaluatorSuite)TestLength(c*C){
-
defer testleak.AfterTest(c)()
-
cases:=[]struct{
-
args interface{}
-
expected int64
-
isNil bool
-
getErr bool
-
}{
-
......
-
}
-
-
for _, t:= range cases{
-
f, err := newFunctionForTest(s.ctx, ast.Length, primitiveValsToConstants([]interface{}{t.args})...)
-
c.Assert(err,IsNil)
-
d, err := f.Eval(nil)
-
// 注意此处不再对 LENGTH 函数的返回值类型进行测试,相应测试被移动到 plan/typeinfer_test.go/TestInferType 函数中,(注意不是expression/typeinferer_test.go)
-
if t.getErr{
-
c.Assert(err,NotNil)
-
}else{
-
c.Assert(err,IsNil)
-
if t.isNil{
-
c.Assert(d.Kind(),Equals, types.KindNull)
-
}else{
-
c.Assert(d.GetInt64(),Equals, t.expected)
-
}
-
}
-
}
-
-
// 测试函数是否具有确定性
-
// 在 review 社区的 PRs 过程中发现,这个测试经常会被遗漏,烦请留意
-
f, err := funcs[ast.Length].getFunction([]Expression{Zero}, s.ctx)
-
c.Assert(err,IsNil)
-
c.Assert(f.isDeterministic(),IsTrue)
-
}
3. executor/executor_test.go
与上一篇文章保持不变,需要注意的是,为了保证可读性, TestStringBuiltin() 方法仅对 expression/builtin_string.go 文件中的 built-in 函数进行测试。如果 executor_test.go 文件中不存在对应的 TestXXXBuiltin() 方法,可以新建一个对应的测试函数。
4. plan/typeinfer_test.go
-
func(s*testPlanSuite)TestInferType(c*C){
-
....
-
tests:=[]struct{
-
sql string
-
tp byte
-
chs string
-
flag byte
-
flen int
-
decimalint
-
}{
-
...
-
// 此处添加对 length 函数返回值类型的测试
-
// 此处注意,对于返回值类型、长度等受参数影响的函数,此处测试请尽量覆盖全面
-
{"length(c_char, c_char)", mysql.TypeLonglong, charset.CharsetBin, mysql.BinaryFlag,10,0},
-
...
-
}
-
for _, tt:= range tests{
-
...
-
}
-
}
注:
当有多个 PR 同时在该文件中添加测试时,若有别的 contributor 的 PR 先于自己的 PR merge 进 master,有可能会发生冲突,此时在本地 merge 一下 master 分支,解决一下再 push 一下即可。
成为 New Contributor 赠送限量版马克杯的活动还在继续中,任何一个新加入集体的小伙伴都将收到我们充满了诚意的礼物,很荣幸能够认识你,也很高兴能和你一起坚定地走得更远。
成为 New Contributor 获赠限量版马克杯,马克杯获取流程如下:
-
提交 PR
-
PR提交之后,请耐心等待维护者进行 Review。
-
目前一般在一到两个工作日内都会进行 Review,如果当前的 PR 堆积数量较多可能回复会比较慢。
-
代码提交后 CI 会执行我们内部的测试,你需要保证所有的单元测试是可以通过的。期间可能有其它的提交会与当前 PR 冲突,这时需要修复冲突。
-
维护者在 Review 过程中可能会提出一些修改意见。修改完成之后如果 reviewer 认为没问题了,你会收到 LGTM(looks good to me) 的回复。当收到两个及以上的 LGTM 后,该 PR 将会被合并。
-
合并 PR 后自动成为 Contributor,会收到来自 PingCAP Team 的感谢邮件,请查收邮件并填写领取表单
-
后台 AI 核查 GitHub ID 及资料信息,确认无误后随即便快递寄出属于你的限量版马克杯
-
期待你分享自己参与开源项目的感想和经验,TiDB Contributor Club 将和你一起分享开源的力量
了解更多关于 TiDB 的资料请登陆我们的官方网站:https://pingcap.com
加入 TiDB Contributor Club 请添加我们的 AI 微信号:tidbai