将表、视图、函数和查询的引用传递给存储过程¶
如果调用存储过程,并将表、视图或函数的标识符传递给存储过程,您可能需要:
允许存储过程对对象执行 SQL 操作,即使存储过程使用所有者权限也是如此。
如果标识符并非限定标识符或部分限定标识符,则允许存储过程解析对象的完全限定名称。
在此类情况下,您可以创建并传入对表、视图或函数的引用。引用 是表、视图或函数的唯一标识符。在存储过程中,对于对象的引用执行 SQL 操作时,这些操作将使用创建引用的用户的活动角色或辅助角色执行。此外,如果表、视图或函数不是完全限定的,则使用创建引用时的当前数据库和架构(即创建引用的用户的数据库和架构)来解析对象名称。
同样,如果需要将查询传入存储过程,并 在 SELECT 语句的 FROM 子句中使用该查询,则可以创建并传入 查询引用。在存储过程中,查询使用创建查询引用的用户的活动角色或辅助角色执行。与对表、视图和函数的引用一样,如果查询中的对象名称并非完全限定形式,则使用创建查询引用时在使用的数据库和架构来解析对象名称。
本主题介绍如何创建和使用引用。
简单示例¶
假设某个所有者权限存储过程旨在将行插入到一个表中,该表通过输入实参指定。以下是使用 Snowflake Scripting 编写的示例:
USE ROLE stored_proc_owner;
CREATE OR REPLACE PROCEDURE insert_row(table_identifier VARCHAR)
RETURNS TABLE()
LANGUAGE SQL
AS
$$
BEGIN
LET stmt VARCHAR := 'INSERT INTO ' || table_identifier || ' VALUES (10)';
LET res RESULTSET := (EXECUTE IMMEDIATE stmt);
RETURN TABLE(res);
END;
$$;
以下是使用 JavaScript 编写的一个类似示例:
USE ROLE stored_proc_owner;
CREATE OR REPLACE PROCEDURE insert_row(table_identifier VARCHAR)
RETURNS FLOAT
LANGUAGE JAVASCRIPT
AS
$$
let res = snowflake.execute({
sqlText: "INSERT INTO IDENTIFIER(?) VALUES (10);",
binds : [TABLE_IDENTIFIER]
});
res.next()
return res.getColumnValue(1);
$$;
假设您需要为另外一个角色拥有的表调用此过程:
USE ROLE table_owner;
CREATE OR REPLACE TABLE table_with_different_owner (x NUMBER) AS SELECT 42;
如您调用存储过程并传入表的名称,存储过程会失败,因为存储过程的所有者没有足够的权限来访问此表:
USE ROLE table_owner;
CALL insert_row('table_with_different_owner');
002003 (42S02): Uncaught exception of type 'STATEMENT_ERROR' on line 4 at position 25 : SQL compilation error:
Table 'TABLE_WITH_DIFFERENT_OWNER' does not exist or not authorized.
若要使存储过程能够以调用方身份对表执行 SQL 操作,请创建对表的引用并传入该引用,而非表名。
若要创建引用,请调用 SYSTEM$REFERENCE 函数。例如:
USE ROLE table_owner;
CALL insert_row(SYSTEM$REFERENCE('TABLE', 'table_with_different_owner', 'SESSION', 'INSERT'));
上面的示例将以下实参传递给 SYSTEM$REFERENCE 函数:
'TABLE'
(表示对象的类型)。'table_with_different_owner'
(表示表的名称)。'SESSION'
(指示引用范围应限定为会话)。'INSERT'
(作为对于对象执行操作所需的权限)。
指定引用范围¶
在传递引用的调用持续时间或会话的持续时间内,引用保持有效。创建引用的上下文决定了其范围:
如果在单独一条语句中创建并传递了对存储过程的引用,则该引用的可见性与在存储过程的最外层块中声明的变量相同:
CALL select_from_table(SYSTEM$REFERENCE('TABLE', 'my_table');
如果创建引用并将引用 分配给会话变量,即便取消设置了会话变量,引用在会话期间依然保持有效:
SET tableRef = (SELECT SYSTEM$REFERENCE('TABLE', 'my_table')); SELECT * FROM IDENTIFIER($tableRef);
若要将引用的范围指定为会话的持续时间,而不考虑创建引用的上下文,请将 'SESSION'
作为 SYSTEM$REFERENCE 函数的第三个实参 (session_scope
) 传递。
CALL insert_row(SYSTEM$REFERENCE('TABLE', 'table_with_different_owner', 'SESSION', 'INSERT'));
在引用中授予额外的权限¶
默认情况下,引用根据所引用对象的类型授予权限的某个子集。例如,对表的引用为创建引用的用户的活动角色或辅助角色授予该表的 SELECT 权限。默认权限取决于对象类型。有关支持的对象、权限和默认权限的列表,请参阅 支持的引用对象类型和权限。
若要授予额外权限,请将这些权限指定为 SYSTEM$REFERENCE 函数的附加实参。例如,要授予表的 INSERT、UPDATE 和 TRUNCATE 权限,请执行以下操作:
SELECT SYSTEM$REFERENCE('TABLE', 'table_with_different_owner', 'SESSION', 'INSERT', 'UPDATE', 'TRUNCATE');
请注意,不能指定 OWNERSHIP 或 ALL 作为权限。
创建引用后,对引用创建者的权限所做的更改将体现在与引用关联的权限中。例如,如果为引用创建者撤销了 INSERT 权限,该 INSERT 权限将不再与引用关联。
将对表和视图的引用与掩码策略结合使用¶
使用对于具有掩码策略的表或视图的引用时,引用角色是调用者角色(INVOKER_ROLE 返回的角色),而不论引用是在查询、存储过程还是用户定义的函数中使用。
使用引用并不会更改当前角色(CURRENT_ROLE 返回的角色)。
在存储过程中创建引用¶
如果要编写 所有者权限存储过程,请 不要 在存储过程的正文中创建引用。
在所有者权限存储过程中创建的引用会使用存储过程所有者的角色。引用应使用调用存储过程的用户的角色。对于所有者权限存储过程,应该由调用存储过程的用户创建引用,并将其传递给存储过程。
如果要编写调用者权限存储过程,则可以在存储过程的正文中创建引用。
使用查询引用¶
如果需要传入在存储过程中 SELECT 语句的 FROM 子句中使用的查询,请创建并传入查询引用。
例如,假设存储过程传入一个 SELECT 语句,该语句的目的是在另一个 SELECT 语句的 FROM 子句中使用。在下面的示例中,查询实参应该是 SELECT 语句。此示例使用 Snowflake Scripting 编写:
USE ROLE stored_proc_owner;
CREATE OR REPLACE PROCEDURE get_num_results(query VARCHAR)
RETURNS INTEGER
LANGUAGE SQL
AS
DECLARE
row_count INTEGER DEFAULT 0;
stmt VARCHAR DEFAULT 'SELECT COUNT(*) FROM (' || query || ')';
res RESULTSET DEFAULT (EXECUTE IMMEDIATE :stmt);
cur CURSOR FOR res;
BEGIN
OPEN cur;
FETCH cur INTO row_count;
RETURN row_count;
END;
以下是使用 JavaScript 编写的一个类似示例:
USE ROLE stored_proc_owner;
CREATE OR REPLACE PROCEDURE get_num_results(query VARCHAR)
RETURNS FLOAT
LANGUAGE JAVASCRIPT
AS
$$
let res = snowflake.execute({
sqlText: "SELECT COUNT(*) FROM (" + QUERY + ");",
});
res.next()
return res.getColumnValue(1);
$$;
存储过程会使用所有者的权限。如果存储过程所有者没有在 SELECT 语句中查询表的权限,则对存储过程的调用将失败。
USE ROLE table_owner;
CREATE OR REPLACE TABLE table_with_different_owner (x NUMBER) AS SELECT 42;
CALL get_num_results('SELECT x FROM table_with_different_owner');
002003 (42S02): Uncaught exception of type 'STATEMENT_ERROR' on line 4 at position 29 : SQL compilation error:
Object 'TABLE_WITH_DIFFERENT_OWNER' does not exist or not authorized.
若要使存储过程能够以调用者身份执行查询,请为 SELECT 语句创建查询引用,并传入该引用而非 SELECT 语句。
若要创建引用,请调用 SYSTEM$QUERY_REFERENCE 函数,并传入以下实参:
'SELECT x FROM table_with_different_owner'
(作为查询)。请注意,如果 SELECT 语句包含任何单引号或其他特殊字符(例如换行符),则必须 使用反斜杠对这些字符进行转义。
true
(指示查询引用范围应限定为会话)。
例如:
USE ROLE table_owner;
CALL get_num_results(
SYSTEM$QUERY_REFERENCE('SELECT x FROM table_with_different_owner', true)
);
+-----------------+
| GET_NUM_RESULTS |
|-----------------|
| 1 |
+-----------------+
在存储过程中,可以添加对查询的 FROM 子句的查询引用。例如:
snowflake.execute({
sqlText: "SELECT COUNT(*) FROM (" + QUERY + ");"
});
有关此函数的详细信息,请参阅 SYSTEM$QUERY_REFERENCE。
有关创建和使用查询引用的限制,请参阅 当前限制。
当前限制¶
目前,引用具有以下限制:
GET_DDL 和 SYSTEM$GET_TAG 不支持将引用作为输入实参。
只能创建对表、视图和函数的引用。
在包含引用的查询中,不能使用计划缓存和结果缓存。
对于查询引用:
只能为用作内联视图的 SELECT 语句创建查询引用。
创建查询引用时,不能指定绑定变量或会话变量。
在存储过程中,只能在 SELECT 语句的 FROM 子句中使用查询引用。