HBTlang-Mycat2.0-2020-3-18

137 阅读14分钟

HBTlang-Mycat2.0

author:chenjunwen 2020-3-16

Creative Commons License This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

HBT(Human Brain Tech)简介

HBT在Mycat2中表现为关系表达式领域驱动语言(Relation DSL).

在设计上是Mycat2的运行时的中间语言,关于查询的HBT可以实现与SQL,其他查询优化器,查询编译器的关系表达式相互甚至与SQL DSL框架中的DSL转换.HBT也支持直接从文本和编写代码的方式构建.

 

使用HBT解决什么问题?

1.允许用户直接编写关系表达式实现功能,不同的SQL方言可以对应同一套关系表达式

2.运行用户运行自定义函数

3.免去优化过程,用户编写的关系表达式可以就是执行计划

4.允许使用函数宏展开关系表达式,比如给逻辑表函数宏指定分片范围自动扩展成对应的物理表

5.允许SQL与关系表达式一并编写,例如叶子是SQL,根是Mycat的运算节点

6.可移植到其他平台运行

7.使用DSL作为中间语言下发到其他Mycat节点上运行

8.方便验证测试

 

基本HBT表达式

 

fromTable

获得逻辑表的数据源

name类型参数数量参数
fromTablerel2逻辑库的标识符,逻辑表的标识符

 

text

fromTable(db1,travelrecord)

 

java

//io.mycat.hbt.ast.HBTBuiltinHelper#fromTable(java.lang.String, java.lang.String)
fromTable("db1", "travelrecord")

 

table

匿名表,一种字面量构成的数据源

名称类型参数数量参数
tablerel至少两个字段信息列表,字面量列表(一维列表)

 

 

text

table(fields(fieldType(`1`,`integer`),fieldType(`2`,`varchar`)),values())
table(fields(fieldType(id,int)),values(1,2,3))

 

fieldType

列类型

列名

列类型

可空

精度

小数位数

三种构造方式

fieldType(columnName,columnType)
fieldType(columnName,columnType,columnNullable)
fieldType(columnName,columnType,columnNullable,precision,scale)
    
即
fieldType(String columnName, String columnType)
\\columnNullable = true ,precision = null,scale = null
​
fieldType(String columnName, String columnType, boolean columnNullable)
\\precision = null,scale = null
​
fieldType(String columnName, String columnType, boolean columnNullable,Integer precision, Integer scale)

 

字段类型(不区分大小写)

名称precision(精度) - scale(小数位数)java类型
Boolno-noboolean
Tinyintno-nobyte
Smallintno-noshort
Integerno-noint
Bigintno-nolong
Decimalno-no,yes-noBigDecimal
Floatno-nofloat
Realno-nofloat
Doubleno-nodouble
Dateno-noDate ‘xxxx-xx-xx’
Timeno-no,yes-noTime ‘xx:xx:xx’
TimeWithLocalTimeZoneno-no,yes-noTimestamp ‘xxxx-xx-xxxx:xx:xx’
Timestampno-no,yes-noTimestamp ‘xxxx-xx-xxxx:xx:xx’
TimestampWithLocalTimeZoneno-no,yes-noTimestamp ‘xxxx-xx-xxxx:xx:xx’
Charno-noString
Varcharno-noString
Binaryno-nobyte[]
Varbinaryno-nobyte[]

 

语法辅助

fields

列类型列表

多个fieldType组成

values

多个字面值组成

解析器会根据fieldType的数量自动把数列转成二维表

 

java

//io.mycat.hbt.ast.HBTBuiltinHelper#table(java.util.List<FieldType>, java.util.List<Object>)
​
​
table(Arrays.asList(fieldType("1", "integer"), fieldType("2", "varchar")), Arrays.asList())
//()

 

table(Arrays.asList(fieldType("id", "integer")), Arrays.asList(1,2,3))
//(1),(2),(3)

 

table(Arrays.asList(fieldType("id", "integer"),fieldType("id2", "integer")), Arrays.asList(1,2,3,4))
//(1,2),(3,4)

 

map

名称类型参数数量参数
maprel至少两个数据源,至少一个表达式

投影

 

text

table(fields(fieldType(id,integer),fieldType(id2,integer)),values(1,2))
.map(id2 as id4)//sugar
​
map(table(fields(fieldType(id,integer),fieldType(id2,integer)),values(1,2)),id2 as id4)
​

 

text

table(fields(fieldType(id,integer),fieldType(id2,integer))
.map(id + id2,id)//sugar
      
map(table(fields(fieldType(id,integer),fieldType(id2,integer)),values(1,2)),id + id2,id)

 

java

//io.mycat.hbt.ast.HBTBuiltinHelper#map(Schema,List<Expr>)

map(table(Arrays.asList(fieldType("id", "integer"), fieldType("id2", "integer")), Arrays.asList()),Arrays.asList(as(new Identifier("id2"), new Identifier("id4"))))

map(table(Arrays.asList(fieldType("id", "integer"), fieldType("id2", "integer")), Arrays.asList()),
    Arrays.asList(add(new Identifier("id2"), new Identifier("id4")),newIdentifier("id")))

 

rename

名称类型参数数量参数
renamerel1+数据源的列数量数据源,多个字段标识符(数据源的列数量)

map表达式的简化,对数据源全列名进行更名,该表达式是一个补充的表达式

 

text

table(fields(fieldType(`1`,`integer`,true),fieldType(`2`,`integer`,true)),values(1,2)).rename(`2`,`1`)

 

java

//io.mycat.hbt.ast.HBTBaseTest#rename(Schema, List<String>)

rename(table(Arrays.asList(fieldType("1", "integer",true), fieldType("2", "integer",true)), Arrays.asList(1,2)), Arrays.asList("2", "1"))
      

 

filter

选择,根据布尔表达式对行进行过滤

名称类型参数数量参数
filterrel2数据源,一个布尔表达式

 

text

fromTable(db1,travelrecord).filter(`id` = 1)

filter(fromTable(db1,travelrecord),`id` = 1)

 

java

//io.mycat.hbt.ast.HBTBuiltinHelper#filter(Schema,Expr)
filter(fromTable("db1", "travelrecord"), eq(new Identifier("id"), new Literal(1)))

 

Set

集合操作

名称类型参数数量参数
集合操作rel至少2个大于等于两个数据源

 

unionAll
并
unionDistinct
并,去重
exceptAll
除
exceptDistinct
除,去重
minusAll
减
minusDistinct
减,去重
intersectAll
交
intersectDistinct
交,去重

text

fromTable(db1,travelrecord) unionAll  fromTable(db1,travelrecord)

unionAll(fromTable(db1,travelrecord),fromTable(db1,travelrecord))

 

java

//io.mycat.hbt.ast.HBTOp
//io.mycat.hbt.ast.HBTOp#UNION_ALL
//io.mycat.hbt.ast.HBTOp#UNION_DISTINCT
//io.mycat.hbt.ast.HBTOp#EXCEPT_ALL
//io.mycat.hbt.ast.HBTOp#EXCEPT_DISTINCT
//io.mycat.hbt.ast.HBTOp#MINUS_ALL
//io.mycat.hbt.ast.HBTOp#MINUS_DISTINCT
//io.mycat.hbt.ast.HBTOp#INTERSECT_ALL
//io.mycat.hbt.ast.HBTOp#INTERSECT_DISTINCT
//io.mycat.hbt.ast.HBTBuiltinHelper#set(io.mycat.hbt.HBTOp, java.util.List<io.mycat.hbt.ast.base.Schema>)
set(HBTOp.UNION_ALL, Arrays.asList(fromTable("db1", "travelrecord"), fromTable("db1", "travelrecord")))

 

distinct

名称类型参数数量参数
去重rel一个一个数据源

 

去重,该表达式是补充的表达式

 

text

fromTable(db1,travelrecord).distinct()

distinct(fromTable(db1,travelrecord))

 

java

io.mycat.hbt.ast.HBTBuiltinHelper#distinct(Schema)
distinct(fromTable("db1", "travelrecord"))

 

GroupBy

分组

名称类型参数数量参数
分组rel2分组键,聚合函数

 

text

fromTable(db1,travelrecord).groupBy(keys(groupKey(`id`)),aggregating(avg(`id`))))

groupBy(fromTable(db1,travelrecord),keys(groupKey(`id`)),aggregating(avg(`id`)))

 

分组键

groupKey(`id`)
groupKey(`id`,`id2`)

由一个或者多个以数据源列名组成

 

聚合函数

基本构造参数

参数描述
函数名必须填写
参数列表不同函数不同

 

avg

名称类型参数数量参数
avgagg expr1列名标识符

扩展型聚合函数,会被优化器转换成sum()/count()

输入值要求是数字类型

 

count

名称类型参数数量参数
countagg expr任意多个列名标识符或无

当有多个列名标识符作为参数的时候,,返回其值不为null 的输入行数

无参的时候返回输入的行数

 

max

名称类型参数数量参数
maxexpr1列名标识符

输入值要求是数字类型 返回所有输入值中的最大值

 

min

名称类型参数数量参数
minagg expr1列名标识符

 

输入值要求是数字类型

返回所有输入值中的最小值

 

sum

名称类型参数数量参数
sumagg expr1列名标识符

输入值要求是数字类型

返回所有输入值的数值总和

 

 

语法辅助

keys

由一个或者多个分组键组成

一般只有一个groupKey

多个groupKey组成GROUPING SETS

keys(groupKey(`id`),groupKey(`id2`))

 

aggregating

由一个或者多个聚合函数组成

 

java

groupBy(fromTable("db1", "travelrecord"), Arrays.asList(groupKey(Arrays.asList(id(("id"))))),
                Arrays.asList(new AggregateCall("avg", Arrays.asList(id(("id")))).alias("a").distinct().approximate().ignoreNulls().filter(literal(true)).orderBy(Arrays.asList(order("user_id", Direction.DESC)))))

 

Join

Join操作

innerJoin

inner join

内连接

在表中存在至少一个匹配时,返回行。

 

leftJoin

left outer Join

左连接

从左表返回所有的行,即使在右表中没有匹配的行。

 

rightJoin

right outer Join

右连接

从右表返回所有的行,即使在左表中没有匹配的行

 

fullJoin

full outer Join

全连接

只要其中某个表存在匹配,就会返回行

 

semiJoin

left semi join

半连接,左半连接

当条件成立时,返回左表的行

 

antiJoin

anti semi join

反连接,反半连接

当条件不成立时,返回左表的行

 

文本

名称类型参数数量参数
innerJoin,leftJoin,rightJoin,semiJoin,antiJoinrel三个条件:布尔表达式,左数据源,右数据源

 

 

//innerJoin
//leftJoin
//rightJoin
//semiJoin
//antiJoin
innerJoin(`id0` = `id`,fromTable(db1,travelrecord)
          .map(`id` as `id0`),fromTable(db1,travelrecord))

 

java

new JoinSchema(HBTOp.INNER_JOIN,
                eq(id("id0"), id("id"))
                , map(fromTable("db1", "travelrecord"), Arrays.asList(as(new Identifier("id"), new Identifier("id0")))),
                fromTable("db1", "travelrecord")

 

 

列名冲突

两个数据源中字段名相同的情况下,使用as表达式建立别名后,才可以在条件里正确使用

 

行类型变更

join操作后,行类型是左数据源+右数据源的列,

比如上述的

fromTable(db1,travelrecord).map(id as id0),fromTable(db1,travelrecord)

行类型分别是(id0),(id,user_id)

innerJoin操作后变为(id0,id,user_id)

 

OrderBy

对数据源排序

名称类型参数数量参数
OrderByrel1+order数量数据源,多个order

 

文本

fromTable(db1,travelrecord).orderBy(order(id,ASC), order(user_id,DESC))

orderBy(fromTable(db1,travelrecord),order(id,ASC), order(user_id,DESC))

 

java

orderBy(fromTable("db1", "travelrecord"), Arrays.asList(order("id", Direction.ASC), order("user_id", Direction.DESC)))

 

语法辅助

order(列名,asc或者desc,不区分大小写)

 

limit

要数据源返回的行的一部分

名称类型参数数量参数
limitrel1+多个order数量数据源,offset,limit

 

文本

fromTable(db1,travelrecord).limit(1,2)
limit(fromTable(db1,travelrecord),1,2)

 

java

limit(fromTable("db1", "travelrecord"), 1, 2)

 

HBT-扩展表达式

fromSql

sql作为数据源

名称类型参数数量参数
fromSqlrel2或3见下面

三个参数

 

1.列类型信息

2.查询目标名字

3.任意sql,返回的结果集的类型必须与给出的列类型一致

fromSql(fields(fieldType(`id`,`integer`),fieldType(`user_id`,`integer`),targetName,'select * from db1.travelrecord')

 

两个参数

2.查询目标名字

3.sql,自动推导列信息,暂时,sql只能配置在元数据配置的dataNode中的表名

fromSql(targetName,'select * from db1.travelrecord')

 

filterFromTable

1.强调可下推的谓词

2.避免逻辑表与物理表耦合

名称类型参数数量参数
filterFromTablerel3布尔表达式,逻辑库标识符,逻辑表标识符

 

等价

filterFromTable(`id` = 1,db1,travelrecord)//强调可下推
<=>
fromTable(`id` = 1,db1,travelrecord).filter(`id` = 1)//不强调下推

 

下推

filterFromTable(`id` = 1,db1,travelrecord)//经过上下文分析生成查询的目标和查询的SQL
=>
fromSql('targetName','select * from db1.travelrecord where `id` = 1')//可能的SQL

 

文本

filterFromTable(`id` = 1,db1,travelrecord)
filterFromTable(`id` = 1 or `id` = 2 ...,db1,travelrecord)
filterFromTable(1<= `id` and `id` <= 10000,db1,travelrecord)

 

布尔表达式必须是简单的形式,不支持任何计算,别名

 

fromRelToSql

转换HBT到SQL

极少数转换可能会失败,即使转换成功,SQL未必是数据库支持的,转换目标方言默认是mysql

名称类型参数数量参数
fromRelToSqlrel2 

 

文本

fromRelToSql(targetName,fromTable('db1','travelrecord').filter(`id` = 1).map(`id`))
=>等价转换
fromSql(targetName,'select db1.travelrecord.id from db1.travelrecord where `id` = 1')

 

modifyFromSql

向目标执行返回值不是结果集的SQL

名称类型参数数量参数
modifyFromSqlupdate2目标标识符,sql

执行结果

updateCount

lastInsertId

 

文本

modifyFromSql(targetName,'delete db1.travelrecord1')

 

mergeModify

合拼多个modifyFromSql结果

updateCount 求和

lastInsertId 取mergeModify中最大值

 

名称类型参数数量参数
mergeModifyupdate一个或者多个modifyFromSqlmodifyFromSql

 

文本

mergeModify(
    modifyFromSql(targetName,'delete db1.travelrecord1'),
    modifyFromSql(targetName2,'delete db2.travelrecord1')
    )

 

HBT-命令表达式

explain

1.显示数据源的列类型,辅助编写hbt

2.显示执行计划

 

文本

explain filterFromTable(`id` = 1,db1,travelrecord)

 

HBT-标量表达式(运算符)

计算结果是一个特定数据类型的标量值的表达式

 

运算符函数名描述算符优先级参数数量布尔表达式
eq=等于102y
ne<>,!=不等于102y
gt大于112y
lt<小于112y
gte>=大于等于112y
lte<=小于等于112y
add+算术加法132n
minus-算术减法132n
and逻辑与62y
or逻辑或52y
not逻辑非1y
as别名32n
cast类型转换2n
ref关联连接数据源引用2n

上述描述的运算符都是左结合,其他情况使用函数调用

当运算符参数数量为2并且具有优先级的时候可以使用中缀表达式编写文本,解析器自动完成转换为函数调用并解糖

 

1+2 => add(1,2)

 

需要用到其他函数,请提PR

 

支持中缀表达式编写文本的关系表达式,同理,也可以被解析器完成转换,

fromTable(db1,travelrecord) unionAll  fromTable(db1,travelrecord) 
=>
unionAll(fromTable(db1,travelrecord),fromTable(db1,travelrecord))

 

算符优先级都是1,左结合

unionAll,
unionDistinct,
exceptDistinct,
exceptAll,
minusAll,
minusDistinct,
rename,
groupBy
distinct,
orderBy
alias//仅用于聚合函数 ,
distinct//用于关系表达式,聚合函数 ,
approximate//仅用于聚合函数 ,
ignoreNulls//仅用于聚合函数 ,
filter//用于关系表达式,聚合函数 ,

 

类型转换与别名

fromTable(db1,travelrecord).map(cast(id,float) as a)

 

名称类型参数数量参数
castexpr2表达式,类型标识符(不接受待求值的表达式)

 

名称类型参数数量参数
asexpr2表达式,标识符(不接受待求值的表达式)

 

内置常用函数

名称类型参数数量参数
lowerexpr1表达式(字符串类型)

把参数转成小写

名称类型参数数量参数
upperexpr1表达式(字符串类型)

把参数转成大写

名称类型参数数量参数
isnullexpr1表达式

表达式的结果是否null

名称类型参数数量参数
nullifexpr2表达式1,表达式2

如果两表达式的结果相同,则返回null

名称类型参数数量参数
isnotnullexpr1表达式

表达式的结果是否非null

 

 

HBT-列名引用

代数关系表达式中的表达式引用列名的方法以及存在的规则

1.在HBT里面,一般来说,一个数据源上的列名是唯一的,不能存在相同的列名,不能同时以$开头和以数字结尾

2.列表使用``包裹强调是标识符

fromTable(db1,travelrecord).filter(`id` = 1)

3.在涉及两个数据源的情况下,需要遇上列名相同的情况,as表达式或者rename关系表达式把其中一个数据源的列名改变,一般风格是添上0为结尾

innerJoin(`id0` = `id`,fromTable(db1,travelrecord)
          .map(`id` as `id0`),fromTable(db1,travelrecord))

4.支持下标引用列名

以$[0-9]格式,以下标引用第一个数据源的列名

fromTable(db1,travelrecord).filter(`$0` = 1)//引用位置0的列表,即id

在join关系表达式下,以$$[0-9]格式,以下标引用第二个数据源的列名

innerJoin(`$0` = `$$0`,fromTable(db1,travelrecord),fromTable(db1,travelrecord))

$0引用第一个数据源的id

$1引用第一个数据源的user_id

$$0引用第二个数据源的id

$$1引用第二个数据源的user_id

 

编译器会根据上下文判断列名是哪个数据源哪个下标,最终转换成下标表示,并解决列名冲突,在相同列名上添加0.

 

 

HBT-词法

HBT词法分析就是MySQL的词法

编码为UTF8

不支持字符串里嵌入二进制数据,使用X'4D2AFF'这种格式描述二进制数据

这里不再重复描述MySQL词法

 

词法可以区分字面量和标识符,其中字面量一般就是值

 

 

HBT-语法

基本形式

语法的设计选取了函数调用语法

function(arg1,arg2)

作为基本形式,任何语法糖都会语法分析器编译成该形式

 

语法糖

 

中缀表达式语法糖

在基本形式的基础上使用中缀表达式简化标量表达式的编写,尤其是and or表达式

`id` = 1 and `user_id` = 2 => and(eq(`id`,1),eq(`user_id`,2))

 

函数别名语法糖

为解决eq,lt等函数名并不接近sql常用的运算符= >的问题

语法分析器在判断它们是函数之后,会把它们的函数名指向最终的函数名

 

链式调用语法糖

在基本形式的基础上使用链式调用语法减少深层括号嵌套的影响

fromSql(aaaa).filter(bbbb) => filter(fromSql(aaaa),bbbb)

 

语法辅助(函数)

用于分隔语法节点层次

 

函数的参数

HBT语法上使用函数调用方式,但是实际上是使用文本转换成语法树表达builder模式

参数的实际含义取决于所在的函数名,参数位置

例如filter的第二个参数本质上是接收语法树而非表达式的语法树的求值结果

 

HBT类型转换

HBT无隐式的类型转换,一般使用cast显式编写

对于整型数值的算术运算需要保持精度的情况,比如avg运算,请在avg的参数表达式上先转换成float等浮点类型

 

高级用法(少数情况出现,一般不使用)

额外类型

以下类型未正式支持

名称precision(精度) - scale(小数点后的位数)java类型
IntervalYearno-no 
IntervalYearMonthno-no 
IntervalMonthno-no 
IntervalDayno-no,yes-no 
IntervalDayHourno-no,yes-no 
IntervalDayMinuteno-no,yes-no 
IntervalDaySecondno-no,yes-no 
IntervalHourno-no,yes-no 
IntervalHourMinuteno-no,yes-no 
IntervalMinuteno-no,yes-no 
IntervalMinuteSecondno-no,yes-no 
IntervalSecondno-no,yes-no 

 

聚合函数

 

额外构建参数,需要规则甚至执行器配合,不推荐使用,不在HBT的目标之内

参数描述
distinct如果distinct存在, 则聚合前进行distinct运算过滤,默认不开启
approximate不同函数不同,默认不开启,用于特殊函数
ignoreNulls如果ignoreNulls存在,则聚合前使用布尔表达式过滤,优化器会用自动添加Order表达式,默认不开启
filter如果filter存在,则聚合前使用布尔表达式过滤,优化器会用自动添加Order表达式,默认不开启
orderKeys如果orderKeys存在,则聚合前先排序,优化器会用自动添加filter表达式,默认不开启
alias为聚合函数对应的列设置别名,默认不开启

 

完整构建语法样例

text

fromTable(db1,travelrecord)
.groupBy(keys(groupKey(`columnName`)),aggregating(avg(`columnName`).alias(a).distinct().approximate().ignoreNulls().filter(true).orderBy(order(user_id,DESC))))

 

java

groupBy(fromTable("db1", "travelrecord"), Arrays.asList(groupKey(Arrays.asList(id(("columnName"))))),
                Arrays.asList(new AggregateCall("avg", Arrays.asList(id(("columnName")))).alias("a").distinct().approximate().ignoreNulls().filter(literal(true))
                        .orderBy(Arrays.asList(order("user_id", Direction.DESC)))))

 

关联连接

对应关联子查询

循环获取左表的行,使用该行的值作为查询用到的参数重新获取右表数据源,之后进行内连接或者左连接

具体执行一般实现为NestedLoops

correlateInnerJoin

关联内连接

 

correlateLeftJoin

关联左连接

 

构建步骤

1.组合两个数据源而成为新的数据源,并为一个数据源建立别名供第二个数据源引用

correlateInnerJoin(别名,数据源1 ,数据源2)
correlateLeftJoin(别名,数据源1 ,数据源2)

2.第二个数据源引用别名

ref(别名,字段)

实际上别名就是数据源1

3.综上

text

correlateInnerJoin(`t`,
                   table(fields(fieldType(id0,integer,false)),values(1,2,3,4)) , fromTable(`db1`,`travelrecord`).filter(ref(`t`,`id0`) = `id`)))

 

java

  Schema db0 = table(Arrays.asList(fieldType("id0", "Integer",false)), Arrays.asList(1, 2, 3, 4));
        Schema db1 = filter(fromTable("db1", "travelrecord"), eq(ref("t", "id0"), new Identifier("id")));
        Schema schema = correlate(CORRELATE_INNER_JOIN, "t", db0, db1);

 

文档更改记录

2020.3.16-2020-3-17 完成文档编写 (chenjunwen)