10.8 预览特性:CREATE FUNCTION 中的 IN, OUT, INOUT, IN OUT 参数

作为 Oracle 兼容性语法扩展,存储函数参数的 IN, OUTINOUT / IN OUT 限定符在 MDEV-10654 中被提出需求。据指出,Postgres 和 DB2 也支持此语法。几年后,得益于我们的社区贡献者,来自日本乐天证券的 Manohar KB,此功能得以实现。来自 MariaDB Corporation 的 Alexander Barkov 提供了评审,并将此 PL/SQL 语法扩展到非 SQL_MODE=ORACLE 模式下的 SQL/PSM 中可用。

函数拥有 OUT 参数使得嵌套函数可以实现多个返回值,而此前只能有一个返回值。在此特性之前,限定符仅在存储过程中可用。

Oracle 模式下提供的语法

此更改在 SQL_MODE=ORACLE 模式下的 PACKAGEPACKAGE BODY 中支持的语法是

FUNCTION func(a [IN | OUT | INOUT  | IN OUT] INT) RETURN INT

Oracle 模式在函数声明中将 IN/OUT/INOUT/IN OUT 放在参数标识符之后,而下一节中的 SQL/PSM 语法显示参数标识符在第二位。

你可以在 Oracle 模式下通过以下命令运行你自己的容器实例来体验此特性

$ podman run --rm --detach --env MARIADB_RANDOM_ROOT_PASSWORD=1 --env MARIADB_DATABASE=test --env MARIADB_USER=testuser --env MARIADB_PASSWORD=testpassword --name mdb108_inout quay.io/mariadb-foundation/mariadb-devel:10.8-mdev-10654-inout --sql-mode=ORACLE

注意:所有示例中我都使用了 podman,但任何 OCI 运行时实现都同样适用。

然后使用 MariaDB monitor 接口,我们可以创建一些使用 IN, OUT 和 INOUT 的函数。

$ podman exec -ti mdb108_inout mariadb -u testuser -ptestpassword test
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.8.0-MariaDB-MDEV-10654-inout mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [test]> DELIMITER #
MariaDB [test]> CREATE OR REPLACE PACKAGE pkg2
    -> AS
    -> FUNCTION add_func2(a IN INT, b IN INT) RETURN INT;
    -> END
    -> #
MariaDB [test]> CREATE OR REPLACE PACKAGE BODY pkg2
-> AS
-> FUNCTION add_func2(a IN INT, b IN INT) RETURN INT
-> AS
-> BEGIN
-> RETURN a + b;
-> END;
-> END;
-> #
Query OK, 0 rows affected (0.005 sec)

MariaDB [test]> select pkg2.add_func2(3, 9)
-> #
+----------------------+
| pkg2.add_func2(3, 9) |
+----------------------+
|                   12 |
+----------------------+
1 row in set (0.000 sec)

注意,与下面的 SQL/PSM 模式一样,Oracle 模式下也存在限制,即不能在 SELECT 语句中使用包含 OUT 的限定符。

SQL/PSM(标准,非 Oracle)模式下提供的语法

不指定 SQL_MODE 运行容器

$ podman run --rm -d -e MARIADB_RANDOM_ROOT_PASSWORD=1 -e MARIADB_DATABASE=test -e MARIADB_USER=testuser -e MARIADB_PASSWORD=testpassword --name mdb108_inout quay.io/mariadb-foundation/mariadb-devel:10.8-mdev-10654-inout

在容器内运行 MariaDB monitor

$ podman exec -ti mdb108_inout mariadb -u testuser -ptestpassword test
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.8.0-MariaDB-MDEV-10654-inout mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [test]> CREATE FUNCTION Hypotenuse (IN a DOUBLE, IN b DOUBLE) RETURNS DOUBLE DETERMINISTIC CONTAINS SQL RETURN SQRT(a*a + b*b);
Query OK, 0 rows affected (0.022 sec)

MariaDB [test]> select Hypotenuse(3.0, 4.0);
+----------------------+
| Hypotenuse(3.0, 4.0) |
+----------------------+
|                    5 |
+----------------------+
1 row in set (0.000 sec)

OUT, INOUTIN OUT 限定符可以在 SET 语句中使用,但不能在 SELECT 语句中使用。这在 Oracle 模式下也一样。以下示例可以证明这一点:

MariaDB [test]> delimiter #
MariaDB [test]> CREATE FUNCTION add_func3(IN a INT, IN b INT, OUT c INT) RETURNS INT BEGIN   SET c = 100;   RETURN a + b; END; #                       
Query OK, 0 rows affected (0.007 sec)

MariaDB [test]> delimiter ;
MariaDB [test]> SET @a = 2; SET @b = 3; SET @c = 0; SET @res= add_func3(@a, @b, @c);
Query OK, 0 rows affected (0.000 sec)
Query OK, 0 rows affected (0.000 sec)
Query OK, 0 rows affected (0.000 sec)
Query OK, 0 rows affected (0.000 sec)

MariaDB [test]> select @a, @b, @c, @res;
+------+------+------+------+
| @a   | @b   | @c   | @res |
+------+------+------+------+
|    2 |    3 |  100 |    5 |
+------+------+------+------+
1 row in set (0.000 sec)

MariaDB [test]>  select add_func3(@a, @b, @c);
ERROR 4186 (HY000): OUT or INOUT argument 3 for function add_func3 is not allowed here

使用 SET 的一个有用之处在于可以利用其他函数或存储过程的结果,例如这些示例:

MariaDB [test]> delimiter #
MariaDB [test]> begin not atomic
    -> declare res int;
    -> declare c int;      
    -> set res = add_func3(2, 4, c);
    -> select if(c > 99, res, 0);
    -> end;
    -> #
+--------------------+
| if(c > 99, res, 0) |
+--------------------+
|                  6 |
+--------------------+
1 row in set (0.001 sec)

MariaDB [test]>  CREATE FUNCTION add_func4(IN a INT, IN b INT, d INT) RETURNS INT
BEGIN
  DECLARE c, res INT;
  SET res = add_func3(a, b, c) + d;
  if (c > 99) then
    return  3;
  else
    return res;
  end if;
END; #
Query OK, 0 rows affected (0.024 sec)

MariaDB [test]> select add_func4(1,2,3)#
+------------------+
| add_func4(1,2,3) |
+------------------+
|                3 |
+------------------+
1 row in set (0.000 sec)

游标也可以使用 IN 语法(Oracle 模式下也一样)

MariaDB [test]> delimiter #
MariaDB [test]> BEGIN NOT ATOMIC
    ->   DECLARE va INT;
    ->   DECLARE cur CURSOR (IN a INT) FOR SELECT a FROM dual;
    ->   OPEN cur(1);
    ->   FETCH cur INTO va;
    ->   CLOSE cur;
    ->   SELECT va;
    -> END;
    -> #
+------+
| va   |
+------+
|    1 |
+------+
1 row in set (0.000 sec)

Query OK, 0 rows affected (0.000 sec)

致谢

再次感谢 Manohar KB 编写此特性及其测试用例,以及他的雇主日本乐天证券使之成为可能。正是这样的努力,将杰出的特性请求变成了所有 MariaDB 用户可用的特性。我们鼓励所有具备编程能力的人为 MariaDB 做出类似的贡献,实现他们最喜欢的、可支持的特性扩展。

欢迎反馈

如果你在此特性中遇到任何问题,请在 MDEV 项目上提交 JIRA 错误/特性请求告知我们。

参考资料