成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

十分鐘成為 Contributor 系列 | 為 TiDB 重構(gòu) built-in 函數(shù)

xzavier / 3055人閱讀

摘要:同樣的,對(duì)于上圖表達(dá)式樹(shù)中的表達(dá)式,計(jì)算前需要將其參數(shù)分別轉(zhuǎn)化為類(lèi)型對(duì)于表達(dá)式,計(jì)算前需要將其參數(shù)分別轉(zhuǎn)化為類(lèi)型。成為獲贈(zèng)限量版馬克杯,馬克杯獲取流程如下提交提交之后,請(qǐng)耐心等待維護(hù)者進(jìn)行。當(dāng)收到兩個(gè)及以上的后,該將會(huì)被合并。

這是十分鐘成為 TiDB Contributor 系列的第二篇文章,讓大家可以無(wú)門(mén)檻參與大型開(kāi)源項(xiàng)目,感謝社區(qū)為 TiDB 帶來(lái)的貢獻(xiàn),也希望參與 TiDB Community 能為你的生活帶來(lái)更多有意義的時(shí)刻。

為了加速表達(dá)式計(jì)算速度,最近我們對(duì)表達(dá)式的計(jì)算框架進(jìn)行了重構(gòu),這篇教程為大家分享如何利用新的計(jì)算框架為 TiDB 重寫(xiě)或新增 built-in 函數(shù)。對(duì)于部分背景知識(shí)請(qǐng)參考這篇文章,本文將首先介紹利用新的表達(dá)式計(jì)算框架重構(gòu) built-in 函數(shù)實(shí)現(xiàn)的流程,然后以一個(gè)函數(shù)作為示例進(jìn)行詳細(xì)說(shuō)明,最后介紹重構(gòu)前后表達(dá)式計(jì)算框架的區(qū)別。

重構(gòu) built-in 函數(shù)整體流程

在 TiDB 源碼 expression 目錄下選擇任一感興趣的函數(shù),假設(shè)函數(shù)名為 XX

重寫(xiě) XXFunctionClass.getFunction() 方法

該方法參照 MySQL 規(guī)則,根據(jù) built-in 函數(shù)的參數(shù)類(lèi)型推導(dǎo)函數(shù)的返回值類(lèi)型

根據(jù)參數(shù)的個(gè)數(shù)、類(lèi)型、以及函數(shù)的返回值類(lèi)型生成不同的函數(shù)簽名,關(guān)于函數(shù)簽名的詳細(xì)介紹見(jiàn)文末附錄

實(shí)現(xiàn)該 built-in 函數(shù)對(duì)應(yīng)的所有函數(shù)簽名的 evalYY() 方法,此處 YY 表示該函數(shù)簽名的返回值類(lèi)型

添加測(cè)試:

在 expression 目錄下,完善已有的 TestXX() 方法中關(guān)于該函數(shù)實(shí)現(xiàn)的測(cè)試

在 executor 目錄下,添加 SQL 層面的測(cè)試

運(yùn)行 make dev,確保所有的 test cast 都能跑過(guò)

示例

這里以重寫(xiě) LENGTH() 函數(shù)的 PR 為例,進(jìn)行詳細(xì)說(shuō)明

首先看 expression/builtin_string.go:

(1)實(shí)現(xiàn) lengthFunctionClass.getFunction() 方法

該方法主要完成兩方面工作:

參照 MySQL 規(guī)則推導(dǎo) LEGNTH 的返回值類(lèi)型

根據(jù) LENGTH 函數(shù)的參數(shù)個(gè)數(shù)、類(lèi)型及返回值類(lèi)型生成函數(shù)簽名。由于 LENGTH 的參數(shù)個(gè)數(shù)、類(lèi)型及返回值類(lèi)型只存在確定的一種情況,因此此處沒(méi)有定義新的函數(shù)簽名類(lèi)型,而是修改已有的 builtinLengthSig,使其組合了 baseIntBuiltinFunc(表示該函數(shù)簽名返回值類(lèi)型為 int)

type builtinLengthSig struct {
    baseIntBuiltinFunc
}
 
func (c *lengthFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
    // 參照 MySQL 規(guī)則,對(duì) LENGTH 函數(shù)返回值類(lèi)型進(jìn)行推導(dǎo)
    tp := types.NewFieldType(mysql.TypeLonglong)
    tp.Flen = 10
    types.SetBinChsClnFlag(tp)
           
    // 根據(jù)參數(shù)個(gè)數(shù)、類(lèi)型及返回值類(lèi)型生成對(duì)應(yīng)的函數(shù)簽名,注意此處與重構(gòu)前不同,使用的是 newBaseBuiltinFuncWithTp 方法,而非 newBaseBuiltinFunc 方法
    // newBaseBuiltinFuncWithTp 的函數(shù)聲明中,args 表示函數(shù)的參數(shù),tp 表示函數(shù)的返回值類(lèi)型,argsTp 表示該函數(shù)簽名中所有參數(shù)對(duì)應(yīng)的正確類(lèi)型
    // 因?yàn)?LENGTH 的參數(shù)個(gè)數(shù)為1,參數(shù)類(lèi)型為 string,返回值類(lèi)型為 int,因此此處傳入 tp 表示函數(shù)的返回值類(lèi)型,傳入 tpString 用來(lái)標(biāo)識(shí)參數(shù)的正確類(lèi)型。對(duì)于多個(gè)參數(shù)的函數(shù),調(diào)用 newBaseBuiltinFuncWithTp 時(shí),需要傳入所有參數(shù)的正確類(lèi)型
    bf, err := newBaseBuiltinFuncWithTp(args, tp, ctx, tpString)
    if err != nil {
        return nil, errors.Trace(err)
    }
    sig := &builtinLengthSig{baseIntBuiltinFunc{bf}}
    return sig.setSelf(sig), errors.Trace(c.verifyArgs(args))
}

(2) 實(shí)現(xiàn) builtinLengthSig.evalInt() 方法

func (b *builtinLengthSig) evalInt(row []types.Datum) (int64, bool, error) {
    // 對(duì)于函數(shù)簽名 builtinLengthSig,其參數(shù)類(lèi)型已確定為 string 類(lèi)型,因此直接調(diào)用 b.args[0].EvalString() 方法計(jì)算參數(shù)
    val, isNull, err := b.args[0].EvalString(row, b.ctx.GetSessionVars().StmtCtx)
    if isNull || err != nil {
        return 0, isNull, errors.Trace(err)
    }
    return int64(len([]byte(val))), false, nil
}
然后看 expression/builtin_string_test.go,對(duì)已有的 TestLength() 方法進(jìn)行完善:
func (s *testEvaluatorSuite) TestLength(c *C) {
    defer testleak.AfterTest(c)() // 監(jiān)測(cè) goroutine 泄漏的工具,可以直接照搬
      // cases 的測(cè)試用例對(duì) length 方法實(shí)現(xiàn)進(jìn)行測(cè)試
    // 此處注意,除了正常 case 之外,最好能添加一些異常的 case,如輸入值為 nil,或者是多種類(lèi)型的參數(shù)
    cases := []struct {
        args     interface{}
        expected int64
        isNil    bool
        getErr   bool
    }{
        {"abc", 3, false, false},
        {"你好", 6, false, false},
        {1, 1, false, false},
        ...
    }
    for _, t := range cases {
        f, err := newFunctionForTest(s.ctx, ast.Length, primitiveValsToConstants([]interface{}{t.args})...)
        c.Assert(err, IsNil)
        // 以下對(duì) LENGTH 函數(shù)的返回值類(lèi)型進(jìn)行測(cè)試
        tp := f.GetType()
        c.Assert(tp.Tp, Equals, mysql.TypeLonglong)
        c.Assert(tp.Charset, Equals, charset.CharsetBin)
        c.Assert(tp.Collate, Equals, charset.CollationBin)
        c.Assert(tp.Flag, Equals, uint(mysql.BinaryFlag))
        c.Assert(tp.Flen, Equals, 10)
        // 以下對(duì) LENGTH 函數(shù)的計(jì)算結(jié)果進(jìn)行測(cè)試
        d, err := f.Eval(nil)
        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)
            }
        }
    }
    // 以下測(cè)試函數(shù)是否是具有確定性
    f, err := funcs[ast.Length].getFunction([]Expression{Zero}, s.ctx)
    c.Assert(err, IsNil)
    c.Assert(f.isDeterministic(), IsTrue)
}
最后看 executor/executor_test.go,對(duì) LENGTH 的實(shí)現(xiàn)進(jìn)行 SQL 層面的測(cè)試:
// 關(guān)于 string built-in 函數(shù)的測(cè)試可以在這個(gè)方法中添加
func (s *testSuite) TestStringBuiltin(c *C) {
    defer func() {
        s.cleanEnv(c)
        testleak.AfterTest(c)()
    }()
    tk := testkit.NewTestKit(c, s.store)
    tk.MustExec("use test")
 
    // for length
    // 此處的測(cè)試最好也能覆蓋多種不同的情況
    tk.MustExec("drop table if exists t")
    tk.MustExec("create table t(a int, b double, c datetime, d time, e char(20), f bit(10))")
    tk.MustExec(`insert into t values(1, 1.1, "2017-01-01 12:01:01", "12:01:01", "abcdef", 0b10101)`)
    result := tk.MustQuery("select length(a), length(b), length(c), length(d), length(e), length(f), length(null) from t")
    result.Check(testkit.Rows("1 3 19 8 6 2 "))
}
重構(gòu)前的表達(dá)式計(jì)算框架

TiDB 通過(guò) Expression 接口(在 expression/expression.go 文件中定義)對(duì)表達(dá)式進(jìn)行抽象,并定義 eval 方法對(duì)表達(dá)式進(jìn)行計(jì)算:

type Expression interface{
    ...
    eval(row []types.Datum) (types.Datum, error)
    ...
}

實(shí)現(xiàn) Expression 接口的表達(dá)式包括:

Scalar Function:標(biāo)量函數(shù)表達(dá)式

Column:列表達(dá)式

Constant:常量表達(dá)式

下面以一個(gè)例子說(shuō)明重構(gòu)前的表達(dá)式計(jì)算框架。

例如:

create table t (
    c1 int,
    c2 varchar(20),
    c3 double
)
 
select * from t where c1 + CONCAT( c2, c3 < “1.1” )

對(duì)于上述 select 語(yǔ)句 where 條件中的表達(dá)式:
編譯階段,TiDB 將構(gòu)建出如下圖所示的表達(dá)式樹(shù):

執(zhí)行階段,調(diào)用根節(jié)點(diǎn)的 eval 方法,通過(guò)后續(xù)遍歷表達(dá)式樹(shù)對(duì)表達(dá)式進(jìn)行計(jì)算。

對(duì)于表達(dá)式 ‘<’,計(jì)算時(shí)需要考慮兩個(gè)參數(shù)的類(lèi)型,并根據(jù)一定的規(guī)則,將兩個(gè)參數(shù)的值轉(zhuǎn)化為所需的數(shù)據(jù)類(lèi)型后進(jìn)行計(jì)算。上圖表達(dá)式樹(shù)中的 ‘<’,其參數(shù)類(lèi)型分別為 double 和 varchar,根據(jù) MySQL 的計(jì)算規(guī)則,此時(shí)需要使用浮點(diǎn)類(lèi)型的計(jì)算規(guī)則對(duì)兩個(gè)參數(shù)進(jìn)行比較,因此需要將參數(shù) “1.1” 轉(zhuǎn)化為 double 類(lèi)型,而后再進(jìn)行計(jì)算。

同樣的,對(duì)于上圖表達(dá)式樹(shù)中的表達(dá)式 CONCAT,計(jì)算前需要將其參數(shù)分別轉(zhuǎn)化為 string 類(lèi)型;對(duì)于表達(dá)式 ‘+’,計(jì)算前需要將其參數(shù)分別轉(zhuǎn)化為 double 類(lèi)型。

因此,在重構(gòu)前的表達(dá)式計(jì)算框架中,對(duì)于參與運(yùn)算的每一組數(shù)據(jù),計(jì)算時(shí)都需要大量的判斷分支重復(fù)地對(duì)參數(shù)的數(shù)據(jù)類(lèi)型進(jìn)行判斷,若參數(shù)類(lèi)型不符合表達(dá)式的運(yùn)算規(guī)則,則需要將其轉(zhuǎn)換為對(duì)應(yīng)的數(shù)據(jù)類(lèi)型。

此外,由 Expression.eval() 方法定義可知,在運(yùn)算過(guò)程中,需要通過(guò) Datum 結(jié)構(gòu)不斷地對(duì)中間結(jié)果進(jìn)行包裝和解包,由此也會(huì)帶來(lái)一定的時(shí)間和空間開(kāi)銷(xiāo)。

為了解決這兩點(diǎn)問(wèn)題,我們對(duì)表達(dá)式計(jì)算框架進(jìn)行重構(gòu)。

重構(gòu)后的表達(dá)式計(jì)算框架

重構(gòu)后的表達(dá)式計(jì)算框架,一方面,在編譯階段利用已有的表達(dá)式類(lèi)型信息,生成參數(shù)類(lèi)型“符合運(yùn)算規(guī)則”的表達(dá)式,從而保證在運(yùn)算階段中無(wú)需再對(duì)類(lèi)型增加分支判斷;另一方面,運(yùn)算過(guò)程中只涉及原始類(lèi)型數(shù)據(jù),從而避免 Datum 帶來(lái)的時(shí)間和空間開(kāi)銷(xiāo)。

繼續(xù)以上文提到的查詢(xún)?yōu)槔?,?strong>編譯階段,生成的表達(dá)式樹(shù)如下圖所示,對(duì)于不符合函數(shù)參數(shù)類(lèi)型的表達(dá)式,為其加上一層 cast 函數(shù)進(jìn)行類(lèi)型轉(zhuǎn)換;

這樣,在執(zhí)行階段,對(duì)于每一個(gè) ScalarFunction,可以保證其所有的參數(shù)類(lèi)型一定是符合該表達(dá)式運(yùn)算規(guī)則的數(shù)據(jù)類(lèi)型,無(wú)需在執(zhí)行過(guò)程中再對(duì)參數(shù)類(lèi)型進(jìn)行檢查和轉(zhuǎn)換。

附錄

對(duì)于一個(gè) built-in 函數(shù),由于其參數(shù)個(gè)數(shù)、類(lèi)型以及返回值類(lèi)型的不同,可能會(huì)生成多個(gè)函數(shù)簽名分別用來(lái)處理不同的情況。對(duì)于大多數(shù) built-in 函數(shù),其每個(gè)參數(shù)類(lèi)型及返回值類(lèi)型均確定,此時(shí)只需要生成一個(gè)函數(shù)簽名。

對(duì)于較為復(fù)雜的返回值類(lèi)型推導(dǎo)規(guī)則,可以參考 CONCAT 函數(shù)的實(shí)現(xiàn)和測(cè)試。可以利用 MySQLWorkbench 工具運(yùn)行查詢(xún)語(yǔ)句 select funcName(arg0, arg1, ...) 觀(guān)察 MySQL 的 built-in 函數(shù)在傳入不同參數(shù)時(shí)的返回值數(shù)據(jù)類(lèi)型。

在 TiDB 表達(dá)式的運(yùn)算過(guò)程中,只涉及 6 種運(yùn)算類(lèi)型(目前正在實(shí)現(xiàn)對(duì) JSON 類(lèi)型的支持),分別是

int (int64)

real (float64)

decimal

string

Time

Duration

通過(guò) WrapWithCastAsXX() 方法可以將一個(gè)表達(dá)式轉(zhuǎn)換為對(duì)應(yīng)的類(lèi)型。

對(duì)于一個(gè)函數(shù)簽名,其返回值類(lèi)型已經(jīng)確定,所以定義時(shí)需要組合與該類(lèi)型對(duì)應(yīng)的 baseXXBuiltinFunc,并實(shí)現(xiàn) evalXX() 方法。(XX 不超過(guò)上述 6 種類(lèi)型的范圍)

---------------------------- 我是 AI 的分割線(xiàn) ----------------------------------------

回顧三月啟動(dòng)的《十分鐘成為 TiDB Contributor 系列 | 添加內(nèi)建函數(shù)》活動(dòng),在短短的時(shí)間內(nèi),我們收到了來(lái)自社區(qū)貢獻(xiàn)的超過(guò) 200 條新建內(nèi)建函數(shù),這之中有很多是來(lái)自大型互聯(lián)網(wǎng)公司的資深數(shù)據(jù)庫(kù)工程師,也不乏在學(xué)校或是剛畢業(yè)在刻苦鉆研分布式系統(tǒng)和分布式數(shù)據(jù)庫(kù)的學(xué)生。

TiDB Contributor Club 將大家聚集起來(lái),我們互相分享、討論,一起成長(zhǎng)。

感謝你的參與和貢獻(xiàn),在開(kāi)源的道路上我們將義無(wú)反顧地走下去,和你一起。

成為 New Contributor 贈(zèng)送限量版馬克杯的活動(dòng)還在繼續(xù)中,任何一個(gè)新加入集體的小伙伴都將收到我們充滿(mǎn)了誠(chéng)意的禮物,很榮幸能夠認(rèn)識(shí)你,也很高興能和你一起堅(jiān)定地走得更遠(yuǎn)。

成為 New Contributor 獲贈(zèng)限量版馬克杯,馬克杯獲取流程如下:

提交 PR

PR提交之后,請(qǐng)耐心等待維護(hù)者進(jìn)行 Review。
目前一般在一到兩個(gè)工作日內(nèi)都會(huì)進(jìn)行 Review,如果當(dāng)前的 PR 堆積數(shù)量較多可能回復(fù)會(huì)比較慢。代碼提交后 CI 會(huì)執(zhí)行我們內(nèi)部的測(cè)試,你需要保證所有的單元測(cè)試是可以通過(guò)的。期間可能有其它的提交會(huì)與當(dāng)前 PR 沖突,這時(shí)需要修復(fù)沖突。維護(hù)者在 Review 過(guò)程中可能會(huì)提出一些修改意見(jiàn)。修改完成之后如果 reviewer 認(rèn)為沒(méi)問(wèn)題了,你會(huì)收到 LGTM(looks good to me) 的回復(fù)。當(dāng)收到兩個(gè)及以上的 LGTM 后,該 PR 將會(huì)被合并。

合并 PR 后自動(dòng)成為 Contributor,會(huì)收到來(lái)自 PingCAP Team 的感謝郵件,請(qǐng)查收郵件并填寫(xiě)領(lǐng)取表單
表單填寫(xiě)地址:http://cn.mikecrm.com/01wE8tX

后臺(tái) AI 核查 GitHub ID 及資料信息,確認(rèn)無(wú)誤后隨即便快遞寄出屬于你的限量版馬克杯

期待你分享自己參與開(kāi)源項(xiàng)目的感想和經(jīng)驗(yàn),TiDB Contributor Club 將和你一起分享開(kāi)源的力量

了解更多關(guān)于 TiDB 的資料請(qǐng)登陸我們的官方網(wǎng)站:https://pingcap.com

加入 TiDB Contributor Club 請(qǐng)?zhí)砑游覀兊?AI 微信:

TiDB Robot 微信二維碼

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/17613.html

相關(guān)文章

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<