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的条件判断语句具有以下特色:

  1. 语法设计遵循SQL的一些原则。比如采用 and/or 替代 &&,||.使用select语句做变量赋值
  2. 兼容大部分SQL函数
  3. 支持多个语句,最后一条语句作为最后的条件
  4. 支持用户自定义函数(参看文章后半部分)

一个复杂的例子

下面是一个更复杂的例子:

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语句,但是依然有输出,从而造成错误。解决办法有两个:

  1. 请求参数设置sessionPerRequest,这样每次请求都会保证隔离。
  2. 在脚本里备注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;

results matching ""

    No results matching ""