title: 十分鐘成爲 Contributor 系列 | 重構內建函數進度報告
author: 徐懷宇
date: 2017-07-14
summary: 爲了方便社區同窗更好地參與 TiDB 項目,本文一方面對繼上一篇文章發佈後參考社區的反饋對錶達式計算框架所作的修改進行詳細介紹,另外一方面對還沒有重寫的 built-in 函數進行陳列。mysql
6 月 22 日,TiDB 發佈了一篇如何十分鐘成爲 TiDB Contributor 系列的第二篇文章,向你們介紹如何爲 TiDB 重構 built-in 函數。git
截止到目前,獲得了來自社區的積極支持與熱情反饋,TiDB 參考社區 contributors 的建議,對計算框架進行了部分修改以下降社區同窗參與的難度。github
本文完成如下2 項工做,但願幫助社區更好的參與進 TiDB 的項目中來:sql
共計 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 函數express
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
Coalesce | Uncompress | Log10 | Default | UnaryOp | SysDate |
Greatest | UncompressedLength | Rand | InetAton | IsNull | CurrentDate |
Least | ValidatePasswordStrength | Pow | InetNtoa | In | CurrentTime |
Interval | Database | Round | Inet6Aton | Row | Time |
CaseWhen | FoundRows | Conv | Inet6Ntoa | SetVar | UTCDate |
If | CurrentUser | CRC32 | IsFreeLock | GetVar | UTCTimestamp |
IfNull | User | Sqrt | IsIPv4 | Values | Extract |
NullIf | ConnectionID | Arithmetic | IsIPv4Prefixed | BitCount | DateArith |
AesDecrypt | LastInsertID | Acos | IsIPv6 | Reverse | TimestampDiff |
AesEncrypt | Version | Asin | IsUsedLock | Convert | UnixTimestamp |
Compress | Benchmark | Atan | MasterPosWait | Substring | TimeToSec |
Decode | Charset | Cot | NameConst | SubstringIndex | TimestampAdd |
DesDecrypt | Coercibility | Exp | ReleaseAllLocks | Locate | ToDays |
DesEncrypt | Collation | PI | UUID | Hex | ToSeconds |
Encode | RowCount | Radians | UUIDShort | UnHex | UTCTim |
Encrypt | Regexp | Truncate | AndAnd | Trim | |
OldPassword | Abs | Sleep | OrOr | LTrim | |
RandomBytes | Ceil | Lock | LogicXor | RTrim | |
SHA1 | Floor | ReleaseLock | BitOp | Rpad | |
SHA2 | Log | AnyValue | IsTrueOp | BitLength | |
Char | Format | FromDays | DayOfWeek | Timestamp | |
CharLength | FromBase64 | Hour | DayOfYear | AddTime | |
FindInSet | InsertFunc | Minute | Week | ConvertTz | |
Field | Instr | Second | WeekDay | MakeTime | |
MakeSet | LoadFile | MicroSecond | WeekOfYear | PeriodAdd | |
Oct | Lpad | Month | Year | PeriodDiff | |
Quote | Date | MonthName | YearWeek | Quarter | |
Bin | DateDiff | Now | FromUnixTime | SecToTime | |
Elt | TimeDiff | DayName | GetFormat | SubTime | |
ExportSet | DateFormat | DayOfMonth | StrToDate | TimeFormat |
此處依然使用 Length 函數( expression/builtin_string.go )爲例進行說明,與前文采起相同目錄結構:微信
1. expression/builtin_string.go框架
(1)lengthFunctionClass.getFunction()
方法: 簡化類型推導實現 dom
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))
}複製代碼
注: 單元測試
對於返回值類型爲 string 的函數,須要,注意參考 MySQL 行爲設置
bf.tp.[charset | collate | flag]
查看 MySQL 行爲能夠經過在終端啓動
$ mysql -uroot \-\-column-type-info
,這樣對於每個查詢語句,能夠查看每一列詳細的 metadata
對於返回值類型爲 string 的函數,以 concat 爲例,當存在類型爲 string 且包含 binary flag 的參數時,其返回值也應設置 binary flag
對於返回值類型爲 Time 的函數,須要注意,根據函數行爲,設置bf.tp.Tp = [ TypeDate | TypeDatetime | TypeTimestamp ]
,
若爲 TypeDate/ TypeDatetime
,還需注意推導 bf.tp.Decimal
(即小數位數)
不肯定性的函數:
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
Rand | ConnectionID | CurrentUser | User | Database | RowCount |
Schema | FoundRows | LastInsertId | Version | Sleep | UUID |
GetVar | SetVar | Values | SessionUser | SystemUser |
(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 一下便可。
成爲 New Contributor 贈送限量版馬克杯的活動還在繼續中,任何一個新加入集體的小夥伴都將收到咱們充滿了誠意的禮物,很榮幸可以認識你,也很高興能和你一塊兒堅決地走得更遠。
合併 PR 後自動成爲 Contributor,會收到來自 PingCAP Team 的感謝郵件,請查收郵件並填寫領取表單
後臺 AI 覈查 GitHub ID 及資料信息,確認無誤後隨即使快遞寄出屬於你的限量版馬克杯
瞭解更多關於 TiDB 的資料請登錄咱們的官方網站:pingcap.com
加入 TiDB Contributor Club 請添加咱們的 AI 微信:微信號:tidbai