HBTlang-Mycat2.0
author:chenjunwen 2020-3-16
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
样例
/*+ mycat:run{
"hbt":"innerJoin(`id0` = `id`,fromTable(db1,travelrecord).map(`id` as `id0`),fromTable(db1,company))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"table(fields(fieldType(id,integer)),values(1,2,3))"
} */;
/*+ mycat:run{
"hbt":"fromSql('prototype','select 1')"
} */;
/*+ mycat:run{
"hbt":"table(fields(fieldType(id,integer),fieldType(id2,integer)),values(1,2)).map(id + id2,id)"
} */;
/*+ mycat:run{
"hbt":"table(fields(fieldType(`1`,`integer`,true),fieldType(`2`,`integer`,true)),values(1,2)).rename(`2`,`1`)"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).filter(`id` = 1)"
} */;
/*+ mycat:run{
"hbt":"unionAll(fromSql('prototype','select 1'),fromSql('prototype','select 2'))"
} */;
/*+ mycat:run{
"hbt":"unionAll(fromSql('prototype','select 1'),fromSql('prototype','select 1')).distinct()"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).groupBy(keys(groupKey(`id`)),aggregating(avg(`id`))))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).groupBy(keys(groupKey(`id`,`user_id`)),aggregating(avg(`id`))))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).groupBy(keys(groupKey()),aggregating(sum(`id`)))"
} */;
/*+ mycat:run{
"hbt":"unionDistinct(fromSql('prototype','select 1'),fromSql('prototype','select 1'))"
} */;
/*+ mycat:run{
"hbt":"exceptDistinct(fromSql('prototype','select 1'),fromSql('prototype','select 1'))"
} */;
/*+ mycat:run{
"hbt":"exceptAll(fromSql('prototype','select 2'),fromSql('prototype','select 2'))"
} */;
/*+ mycat:run{
"hbt":"intersectAll(fromSql('prototype','select 1'),fromSql('prototype','select 2'))"
} */;
/*+ mycat:run{
"hbt":"intersectDistinct(fromSql('prototype','select 2'),fromSql('prototype','select 2'))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).groupBy(keys(groupKey()),aggregating(max(`id`)))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).groupBy(keys(groupKey()),aggregating(count()))"
} */;
/*+ mycat:run{
"hbt":"unionAll( fromSql(prototype,'SELECT COUNT(*) FROM `db1`.`travelrecord`'), fromSql(prototype,'SELECT COUNT(*) FROM (SELECT NULL FROM `db1`.`travelrecord` UNION ALL SELECT NULL FROM `db1`.`travelrecord` UNION ALL SELECT NULL FROM `db1`.`travelrecord`) AS `t2`')).groupBy(keys(groupKey()),aggregating(count()))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).orderBy(order(id,DESC))"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).orderBy(order(id,ASC)).limit(0,1)"
} */;
/*+ mycat:run{
"hbt":"fromTable(db1,travelrecord).orderBy(order(id,ASC)).limit(1,1)"
} */;
/*+ mycat:run{
"hbt":"leftJoin(`id0` = `id`,fromTable(db1,travelrecord).map(`id` as `id0`),fromTable(db1,company)).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"rightJoin(`id0` = `id`,fromTable(db1,travelrecord).map(`id` as `id0`),fromTable(db1,company)).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"semiJoin(`id0` = `id`,fromTable(db1,travelrecord).map(`id` as `id0`),fromTable(db1,company)).orderBy(order(id0,ASC))"
} */;
/*+ mycat:run{
"hbt":"antiJoin(`id0` = `id`,fromTable(db1,travelrecord).map(`id` as `id0`),fromTable(db1,company)).orderBy(order(id0,ASC))"
} */;
/*+ mycat:run{
"hbt":"antiJoin(`id0` = `id`,fromTable(db1,travelrecord).map(`id` as `id0`),fromTable(db1,company)).orderBy(order(id0,ASC))"
} */;
/*+ mycat:run{
"hbt":"leftJoin(`$0` eq `$$3`,leftJoin(`$0` eq `$$3`,unionAll( fromSql(prototype,'SELECT * FROM `db1`.`travelrecord`'), fromSql(prototype,'SELECT * FROM `db1`.`travelrecord` UNION ALL SELECT * FROM `db1`.`travelrecord` UNION ALL SELECT * FROM `db1`.`travelrecord`')),fromSql(prototype,'SELECT `id`, `companyname`, `addressid`, CAST(`id` AS SIGNED) AS `id0` FROM `db1`.`company`')).map(`$0` as `id`,`$1` as `user_id`,`$2` as `traveldate`,`$3` as `fee`,`$4` as `days`,`$5` as `blob`,`$6` as `id0`,`$7` as `companyname`,`$8` as `addressid`),fromSql(prototype,'SELECT `id`, `companyname`, `addressid`, CAST(`id` AS SIGNED) AS `id0` FROM `db1`.`company`')).map(`$0` as `id`,`$1` as `user_id`,`$2` as `traveldate`,`$3` as `fee`,`$4` as `days`,`$5` as `blob`,`$6` as `id0`,`$7` as `companyname`,`$8` as `addressid`,`$9` as `id1`,`$10` as `companyname0`,`$11` as `addressid0`).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"leftJoin(`$0` eq `$$0`,leftJoin(`$0` eq `$$0`,fromTable(db1,travelrecord), fromTable(db1,travelrecord)),fromTable(db1,company)).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"filterFromTable(`id` = 1,db1,travelrecord).orderBy(order(id,ASC))"
} */;
/*+ mycat:run{
"hbt":"fromRelToSql(prototype,fromTable('db1','travelrecord').filter(`id` = 1).map(`id`)).orderBy(order(id,ASC))"
} */;
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 | 类型 | 参数数量 | 参数 |
---|---|---|---|
fromTable | rel | 2 | 逻辑库的标识符,逻辑表的标识符 |
text
fromTable(db1,travelrecord)
java
//io.mycat.hbt.ast.HBTBuiltinHelper#fromTable(java.lang.String, java.lang.String)
fromTable("db1", "travelrecord")
table
匿名表,一种字面量构成的数据源
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
table | rel | 至少两个 | 字段信息列表,字面量列表(一维列表) |
text
table(fields(fieldType(`1`,`integer`),fieldType(`2`,`varchar`)),values())
table(fields(fieldType(id,`integer`)),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类型 |
---|---|---|
Bool | no-no | boolean |
Tinyint | no-no | byte |
Smallint | no-no | short |
Integer | no-no | int |
Bigint | no-no | long |
Decimal | no-no,yes-no | BigDecimal |
Float | no-no | float |
Real | no-no | float |
Double | no-no | double |
Date | no-no | Date ‘xxxx-xx-xx’ |
Time | no-no,yes-no | Time ‘xx:xx:xx’ |
TimeWithLocalTimeZone | no-no,yes-no | Timestamp ‘xxxx-xx-xxxx:xx:xx’ |
Timestamp | no-no,yes-no | Timestamp ‘xxxx-xx-xxxx:xx:xx’ |
TimestampWithLocalTimeZone | no-no,yes-no | Timestamp ‘xxxx-xx-xxxx:xx:xx’ |
Char | no-no | String |
Varchar | no-no | String |
Binary | no-no | byte[] |
Varbinary | no-no | byte[] |
语法辅助
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
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
map | rel | 至少两个 | 数据源,至少一个表达式 |
投影
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
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
rename | rel | 1+数据源的列数量 | 数据源,多个字段标识符(数据源的列数量) |
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
选择,根据布尔表达式对行进行过滤
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
filter | rel | 2 | 数据源,一个布尔表达式 |
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
除,去重
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#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
分组
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
分组 | rel | 2 | 分组键,聚合函数 |
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
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
avg | agg expr | 1 | 列名标识符 |
扩展型聚合函数,会被优化器转换成sum()/count()
输入值要求是数字类型
count
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
count | agg expr | 任意 | 多个列名标识符或无 |
当有多个列名标识符作为参数的时候,,返回其值不为null 的输入行数
无参的时候返回输入的行数
max
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
max | expr | 1 | 列名标识符 |
输入值要求是数字类型
返回所有输入值中的最大值
min
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
min | agg expr | 1 | 列名标识符 |
输入值要求是数字类型
返回所有输入值中的最小值
sum
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
sum | agg expr | 1 | 列名标识符 |
输入值要求是数字类型
返回所有输入值的数值总和
语法辅助
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,antiJoin | rel | 三个 | 条件:布尔表达式,左数据源,右数据源 |
//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
对数据源排序
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
OrderBy | rel | 1+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
要数据源返回的行的一部分
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
limit | rel | 1+多个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作为数据源
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
fromSql | rel | 2或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.避免逻辑表与物理表耦合
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
filterFromTable | rel | 3 | 布尔表达式,逻辑库标识符,逻辑表标识符 |
等价
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
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
fromRelToSql | rel | 2 |
文本
fromRelToSql(targetName,fromTable('db1','travelrecord').filter(`id` = 1).map(`id`))
=>等价转换
fromSql(targetName,'select db1.travelrecord.id from db1.travelrecord where `id` = 1')
modifyFromSql
向目标执行返回值不是结果集的SQL
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
modifyFromSql | update | 2 | 目标标识符,sql |
执行结果
updateCount
lastInsertId
文本
modifyFromSql(targetName,'delete db1.travelrecord1')
mergeModify
合拼多个modifyFromSql结果
即
updateCount 求和
lastInsertId 取mergeModify中最大值
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
mergeModify | update | 一个或者多个modifyFromSql | modifyFromSql |
文本
mergeModify(
modifyFromSql(targetName,'delete from db1.travelrecord1'),
modifyFromSql(targetName2,'delete from db2.travelrecord1')
)
HBT-命令表达式
explain
1.显示数据源的列类型,辅助编写hbt
2.显示执行计划
文本
explain filterFromTable(`id` = 1,db1,travelrecord)
HBT-标量表达式(运算符)
计算结果是一个特定数据类型的标量值的表达式
运算符函数名 | 糖 | 描述 | 算符优先级 | 参数数量 | 布尔表达式 |
---|---|---|---|---|---|
eq | = | 等于 | 10 | 2 | y |
ne | <>,!= | 不等于 | 10 | 2 | y |
gt | > | 大于 | 11 | 2 | y |
lt | < | 小于 | 11 | 2 | y |
gte | >= | 大于等于 | 11 | 2 | y |
lte | <= | 小于等于 | 11 | 2 | y |
add | + | 算术加法 | 13 | 2 | n |
minus | - | 算术减法 | 13 | 2 | n |
and | 无 | 逻辑与 | 6 | 2 | y |
or | 无 | 逻辑或 | 5 | 2 | y |
not | 无 | 逻辑非 | 无 | 1 | y |
as | 无 | 别名 | 3 | 2 | n |
cast | 无 | 类型转换 | 无 | 2 | n |
ref | 无 | 关联连接数据源引用 | 无 | 2 | n |
上述描述的运算符都是左结合,其他情况使用函数调用
当运算符参数数量为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,
rename,
groupBy
distinct,
orderBy
alias//仅用于聚合函数 ,
distinct//用于关系表达式,聚合函数 ,
approximate//仅用于聚合函数 ,
ignoreNulls//仅用于聚合函数 ,
filter//用于关系表达式,聚合函数 ,
类型转换与别名
fromTable(db1,travelrecord).map(cast(id,float) as a)
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
cast | expr | 2 | 表达式,类型标识符(不接受待求值的表达式) |
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
as | expr | 2 | 表达式,标识符(不接受待求值的表达式) |
内置常用函数
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
lower | expr | 1 | 表达式(字符串类型) |
把参数转成小写
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
upper | expr | 1 | 表达式(字符串类型) |
把参数转成大写
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
isnull | expr | 1 | 表达式 |
表达式的结果是否null
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
nullif | expr | 2 | 表达式1,表达式2 |
如果两表达式的结果相同,则返回null
名称 | 类型 | 参数数量 | 参数 |
---|---|---|---|
isnotnull | expr | 1 | 表达式 |
表达式的结果是否非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类型 |
---|---|---|
IntervalYear | no-no | |
IntervalYearMonth | no-no | |
IntervalMonth | no-no | |
IntervalDay | no-no,yes-no | |
IntervalDayHour | no-no,yes-no | |
IntervalDayMinute | no-no,yes-no | |
IntervalDaySecond | no-no,yes-no | |
IntervalHour | no-no,yes-no | |
IntervalHourMinute | no-no,yes-no | |
IntervalMinute | no-no,yes-no | |
IntervalMinuteSecond | no-no,yes-no | |
IntervalSecond | no-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)