创建存储过程的语法如下:

  1. CREATE [OR REPLACE] PROCEDURE Procedure_name
  2. [ (argment [ { IN | IN OUT }] Type,
  3. argment [ { IN | OUT | IN OUT } ] Type ]
  4. [ AUTHID DEFINER | CURRENT_USER ]
  5. { IS | AS }
  6. delarification_block
  7. BEGIN
  8. procedure_body
  9. EXCEPTION
  10. exception_handler
  11. END;

无参数的存储过程示例如下:

  1. CREATE table loghistory
  2. (userid VARCHAR2(20),
  3. logdate date default sysdate);
  4. CREATE OR REPLACE PROCEDURE userlogin
  5. IS
  6. BEGIN
  7. INSERT INTO loghistory (userid) VALUES (USER);
  8. END;

调用存储过程

存储过程建立完成后,用户通过授权可以在 OBClient 、OceanBase Developer Center 或第三方开发工具中来调用运行。

  1. obclient> select * from loghistory;
  2. Empty set (0.02 sec)
  3. obclient> BEGIN
  4. -> userlogin;
  5. -> END;
  6. -> /
  7. Query OK, 0 rows affected (0.03 sec)
  8. obclient> select * from loghistory;
  9. +--------+-----------+
  10. | USERID | LOGDATE |
  11. +--------+-----------+
  12. | HR | 27-SEP-20 |
  13. +--------+-----------+
  14. 1 row in set (0.00 sec)
  15. obclient> commit;
  16. Query OK, 0 rows affected (0.00 sec)

子程序属性

每个子程序属性只能在子程序声明中出现一次,并可以按任何顺序出现。属性位于子程序的 IS 或 AS 关键字之前。 该属性不能出现在嵌套子程序中。

程序包中只能出现 ACCESSIBLE BY 属性。 独立子程序在其声明中可能具有以下属性。

  • AUTHID 属性

  • ACCESSIBLE BY 子句

AUTHID

定义过程时可以定义 AUTHID 子句,来获取存储过程在运行时被授予哪个用户的权限。权限类型包括以下两种:

  • AUTHID DEFINER(定义者权限):默认情况下,存储过程的权限来自于所有者。

  • AUTHID CURRENT_USER(调用者权限):运行时被授予当前会话用户的权限,这可能和当前登录用户相同或不同(ALTER SESSION SET CURRENT_SCHEMA 可以改变调用者 schema)

如下示例为,用 HR 用户创建过程 userlogin,指定 AUTHID DEFINER。

  1. CREATE OR REPLACE PROCEDURE userlogin
  2. AUTHID DEFINER
  3. IS
  4. BEGIN
  5. INSERT INTO loghistory (userid) VALUES (USER);
  6. END;

HR 用户把 userlogin 的执行权限授予给其他用户,这样 scott 用户可以执行过程。

  1. obclient> select * from loghistory;
  2. +--------+-----------+
  3. | USERID | LOGDATE |
  4. +--------+-----------+
  5. | HR | 27-SEP-20 |
  6. +--------+-----------+
  7. obclient> grant EXECUTE ON userlogin to public;
  8. Query OK, 0 rows affected (0.03 sec)

SCOTT 用户对 hr.loghistory 并没有访问权限,但是可以执行过程 userlogin。

  1. obclient> select user from dual;
  2. +-------+
  3. | USER |
  4. +-------+
  5. | SCOTT |
  6. +-------+
  7. 1 row in set (0.00 sec)
  8. obclient> select * from loghistory;
  9. ORA-00942: table or view 'SCOTT.LOGHISTORY' does not exist
  10. obclient> select * from hr.loghistory;
  11. ORA-00942: table or view does not exist
  12. obclient> BEGIN
  13. -> hr.userlogin;
  14. -> END;
  15. -> /
  16. Query OK, 0 rows affected (0.02 sec)
  17. obclient> commit;
  18. Query OK, 0 rows affected (0.00 sec)

scott 用户通过运行 HR 用户创建的过程 userlogin 成功插入 hr.loghistory。说明 scott 用户在运行这个存储过程时,拥有的是 HR 的权限。

  1. obclient> select user from dual;
  2. +------+
  3. | USER |
  4. +------+
  5. | HR |
  6. +------+
  7. 1 row in set (0.00 sec)
  8. obclient> select * from hr.loghistory;
  9. +--------+-----------+
  10. | USERID | LOGDATE |
  11. +--------+-----------+
  12. | HR | 27-SEP-20 |
  13. | SCOTT | 27-SEP-20 |
  14. +--------+-----------+
  15. 2 rows in set (0.01 sec)

关于AUTHID CURRENT_USER子句。HR 用户用 AUTHID CURRENT_USER 子句重新定义过程 userlogin。

  1. CREATE OR REPLACE PROCEDURE userlogin
  2. AUTHID CURRENT_USER
  3. IS
  4. BEGIN
  5. INSERT INTO loghistory (userid) VALUES (USER);
  6. END;

scott 用户再次执行 userlogin 报错,因为 scott 没有访问 hr.loghistory 这张表的权限。用 scott 用户创建表 loghistory,可以执行成功,数据实际插入了 scott.loghistory。

  1. obclient> select user from dual;
  2. +-------+
  3. | USER |
  4. +-------+
  5. | SCOTT |
  6. +-------+
  7. 1 row in set (0.00 sec)
  8. obclient> BEGIN
  9. -> hr.userlogin;
  10. -> END;
  11. -> /
  12. ORA-00942: table or view 'SCOTT.LOGHISTORY' does not exist
  13. obclient> alter session set current_schema=hr;
  14. Query OK, 0 rows affected (0.00 sec)
  15. obclient> BEGIN
  16. -> hr.userlogin;
  17. -> END;
  18. -> /
  19. ORA-00942: table or view does not exist
  20. obclient> alter session set current_schema=scott;
  21. Query OK, 0 rows affected (0.00 sec)
  22. obclient> CREATE table loghistory
  23. -> (userid VARCHAR2(20),
  24. -> logdate date default sysdate);
  25. Query OK, 0 rows affected (0.06 sec)
  26. obclient> BEGIN
  27. -> hr.userlogin;
  28. -> END;
  29. -> /
  30. Query OK, 0 rows affected (0.02 sec)
  31. obclient> commit;
  32. Query OK, 0 rows affected (0.00 sec)
  33. obclient> select * from loghistory;
  34. +--------+-----------+
  35. | USERID | LOGDATE |
  36. +--------+-----------+
  37. | SCOTT | 27-SEP-20 |
  38. +--------+-----------+
  39. 1 row in set (0.01 sec)

ACCESSIBLE BY

ACCESSIBLE BY 可以约束一个对象的调用者。

访问者列表明确列出了可能具有访问权限的单元。 访问者列表可以在子程序包上定义。 除了在包本身(如果有)上可以定义的访问者列表之外,还支持表的自检。 该列表可能仅限制对子程序的访问,而不能扩展访问,以防止非必要的使用内部子程序。 例如,不支持将一个程序包重组为两个程序包:一个程序用于限制少数访问,另一个程序用于公开访问。

ACCESSIBLE BY 子句可用于对象类型、对象类型主体、包和子程序的声明中。

ACCESSIBLE BY 子句可以出现在以下 SQL 语句中:

  • ALTER TYPE 陈述式

  • CREATE FUNCTION 语句

  • 创建过程语句

  • 创建包声明

  • CREATE TYPE 语句

  • CREATE TYPE BODY 语句

语法如下:

  1. unit_kind:
  2. FUNCTION { $$[0] = SP_FUNCTION; }
  3. | PROCEDURE { $$[0] = SP_PROCEDURE; }
  4. | PACKAGE_P { $$[0] = SP_PACKAGE; }
  5. | TRIGGER { $$[0] = SP_TRIGGER; }
  6. | TYPE { $$[0] = SP_TYPE; }
  7. ;
  8. accessor:
  9. pl_schema_name
  10. {
  11. malloc_non_terminal_node($$, parse_ctx->mem_pool_, T_SP_ACCESSOR, 2, NULL, $1);
  12. }
  13. | unit_kind pl_schema_name
  14. {
  15. ParseNode *accessor_kind = NULL;
  16. malloc_terminal_node(accessor_kind, parse_ctx->mem_pool_, T_SP_ACCESSOR_KIND);
  17. accessor_kind->value_ = $1[0];
  18. malloc_non_terminal_node($$, parse_ctx->mem_pool_, T_SP_ACCESSOR, 2, accessor_kind, $2);
  19. }
  20. ;
  21. accessor_list:
  22. accessor_list ',' accessor
  23. {
  24. malloc_non_terminal_node($$, parse_ctx->mem_pool_, T_LINK_NODE, 2, $1, $3);
  25. }
  26. | accessor
  27. {
  28. $$ = $1;
  29. }
  30. ;
  31. accessible_by:
  32. ACCESSIBLE BY '(' accessor_list ')'
  33. {
  34. ParseNode *accessor_list = NULL;
  35. merge_nodes(accessor_list, parse_ctx->mem_pool_, T_SP_ACCESSOR_LIST, $4);
  36. malloc_non_terminal_node($$, parse_ctx->mem_pool_, T_SP_ACCESSIBLE_BY, 1, accessor_list);
  37. }
  38. ;

自治事务

OceanBase 数据库支持自治事务。 自治事务独立于父事务存在,单独进行提交或者回滚而不会对父事务产生影响。

根据如下示例创建对象:

  1. -- 创建日志表
  2. CREATE TABLE logtable(
  3. username varchar2(20),
  4. date_time date,
  5. message varchar2(60)
  6. );
  7. -- 创建临时表
  8. CREATE TABLE demotable( N number );
  9. -- 创建拥有自制事务的过程
  10. CREATE OR REPLACE PROCEDURE log_message(p_message varchar2)
  11. AS
  12. PRAGMA AUTONOMOUS_TRANSACTION;
  13. BEGIN
  14. INSERT INTO logtable VALUES ( user, sysdate, p_message );
  15. COMMIT;
  16. END log_message;

接下来操作步骤如下:

  1. 调用 log_message 写日志。

  2. 在事务中插入 demotable。

  3. 调用 log_message 再次写一条日志。

  4. 回滚当前事务。

  1. obclient> SELECT * FROM logtable;
  2. Empty set (0.00 sec)
  3. obclient> SELECT * FROM demotable;
  4. Empty set (0.01 sec)
  5. obclient> BEGIN
  6. -> Log_message ('About to insert into demotable.');
  7. -> INSERT INTO demotable VALUES (1);
  8. -> Log_message ('Rollback the transaction.');
  9. -> ROLLBACK;
  10. -> END;
  11. -> /
  12. Query OK, 0 rows affected (0.05 sec)
  13. obclient> SELECT * FROM logtable;
  14. +----------+-----------+---------------------------------+
  15. | USERNAME | DATE_TIME | MESSAGE |
  16. +----------+-----------+---------------------------------+
  17. | HR | 28-SEP-20 | About to insert into demotable. |
  18. | HR | 28-SEP-20 | Rollback the transaction. |
  19. +----------+-----------+---------------------------------+
  20. 2 rows in set (0.00 sec)

如上示例中,对 demotable 的插入操作被回滚了,拥有自治事务的过程 log_message 只提交了自己的事务,向日志表里写入的数据。

重新创建过程 log_message,去除自治事务属性,查看数据库的行为。

  1. CREATE OR REPLACE PROCEDURE log_message(p_message varchar2)
  2. AS
  3. BEGIN
  4. INSERT INTO logtable VALUES ( user, sysdate, p_message );
  5. COMMIT;
  6. END log_message;

再次执行上述过程:

  1. obclient> SELECT * FROM logtable;
  2. Empty set (0.01 sec)
  3. obclient> SELECT * FROM demotable;
  4. Empty set (0.01 sec)
  5. obclient> BEGIN
  6. -> Log_message ('About to insert into demotable.');
  7. -> INSERT INTO demotable VALUES (1);
  8. -> Log_message ('Rollback the transaction.');
  9. -> ROLLBACK;
  10. -> END;
  11. -> /
  12. Query OK, 0 rows affected (0.04 sec)
  13. obclient> SELECT * FROM logtable;
  14. +----------+-----------+---------------------------------+
  15. | USERNAME | DATE_TIME | MESSAGE |
  16. +----------+-----------+---------------------------------+
  17. | HR | 28-SEP-20 | About to insert into demotable. |
  18. | HR | 28-SEP-20 | Rollback the transaction. |
  19. +----------+-----------+---------------------------------+
  20. 2 rows in set (0.00 sec)
  21. obclient> SELECT * FROM demotable;
  22. +------+
  23. | N |
  24. +------+
  25. | 1 |
  26. +------+
  27. 1 row in set (0.00 sec)

对比两次执行过程,第二次执行中 demotable 上的事务并没有被回滚。 如果不使用自治事务,所有修改都在同一个事务中进行,第二次调用 log_message 过程时,其中的 commit 语句会把整个事务提交,包括 INSERT INTO demotable VALUES (1)。