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 {
- return nil, 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
- decimal int
- }{
- ...
- // 此处添加对 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 一下即可。
PS:欢迎小伙伴们加入 TiDB Contributor Club ,在这里将和你一起分享开源的力量,加入 TiDB Contributor Club 请添加我们的 AI 微信号:tidbai