当前位置: 首页 > 编程日记 > 正文

十分钟成为 Contributor 系列 | 为 TiDB 重构 built-in 函数

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

这是十分钟成为 TiDB Contributor 系列的第二篇文章,让大家可以无门槛参与大型开源项目,感谢社区为 TiDB 带来的贡献,也希望参与 TiDB Community 能为你的生活带来更多有意义的时刻。

为了加速表达式计算速度,最近我们对表达式的计算框架进行了重构,这篇教程为大家分享如何利用新的计算框架为 TiDB 重写或新增 built-in 函数。对于部分背景知识请参考这篇文章,本文将首先介绍利用新的表达式计算框架重构 built-in 函数实现的流程,然后以一个函数作为示例进行详细说明,最后介绍重构前后表达式计算框架的区别。

重构 built-in 函数整体流程

  1. 在 TiDB 源码 expression 目录下选择任一感兴趣的函数,假设函数名为 XX

  2. 重写 XXFunctionClass.getFunction() 方法 该方法参照 MySQL 规则,根据 built-in 函数的参数类型推导函数的返回值类型 根据参数的个数、类型、以及函数的返回值类型生成不同的函数签名,关于函数签名的详细介绍见文末附录

  3. 实现该 built-in 函数对应的所有函数签名的 evalYY() 方法,此处 YY 表示该函数签名的返回值类型

  4. 添加测试: 在 expression 目录下,完善已有的 TestXX() 方法中关于该函数实现的测试 在 executor 目录下,添加 SQL 层面的测试

  5. 运行 make dev,确保所有的 test cast 都能跑过

示例

这里以重写 LENGTH() 函数的 PR 为例,进行详细说明

首先看 expression/builtin_string.go:

(1)实现 lengthFunctionClass.getFunction() 方法

该方法主要完成两方面工作:

  1. 参照 MySQL 规则推导 LEGNTH 的返回值类型
  2. 根据 LENGTH 函数的参数个数、类型及返回值类型生成函数签名。由于 LENGTH 的参数个数、类型及返回值类型只存在确定的一种情况,因此此处没有定义新的函数签名类型,而是修改已有的 builtinLengthSig,使其组合了 baseIntBuiltinFunc(表示该函数签名返回值类型为 int)
type builtinLengthSig struct {baseIntBuiltinFunc
}func (c *lengthFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {// 参照 MySQL 规则,对 LENGTH 函数返回值类型进行推导tp := types.NewFieldType(mysql.TypeLonglong)tp.Flen = 10types.SetBinChsClnFlag(tp)// 根据参数个数、类型及返回值类型生成对应的函数签名,注意此处与重构前不同,使用的是 newBaseBuiltinFuncWithTp 方法,而非 newBaseBuiltinFunc 方法// newBaseBuiltinFuncWithTp 的函数声明中,args 表示函数的参数,tp 表示函数的返回值类型,argsTp 表示该函数签名中所有参数对应的正确类型// 因为 LENGTH 的参数个数为1,参数类型为 string,返回值类型为 int,因此此处传入 tp 表示函数的返回值类型,传入 tpString 用来标识参数的正确类型。对于多个参数的函数,调用 newBaseBuiltinFuncWithTp 时,需要传入所有参数的正确类型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) 实现 builtinLengthSig.evalInt() 方法

func (b *builtinLengthSig) evalInt(row []types.Datum) (int64, bool, error) {// 对于函数签名 builtinLengthSig,其参数类型已确定为 string 类型,因此直接调用 b.args[0].EvalString() 方法计算参数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,对已有的 TestLength() 方法进行完善:

func (s *testEvaluatorSuite) TestLength(c *C) {defer testleak.AfterTest(c)() // 监测 goroutine 泄漏的工具,可以直接照搬// cases 的测试用例对 length 方法实现进行测试// 此处注意,除了正常 case 之外,最好能添加一些异常的 case,如输入值为 nil,或者是多种类型的参数cases := []struct {args     interface{}expected int64isNil    boolgetErr   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)// 以下对 LENGTH 函数的返回值类型进行测试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)// 以下对 LENGTH 函数的计算结果进行测试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)}}}// 以下测试函数是否是具有确定性f, err := funcs[ast.Length].getFunction([]Expression{Zero}, s.ctx)c.Assert(err, IsNil)c.Assert(f.isDeterministic(), IsTrue)
}

最后看 executor/executor_test.go,对 LENGTH 的实现进行 SQL 层面的测试:

// 关于 string built-in 函数的测试可以在这个方法中添加
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// 此处的测试最好也能覆盖多种不同的情况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 <nil>"))
}

重构前的表达式计算框架

TiDB 通过 Expression 接口(在 expression/expression.go 文件中定义)对表达式进行抽象,并定义 eval 方法对表达式进行计算:

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

实现 Expression 接口的表达式包括:

  • Scalar Function:标量函数表达式
  • Column:列表达式
  • Constant:常量表达式

下面以一个例子说明重构前的表达式计算框架。

例如:

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

对于上述 select 语句 where 条件中的表达式: 在编译阶段,TiDB 将构建出如下图所示的表达式树:

执行阶段,调用根节点的 eval 方法,通过后续遍历表达式树对表达式进行计算。

对于表达式 ‘<’,计算时需要考虑两个参数的类型,并根据一定的规则,将两个参数的值转化为所需的数据类型后进行计算。上图表达式树中的 ‘<’,其参数类型分别为 double 和 varchar,根据 MySQL 的计算规则,此时需要使用浮点类型的计算规则对两个参数进行比较,因此需要将参数 “1.1” 转化为 double 类型,而后再进行计算。

同样的,对于上图表达式树中的表达式 CONCAT,计算前需要将其参数分别转化为 string 类型;对于表达式 ‘+’,计算前需要将其参数分别转化为 double 类型。

因此,在重构前的表达式计算框架中,对于参与运算的每一组数据,计算时都需要大量的判断分支重复地对参数的数据类型进行判断,若参数类型不符合表达式的运算规则,则需要将其转换为对应的数据类型。

此外,由 Expression.eval() 方法定义可知,在运算过程中,需要通过 Datum 结构不断地对中间结果进行包装和解包,由此也会带来一定的时间和空间开销。

为了解决这两点问题,我们对表达式计算框架进行重构。

##重构后的表达式计算框架 重构后的表达式计算框架,一方面,在编译阶段利用已有的表达式类型信息,生成参数类型“符合运算规则”的表达式,从而保证在运算阶段中无需再对类型增加分支判断;另一方面,运算过程中只涉及原始类型数据,从而避免 Datum 带来的时间和空间开销。

继续以上文提到的查询为例,在编译阶段,生成的表达式树如下图所示,对于不符合函数参数类型的表达式,为其加上一层 cast 函数进行类型转换;

重构后表达式计算框架

这样,在执行阶段,对于每一个 ScalarFunction,可以保证其所有的参数类型一定是符合该表达式运算规则的数据类型,无需在执行过程中再对参数类型进行检查和转换。

附录

  • 对于一个 built-in 函数,由于其参数个数、类型以及返回值类型的不同,可能会生成多个函数签名分别用来处理不同的情况。对于大多数 built-in 函数,其每个参数类型及返回值类型均确定,此时只需要生成一个函数签名。
  • 对于较为复杂的返回值类型推导规则,可以参考 CONCAT 函数的实现和测试。可以利用 MySQLWorkbench 工具运行查询语句 select funcName(arg0, arg1, ...) 观察 MySQL 的 built-in 函数在传入不同参数时的返回值数据类型。
  • 在 TiDB 表达式的运算过程中,只涉及 6 种运算类型(目前正在实现对 JSON 类型的支持),分别是
  1. int (int64)
  2. real (float64)
  3. decimal
  4. string
  5. Time
  6. Duration 通过 WrapWithCastAsXX() 方法可以将一个表达式转换为对应的类型。
  • 对于一个函数签名,其返回值类型已经确定,所以定义时需要组合与该类型对应的 baseXXBuiltinFunc,并实现 evalXX() 方法。(XX 不超过上述 6 种类型的范围)

---------------------------- 我是 AI 的分割线 ----------------------------------------

回顾三月启动的《十分钟成为 TiDB Contributor 系列 | 添加內建函数》活动,在短短的时间内,我们收到了来自社区贡献的超过 200 条新建內建函数,这之中有很多是来自大型互联网公司的资深数据库工程师,也不乏在学校或是刚毕业在刻苦钻研分布式系统和分布式数据库的学生。

TiDB Contributor Club 将大家聚集起来,我们互相分享、讨论,一起成长。

感谢你的参与和贡献,在开源的道路上我们将义无反顾地走下去,和你一起。

成为 New Contributor 赠送限量版马克杯的活动还在继续中,任何一个新加入集体的小伙伴都将收到我们充满了诚意的礼物,很荣幸能够认识你,也很高兴能和你一起坚定地走得更远。

成为 New Contributor 获赠限量版马克杯,马克杯获取流程如下:

  1. 提交 PR

  2. PR提交之后,请耐心等待维护者进行 Review。 目前一般在一到两个工作日内都会进行 Review,如果当前的 PR 堆积数量较多可能回复会比较慢。 代码提交后 CI 会执行我们内部的测试,你需要保证所有的单元测试是可以通过的。期间可能有其它的提交会与当前 PR 冲突,这时需要修复冲突。 维护者在 Review 过程中可能会提出一些修改意见。修改完成之后如果 reviewer 认为没问题了,你会收到 LGTM(looks good to me) 的回复。当收到两个及以上的 LGTM 后,该 PR 将会被合并。

  3. 合并 PR 后自动成为 Contributor,会收到来自 PingCAP Team 的感谢邮件,请查收邮件并填写领取表单

    • 表单填写地址:http://cn.mikecrm.com/01wE8tX
  4. 后台 AI 核查 GitHub ID 及资料信息,确认无误后随即便快递寄出属于你的限量版马克杯

  5. 期待你分享自己参与开源项目的感想和经验,TiDB Contributor Club 将和你一起分享开源的力量

了解更多关于 TiDB 的资料请登陆我们的官方网站:https://pingcap.com

加入 TiDB Contributor Club 请添加我们的 AI 微信:

TiDB Robot 微信二维码 二维码.png-88.6kB

转载于:https://my.oschina.net/zhaiyuan/blog/997682

相关文章:

研究生要这样度过!(转)

研究生要这样度过&#xff01; 首先要知道研究生期间做什么&#xff1f;我认为研究生期间学生应该学三件事情&#xff1a; 1&#xff09;建立合理的知识结构&#xff1a;尽量广地涉猎学科基本知识&#xff0c;尽量深地了解所研究领域的 方方面面、过去和现在 2&#xff09;掌握…

后端开发面试自我介绍_字节跳动暑期实习后端开发面试经历

字节跳动后端实习是什么&#xff0c;字节跳动后端实习面试流程是怎样&#xff1f;今天小编就来帮助大家了解一下字节跳动后端实习面试到底有什么内容。&#xff08;好了不皮了&#xff0c;开始正文&#xff09;字节的面试流程总的来说还是挺享受的&#xff0c;和面试官两人的思…

《C#精彩实例教程》小组阅读09 -- C#数组与集合

本微信图文详细介绍了C#的数组与集合。

Oracle执行计划突变诊断之统计信息收集问题

Oracle执行计划突变诊断之统计信息收集问题1. 情形描述DB version&#xff1a;11.2.0.4WITH SQL1 AS(SELECT LAC,CI,TO_NUMBER(C.LONGITUDE) LONGITUDE,TO_NUMBER(C.LATITUDE) LATITUDEFROM MB_SYS_CELL_INFO CWHERE C.CONTY_NAME 道孚县), SQL2 AS(SELECT DISTINCT IMSI, LA…

安装gym库_强化学习Gym库学习实践(一)

最近看了一篇研究方向相关的文章&#xff0c;介绍了一种DQN的应用&#xff0c;感觉还挺新鲜的。想着把这篇文章复现出来&#xff0c;就开始学习强化学习的相关知识&#xff0c;作为一名小白&#xff0c;这一路走的可是真的十分艰难&#xff08;我太菜了啊&#xff01;&#xff…

VS2005编译QT4.8.2

为什么要编译&#xff1f; 因为安装安装版的QT4.8.2&#xff0c;vs2005编译报错。 1.下载QT4.8.2&#xff0c;qt-everywhere-opensource-src-4.8.2.zip&#xff0c;下载vs-AddIn1.1.11. 2.解压QT源码包到C盘&#xff0c; 这里路径为 c:\qt\4.8.2\。 3.配置系统环境变量&#xf…

Don’t Use the Win32 API PostThreadMessage() to Post Messages to UI Threads(翻译)

大龙的博客C博客 | 首页 | 发新随笔 | 发新文章 | 联系 | 聚合 | 管理 Don’t Use the Win32 API PostThreadMessage() to Post Messages to UI Threads(翻译) Don’t Use the Win32 API PostThreadMessage() to Post Messages to UI Threads不要用Win32 API PostThreadMessage…

Matlab编程与数据类型 -- 文本M文件

本微信图文详细介绍了Matlab中的文本M文件。

安卓x86_Android:虚拟机体验基于安卓10的BlissOS V12.2 Android X86版

我是科技鲁工&#xff0c;今天带来基于Android10的x86版本的Bliss os的安装体验。喜欢的朋友可以关注支持一下。Bliss OS是一个基于Android x86项目的开源操作系统&#xff0c;能让您在PC电脑或平板电脑设备上运行最新的Android 10操作系统。该系统基于AOSP(Android开放源代码项…

《C#精彩实例教程》小组阅读10 -- C#属性与方法

本微信图文详细介绍了C#的属性与方法。

JavaScript中几个重要的知识点(1) ---- 面向对象

JavaScript中几个最重要的大知识点 面向对象DOM事件异步交互ajax面向对象 在JS中可以把任意的引用和变量都看成是一个对象。面向对象的主要三个表现形式&#xff1a; 封装继承多态1. 封装 1.1 单例模式 var obj{name: "sam",age: 12,method: function(){var objNamet…

scrollLeft,scrollWidth,clientWidth,offsetWidth到底指的哪到哪的距离

轉自:http://www.cnblogs.com/mrhgw/archive/2006/11/08/553737.html 补充&#xff1a; scrollHeight: 获取对象的滚动高度。 scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop:设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的…

连接服务器_命令行连接FTP服务器

Windows下: 打开命令行窗口,输入 ftp,进入ftp命令模式: 输入 open ip地址 端口,进入ftp服务器,如open 172.16.3.77 2121。如下图: 输入Windows下的用户名,然后输入密码(注意:密码是不显示的,输入用户名密码不能时间太长,否则连接断掉)。 查看ftp服务器中的文件,输…

mapreduce作业reduce被大量kill掉

之前有一段时间。我们的hadoop2.4集群压力非常大。导致提交的job出现大量的reduce被kill掉。同样的job执行时间比在hadoop0.20.203上面长了非常多。这个问题事实上是reduce 任务启动时机的问题&#xff0c;因为yarn中没有map slot和reduce slot的概念&#xff0c;且ResourceMan…

Matlab编程与数据类型 -- M文件的编辑和存储

本微信图文详细介绍了Matlab中M文件的编辑和存储。

一个老工程师对理工科学生的忠告

[1]好好规划自己的路&#xff0c;不要跟着感觉走&#xff01;根据个人的理想决策安排&#xff0c;绝大部分人并不指望成为什么院士或教授&#xff0c;而是希望活得滋润一些&#xff0c;爽一些。那么&#xff0c;就需要慎重安排自己的轨迹。从哪个行业入手&#xff0c;逐渐对该行…

64 安装_解决“不能安装 64 位Office,因已安装 32 位 Office 产品”问题

换了个电脑装64位的Office 2010(Office_64)&#xff0c;双击setup刚准备装&#xff0c;就收到以下错误提示&#xff1a;“不能安装 64 位版本的 Office 2010&#xff0c;因为您当前已经安装 32 位 Office 产品。64 位安装不支持 32 位产品安装&#xff1a;如果要安装 64 位 Off…

Python---内置函数

一、数值类操作 abs(x)求绝对值 1、参数可以是整型&#xff0c;也可以是复数 2、若参数是复数&#xff0c;则返回复数的模complex([real[, imag]])创建一个复数divmod(a, b)分别取商和余数 注意&#xff1a;整型、浮点型都可以float([x])将一个字符串或数转换为浮点数。如果无参…

《C#精彩实例教程》小组阅读11 -- C#结构与类

本微信图文详细介绍了C#的结构与类。

软件测试工程师职业介绍和规划

如存在没有任何错误的程序&#xff0c;那么世界也会不复存在。” 因错误而存在&#xff0c;因修正错误而存在&#xff0c;这就是软件测试工程师的存在之道。虽然测试不是解决错误的根本举措&#xff0c;但却是必须的手段。 软件测试工程师&#xff08;Software Testing Engine…

python selenium脚本_怎样开始写第一个基于python的selenium脚本

1、下载并安装python(http://www.python.org/geti/)。2、安装selenium(http://pypi.python.org/pypi/selenium)下载并解压缩selenium-2.32.0.tar.gz. 把selenium-2.32.0\py\下的selenium整个文件夹放入Python33\Lib\site-packages目录下。3 下载Eclipse后安装pydev插件4 打…

《C#精彩实例教程》小组阅读12 -- C#面向对象技术高级应用

本微信图文详细介绍了C#面向对象技术高级应用。

linux php --ini

$ php --ini

文档类型定义DTD

XML系列&#xff1a;文档类型定义DTD (转) 一&#xff0c;什么是DTD&#xff1f;1&#xff0c;XMl是一种元标记语言&#xff0c;是描叙语言的语言&#xff0c;定义标记的语法结构&#xff0c;从而生成新标记。而DTD则是为新标记建立文档并进行规范说明。也就是说XML定义标记的语…

gis中的擦除_擦除—帮助 | ArcGIS for Desktop

输出 coverage 已不存在。如果注记的左下起点位于擦除多边形内部&#xff0c;则擦除该注记。新结点的属性将设置为零。移除与擦除 coverage 多边形重叠的输入 coverage 多边形。擦除 coverage 必须具有面拓扑。输出 coverage 中所有要素的 User-ID 与输入 coverage 中所有要素的…

C# 3.0/3.5语法新特性示例汇总[转]

//作者:杨卫国//时间:2008年2月21日//说明:C#语法新特型示例usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Windows.Forms;namespaceC3{ class Program { //新特性1:简单属性,无需另外多写一个私有字段,比较适合于…

Matlab编程与数据类型 -- 循环控制语句之二:while/end

本微信图文详细介绍了Matlab中while/end循环控制语句。

Centos6,7默认启动模式的更改

有时候我们想以为图形页面启动&#xff0c;启动后直接到图形页面&#xff0c;但是有时候也想启动后直接进入文本页面&#xff0c;我们只需要修改init即可&#xff1b; Centos6启动模式配置文件路径&#xff1a;/etc/inittab Centos7启动模式配置文件路径&#xff1a;/usr/lib/s…

python中计数_Python中的统计计数

关闭。此问题不符合堆栈溢出准则。它当前不接受答案。想改善这个问题吗&#xff1f;更新问题&#xff0c;使其成为Stack Overflow的主题。6年前关闭。我有一组五个字母A..E。我想将它们分成3个一组&#xff0c;不重复字母&#xff0c;但是对于从哪里开始我一无所知。解决方案假…

angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )

Promise 是 ES 6 Async/Await 是 ES 7 Rxjs 是一个 js 库 在使用 angular 时&#xff0c;你会经常看见这 3 个东西. 它们都和异步编程有关&#xff0c;有些情况下你会觉得用它们其中任何一个效果都一样. 但又觉得好像哪里不太对.... 这篇就来说说&#xff0c;我在开发时的应用方…