http://www.oracle.com/technology/oramag/oracle/07-jul/o47plsql.html
关于INVOKER
By Steven Feuerstein
在Oracle8i以前,所有已编译存储对象(包括packages, procedures, functions, triggers, and views)只能以定义者(Definer)身份解析运行;从Oracle8i开始,Oracle引入调用者 (invoker)权限,使得对象可以以调用者(invoker)身份和权限执行。
l 定义者(Definer)指编译存储对象的所有者。
l 调用者(invoker)指拥有当前会话权限的模式。
注:这可能和当前登录用户相同或不同。
(alter session set current_schema 可以改变调用者Schema)。
例如用户即便拥有DBA权限,那么如果想在存储过程中执行创建表的语句,也会报权限不足的错误。这种情况下必须显式的授予CREATE TABLE权限,才能执行存储过程。实际上,有时为了执行某个存储过程,可能要给用户授予相当多的权限才行。而使用调用者(invoker)权限,可以解决这一问题,只需在存储过程中加入Authid Current_User就可以了。
问题:
我最近了解了很多关于AUTHID CURRENT_USER语句(调用者权限)的用途。我经常为团队编写一些功能模块,我将他们都定义在一个叫SHARED_CODE的模式中。由于我使用了调用者(invoker)权限,所有开发人员都能调用我的程序对他们自己的模式对象进行操作而得到想要的结果。
但是,最近我碰到了调用者(invoker)权限的一个问题。在我们接下来的一个项目中,我们即将通过UTL_FILE包对操作系统中的文件做大量的操作。具体细节是这样的:我们将文本写入文件,然后将通过几种不同的方式进行排版。因此我决定编写一个“读写文件”的程序,来实现UTL_FILE包的所有功能,同时,还可以允许用户指定排版格式。这样做可以使用户获得最大的灵活性。
但是这个程序看上去不管用,我已经定义我的程序使用调用者(invoker)权限,然而它拒绝执行。我到底哪里做错了呢?
解答:
你并没有做错任何事情。相反,你正好遇到调用者(invoker)权限的限制。幸运的是,我能帮助你绕开限制,从而得到你想要的结果。
让我们先来了解一下有关调用者(invoker)权限的背景,它可以应用于PL/SQL块中任何运行态SQL语句,包括静态的和动态的。使用调用者(invoker)权限后,名称解析和相关权限的确认都会在运行时发生(对于后者,将验证用户的角色并确认他们的权限)。
调用者(invoker)权限影响下列语句:
l SELECT, INSERT, UPDATE, 和 DELETE 等数据操作语句
l LOCK TABLE 事务控制语句
l OPEN 和 OPEN-FOR 游标控制语句
l 所有的动态SQL语句(EXECUTE IMMEDIATE, OPEN FOR,和 DBMS_SQL)
现在假设:程序开头部分使用了AUTHID CURRENT_USER子句,在编译这个程序时,依然需要当前模式(Schema) 对程序中所引用的所有数据库对象显式的授予权限。如果程序中的数据库对象是属于当前模式的,这个步骤当然可以省略。而如果程序中引用其他模式的对象,没有经过授权的话,将会不能通过编译。
然后授权其他模式来执行我的程序,当连接到那些模式中的一个用户执行我的程序时,程序中对数据库对象的引用将会按照当前连接模式的权限(显式权限或通过角色授予的权限)来解释。也就是说,程序运行时,ORACLE会判断当前连接模式对这些被引用的对象的权限,来执行。
从中得出一个明显的结论,AUTHID CURRENT_USER子句不能应用于子程序的静态调用。因为程序的静态调用是在编译时就被解释的,而不是运行时。
举个例子,proc1静态调用proc2。
PROCEDURE proc1
IS
BEGIN
proc2 ();
END proc1;
当在ABC模式中编译proc1时,ORACLE数据库将按照ABC模式的权限来解释proc2的引用。现在假设模式ABC中有一个存储过程proc2,然后将ABC.proc1的执行权限授予其他模式,如DEF,然而DEF有自己的存储过程也叫做proc2。当用户连接到DEF中运行ABC.proc1时,依然会调用ABC.proc2,而不是DEF.proc2。
下面让我们看看使用调用者(invoker)权限的效果。
首先,让我们考虑AUTHID CURRENT_USER作用在数据对象上。在表1中,我在HR模式中创建了一个调用者(invoker)权限的存储过程:show_my_data,用来列出my_data表中记录行数(应为10)。然后给SCOTT模式授予这个程序的执行权限。SCOTT模式中也有一张my_data表,只有一行数据。
当SCOTT运行HR.show_my_data时,程序将会显示1而不是10。
表 1: 创建并运行show_my_data
SQL> CONNECT hr/hr Connected. SQL> CREATE TABLE my_data (VALUE NUMBER) 2 /
Table created. SQL> BEGIN 2 FOR indx IN 1 .. 10 3 LOOP 4 INSERT INTO my_data 5 VALUES (indx); 6 END LOOP ; 7 8 COMMIT; 9 END; 10 / PL/SQL procedure successfully completed. SQL> CREATE OR REPLACE PROCEDURE show_my_data 2 AUTHID CURRENT_USER 3 IS 4 num PLS_INTEGER; 5 BEGIN 6 SELECT COUNT (*) 7 INTO num 8 FROM my_data; 9 10 DBMS_OUTPUT.put_line ('Count of my_data = ' || num); 11 END show_my_data; 12 /
Procedure created.
SQL> GRANT EXECUTE ON show_my_data TO scott 2 /
Grant succeeded.
SQL> CONNECT scott/tiger Connected.
SQL> CREATE TABLE my_data (VALUE NUMBER) 2 /
Table created.
SQL> BEGIN 2 INSERT INTO my_data 3 VALUES (1); 4 5 COMMIT; 6 END; 7 /
PL/SQL procedure successfully completed.
SQL> CALL hr.show_my_data(); Count of my_data = 1 |
看看最终结果,即使运行的是HR模式的show_my_data存储过程,这个程序依然显示的是SCOTT模式(调用者)my_data表的数量。这说明在数据对象上,调用者(invoker)权限是起作用的。
现在让我们尝试编写“读写文件”的程序。首先,设置属于HR模式的一个数据库目录,并且创建一个“默认的”格式函数(将文本转化为大写)。
CONNECT hr/hr
CREATE DIRECTORY temp AS 'c:/temp' / GRANT WRITE ON DIRECTORY temp TO scott; /
CREATE OR REPLACE FUNCTION format_line (line_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN UPPER (line_in); END format_line; /
|
其次,我将建立一个非常简单的程序,如表2所列,模仿“读写文件”功能,而且确保SCOTT能够调用他。
表 2: 创建write_to_file
CREATE OR REPLACE PROCEDURE write_to_file ( dir_in IN VARCHAR2 , file_name_in IN VARCHAR2 , lines_in IN DBMS_SQL.varchar2s ) AUTHID CURRENT_USER IS l_file UTL_FILE.file_type; BEGIN l_file := UTL_FILE.fopen (LOCATION => dir_in , filename => file_name_in , open_mode => 'W' , max_linesize => 32767 );
FOR indx IN 1 .. lines_in.COUNT LOOP UTL_FILE.put_line (l_file, format_line (lines_in (indx))); END LOOP ;
UTL_FILE.fclose (l_file); END write_to_file; / GRANT EXECUTE ON write_to_file TO scott / |
然后我以SCOTT连接,创建另一个格式函数(使文本转化为小写),并且运行write_to_file。
表 3: 创建format_line (并且调用write_to_file)
CREATE OR REPLACE FUNCTION format_line (line_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN LOWER (line_in); END format_line; /
DECLARE l_lines DBMS_SQL.varchar2s; BEGIN l_lines (1) := 'steven feuerstein'; l_lines (2) := 'is obsessed with PL/SQL.'; hr.write_to_file ('TEMP', 'myfile.txt', l_lines); END; / |
不幸的是,在myfile.txt输出文件中发现了下列文本。
STEVEN FEUERSTEIN
IS OBSESSED WITH PL/SQL.
看上去非常肯定,调用的依然是HR的format_line函数,而不是调用者SCOTT的。因此,不能在程序调用中利用调用者(invoker)权限,对吗?好吧,也许还有一条路:使用动态SQL。
现在重写HR模式的write_to_file程序。
表 4: 创建更新的write_to_file
CREATE OR REPLACE PROCEDURE write_to_file ( dir_in IN VARCHAR2 , file_name_in IN VARCHAR2 , lines_in IN DBMS_SQL.varchar2s ) AUTHID CURRENT_USER IS l_file UTL_FILE.file_type; l_newline VARCHAR2 (32767); BEGIN l_file := UTL_FILE.fopen (LOCATION => dir_in , filename => file_name_in , open_mode => 'W' , max_linesize => 32767 );
FOR indx IN 1 .. lines_in.COUNT LOOP EXECUTE IMMEDIATE 'BEGIN :new_line := format_line (:old_line); END;' USING OUT l_newline, IN lines_in (indx);
UTL_FILE.put_line (l_file, l_newline); END LOOP ;
UTL_FILE.fclose (l_file); END write_to_file; / |
请注意,这里变动了format_line函数的调用方式,因此它现在处于EXECUTE IMMEDIATE语句中。这就是说,使用动态PL/SQL块来调用这个函数。
现在,连接SCOTT并且调用write_to_file。
DECLARE l_lines DBMS_SQL.varchar2s; BEGIN l_lines (1) := 'steven feuerstein'; l_lines (2) := 'is obsessed with PL/SQL.'; hr.write_to_file ('TEMP', 'myfile.txt', l_lines); END; / |
现在可以在myfile.txt输出文件中看到下列内容:
steven feuerstein
is obsessed with pl/sql.
嘿,成功了!这怎么可能?
由于替换为动态执行的PL/SQL块中来调用子程序的方法,而调用者(invoker)权限可以应用到EXECUTE IMMEDIATE动态SQL语句上,因此调用可以成功执行。这就是说,程序是在SCOTT模式的权限下运行,因此被调用的是SCOTT模式下的format_line函数。
因此可以得出结论:使用动态PL/SQL时,调用者(invoker)权限可以发挥作用。
然而请注意,动态PL/SQL子程序比静态调用的执行效率要低。对运行时间较长的程序,这点效率降低可以忽略不计,但如果将这种方法应用到运行较快的程序时可能就会发现这个问题。我比较了一下静态和动态运行一个程序的性能,运行静态调用的程序100000次,花费2.83秒,而运行动态调用的程序则花费了6.66秒。
附注:可以得到同样结果的另一种方式是动态多态(dynamic polymorphism)。这种技术是由PL/SQL中的对象类型来实现的——明确一点说,是通过使用UNDER关键字设置对象类型的继承性来实现的。关于对象类型和动态多态的更多细节,请参考ORACLE Database PL/SQL User's Guide and Reference第12章“Using PL/SQL With Object Types”。