将对象和查询的引用传递给存储过程

如果调用存储过程,并将表、视图、函数或过程的标识符传递给存储过程,您可能需要:

  • Allow the stored procedure to perform SQL actions on the object, even if the stored procedure uses owner’s rights.
  • Allow the stored procedure to resolve the fully qualified name of the object, if the identifier is not qualified or is partially qualified.

In these cases, you can create and pass in a reference to the object (for example, the table, view, function, or procedure). A reference is a unique identifier for an object. Within the stored procedure, when you execute SQL actions on a reference to an object, the actions are performed using the active role or secondary roles of the user who created the reference. In addition, if the object identifier is not fully qualified, the name of the object is resolved by using the current database and schema when the reference was created (in other words, the database and schema of the user who created the reference).

Similarly, if you need to pass in a query to a stored procedure and use that query in the FROM clause of a SELECT statement, you can create and pass in a query reference. Within the stored procedure, the query is performed using the active role or secondary roles of the user who created the query reference. As is the case with references to objects, if the object name in the query is not fully qualified, the name of the object is resolved by using the database and schema that were in use when the query reference was created.

本主题介绍如何创建和使用引用。

背景:与向存储过程传递对象和查询有关的问题

假设某个所有者权限存储过程旨在将行插入到一个表中,该表通过输入实参指定。以下是用 Snowflake Scripting 和 JavaScript 编写的示例:

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;
$$;

假设您需要为另外一个角色拥有的表调用此过程:

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.

To enable the stored procedure to perform SQL actions on the table as the caller, create a reference to the table and pass in that reference, rather than the table name.

创建引用

To create the reference, call the SYSTEM$REFERENCE function. For example:

USE ROLE table_owner;

CALL insert_row(SYSTEM$REFERENCE('TABLE', 'table_with_different_owner', 'SESSION', 'INSERT'));

上面的示例将以下实参传递给 SYSTEM$REFERENCE 函数:

Note

If you need to create a reference to an object that you don’t plan to modify (for example, if you are passing in a table that the stored procedure will query) and you want that reference to be valid for the scope of the call (rather than for the entire session), you can use the TABLE keyword instead of calling SYSTEM$REFERENCE. For details, see 使用 TABLE 关键字创建对表、视图或查询的引用.

指定引用范围

在传递引用的调用持续时间或会话的持续时间内,引用保持有效。创建引用的上下文决定了其范围:

  • If you create and pass a reference to a stored procedure in a single statement, the reference has the same visibility as a variable declared in the outermost block of the stored procedure:

    CALL select_from_table(SYSTEM$REFERENCE('TABLE', 'my_table');
  • If you create a reference and assign the reference to a session variable, the reference is valid for the duration of the session, even if you unset the session variable:

    SET tableRef = (SELECT SYSTEM$REFERENCE('TABLE', 'my_table'));
    
    SELECT * FROM IDENTIFIER($tableRef);

To specify that the scope of the reference should be the duration of the session, regardless of the context in which the reference is created, pass 'SESSION' for the third argument (session_scope) of the SYSTEM$REFERENCE function:

CALL insert_row(SYSTEM$REFERENCE('TABLE', 'table_with_different_owner', 'SESSION', 'INSERT'));

在引用中授予额外的权限

By default, a reference confers a subset of privileges, based on the type of the object being referenced. For example, a reference to a table confers the SELECT privilege on that table for the active role or secondary role of the user who created the reference. The default privileges depend on the object type. For the list of supported objects, privileges, and default privileges, see Supported object types and privileges for references.

To confer additional privileges, specify those privileges as additional arguments to the SYSTEM$REFERENCE function. For example, to confer the INSERT, UPDATE, and TRUNCATE privileges on a table:

SELECT SYSTEM$REFERENCE('TABLE', 'table_with_different_owner', 'SESSION', 'INSERT', 'UPDATE', 'TRUNCATE');

请注意,不能指定 OWNERSHIP 或 ALL 作为权限。

创建引用后,对引用创建者的权限所做的更改将体现在与引用关联的权限中。例如,如果为引用创建者撤销了 INSERT 权限,该 INSERT 权限将不再与引用关联。

将对表和视图的引用与掩码策略结合使用

When you use a reference to a table or view that has a masking policy, the reference role is the invoker role (the role returned by INVOKER_ROLE), regardless of whether the reference is used in a query, stored procedure, or user-defined function.

Using a reference does not change the current role (the role returned by CURRENT_ROLE).

在存储过程中创建引用

If you are writing an owner’s rights stored procedure, do not create a reference within the body of the stored procedure.

在所有者权限存储过程中创建的引用会使用存储过程所有者的角色。引用应使用调用存储过程的用户的角色。对于所有者权限存储过程,应该由调用存储过程的用户创建引用,并将其传递给存储过程。

如果要编写调用者权限存储过程,则可以在存储过程的正文中创建引用。

使用查询引用

如果需要传入在存储过程中 SELECT 语句的 FROM 子句中使用的查询,请创建并传入查询引用。

例如,假设存储过程传入一个 SELECT 语句,该语句的目的是在另一个 SELECT 语句的 FROM 子句中使用。在下面的示例中,查询实参应该是 SELECT 语句。此示例使用 Snowflake Scripting 和 JavaScript:

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;

存储过程会使用所有者的权限。如果存储过程所有者没有在 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 语句。

To create the query reference, you can call the SYSTEM$QUERY_REFERENCE function.

Note

If you need to create a query reference that is valid for the scope of the call (rather than for the entire session), you can use the TABLE keyword instead of calling SYSTEM$QUERY_REFERENCE. For details, see 使用 TABLE 关键字创建对表、视图或查询的引用.

如果调用 SYSTEM$QUERY_REFERENCE 函数,请传入:

  • 'SELECT x FROM table_with_different_owner' as the query.

    Note that if the SELECT statement contains any single quotes or other special characters (e.g. newlines), you must escape those characters with backslashes.

  • true to indicate that the query reference should be scoped to the session.

例如:

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 + ");"
});

For details on this function, refer to SYSTEM$QUERY_REFERENCE.

For the limitations with creating and using query references, refer to 当前限制.

使用 TABLE 关键字创建对表、视图或查询的引用

如果您需要创建对您没有修改且存储过程应查询的表、视图或安全视图的引用,并且希望该引用对调用范围有效(而不是对整个会话有效),请使用采用以下语法的 TABLE 关键字:

TABLE( [[<database_name>.]<schema_name>.]<object_name> )
TABLE("<object_name_that_requires_double_quotes>")
TABLE(IDENTIFIER('string_literal_for_object_name'))

TABLE 关键字为调用表或视图的 SYSTEM$REFERENCE 函数提供了更简单的语法,无需指定对象类型的实参。使用 TABLE 关键字时,引用只授予 SELECT 权限,引用的作用范围是调用(而不是会话)。

The following examples call the stored procedure my_procedure and pass in references to tables and views:

CALL my_procedure(TABLE(my_table));
CALL my_procedure(TABLE(my_database.my_schema.my_view));
CALL my_procedure(TABLE("My Table Name"));
CALL my_procedure(TABLE(IDENTIFIER('my_view')));

Note

您不能将 TABLE 关键字用于函数或存储过程的名称。

如果要创建对查询的引用,且该引用只需要对调用范围有效(而不是对整个会话有效),可以使用 TABLE 关键字作为调用 SYSTEM$QUERY_REFERENCE 函数的替代方法。要使用 TABLE 关键字,请使用以下语法:

TABLE(<select_statement>)

例如:

CALL my_procedure(TABLE(SELECT * FROM my_view));
CALL my_procedure(TABLE(WITH c(s) as (SELECT $1 FROM VALUES (1), (2)) SELECT a, count(*) FROM T, C WHERE s = a GROUP BY a));

请注意以下事项:

  • 不得在对象名称或查询中使用绑定变量。
  • The reference created by the TABLE keyword is valid for the duration of the call. You cannot specify a different scope for the reference.
  • The reference has the default privileges conferred for the type of object.

当前限制

目前,引用具有以下限制:

  • GET_DDL and SYSTEM$GET_TAG do not support references as input arguments.
  • 您只能创建对表、视图、函数和过程的引用。
  • 在包含引用的查询中,不能使用计划缓存和结果缓存。
  • 对于查询引用:
    • 只能为用作内联视图的 SELECT 语句创建查询引用。
    • 创建查询引用时,不能指定绑定变量或会话变量。
    • 在存储过程中,只能在 SELECT 语句的 FROM 子句中使用查询引用。