MLSQL 支持条件分支语句
2.1.0-SNAPSHOT 及以上版本可用
MLSQL 在设计之初,并没打算支持分支语句,比如典型的分支语句if/else,或者for之类的。原因在于我们希望MLSQL尽可能的简单。然而,作为一个编程语言,终究难以逃脱if/else。最后我不得不安慰自己说,鼓励大家尽量不要去用就可以了,但无论如何,在语言级别应该还要支持的。
初见语法
下面是分支语句的一个典型语法:
set a = "wow,jack";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
在MLSQL中,if/else并非关键字,都是一个命令。既然是一个命令,那么为了符合MLSQL语法规范,那么每个命令后面都需要添加';'表示这个命令的结束。
我们来看看if 语句:
!if ''' split(:a,",")[0] == "jack" ''';
!if 命令后面接一个文本参数,该文本的内容是一个表达式。在表达式里,我们可以使用大部分SQL支持的函数。比如上面的例子是split函数。 我们使用":"来标识一个变量。变量来源于set语法。比如示例中表达式的:a
变量对应的值为"wow,jack",他是通过set语法来设置的。
从上面的例子可以看到,MLSQL的条件判断语句具有以下特色:
- 语法设计遵循SQL的一些原则。比如采用 and/or 替代
&&,||
.使用select语句做变量赋值 - 兼容大部分SQL函数
- 支持多个语句,最后一条语句作为最后的条件
- 支持用户自定义函数(参看文章后半部分)
一个复杂的例子
下面是一个更复杂的例子:
set a="jack,2";
!if ''' select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
:name == "jack" and :num == 3
''';
select 0 as a as b;
!elif ''' select split(:a,",")[1] as :num; :num==2 ''';
!if ''' 2==1 ''';
select 1.1 as a as b;
!else;
select 1.2 as a as b;
!fi;
!else;
select 2 as a as b;
!fi;
select * from b as output;
我们再来看 !if
语句
!if ''' select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
:name == "jack" and :num == 3
''';
和上个例子有些不同,因为我们要对:a变量进行多次处理,为了使得最后的表达式更加简单,MLSQL支持通过select语法来做变量赋值。语句
select split(:a,",")[0] as :name, split(:a,",")[1] as :num;
通过该语句,我们得到了:name 和:num两个变量。之后通过";"表示该语句的结束。在后续的语句中我们就可以引用对应的变量了:
:name == "jack" and :num == 3
MLSQL会对:num自动进行类型转换。
变量的作用域
在if/elif里申明的变量有效范围是整个!if/!fi区间。子if/else语句可以看到上层if/else语句的变量。 比如:
set name = "jack";
!if '''select :name as :newname ;:name == "jack" ''';
!if ''' :newname == "jack" ''';
!println '''====1''';
!else;
!println '''====2 ''';
!fi;
!else;
!println '''=====3''';
!fi;
该语句输出为====1
,我们在子if语句中使用了上面的 select产生的:newname
变量。
同样的,我们可以在子语句里方便的使用变量:
set name = "jack";
!if '''select concat(:name,"dj") as :newname ;:name == "jack" ''';
!if ''' :newname == "jackdj" ''';
!println '''====${newname}''';
select "${newname}" as a as b;
!else;
!println '''====2 ''';
!fi;
!else;
!println '''=====3''';
!fi;
select * from b as output;
我们可以在if/else的子语句里,比如select
,!println
等语句里通过原先的'${}'符号引用!if
或者!elif
里申明的变量。
结合set语法
条件分支语句结合强大的set语法,其实可以做很多有意思的事情,比如:
set a = "wow,jack" where type="defaultParam";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
此时他的输出会是2。 但是如果你在前面加一句:
set a = "jack,";
set a = "wow,jack" where type="defaultParam";
!if ''' split(:a,",")[0] == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
这个时候会输出1. 也就是用户可以通过添加set变量覆盖已经存在的变量从而控制脚本的执行。
函数定义
另外,MLSQL也支持使用自定义UDF函数,并且在if语句中也是可以使用的。比如:
register ScriptUDF.`` as title where
lang="scala"
and code='''def apply()={
"jack"
}'''
and udfType="udf";
!if ''' title() == "jack" ''';
select 1 as a as b;
!else;
select 2 as a as b;
!fi;
select * from b as output;
这意味着你可以通过代码获取某个接口的值(或者特定的处理逻辑),来决定脚本的最后执行情况。有木有很强大。
if/else带来的问题
因为在MLSQL里有session的概念。当一个表被使用过后,系统会自动记住。这样可以很方便的用户进行调试。 但是当使用if/else的时候则会有困惑发生。
我们来看下面这个例子:
!if ''' 2==1 ''';
select 1 as a as b;
!else;
!fi;
select * from b as output;
当一次运行的时候,系统会报错,因为没有表b
.
如果我们将2==1 修改为 1==1 时,则系统正常运行。
然后我们将1==1 再次修改为2==1 此时系统依然正常运行。
因为系统记住了上次运行的b,所以虽然当前没有执行select语句,但是依然有输出,从而造成错误。解决办法有两个:
- 请求参数设置sessionPerRequest,这样每次请求都会保证隔离。
- 在脚本里备注
set __table_name_cache__ = "false";
让系统不要记住表名。
第二种方式我们推荐,使用如下:
set __table_name_cache__ = "false";
!if ''' 2==1 ''';
select 1 as a as b;
!else;
!fi;
select * from b as output;