将表、视图、函数和查询的引用传递给存储过程

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

  • 允许存储过程对对象执行 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;
$$;
Copy

以下是使用 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);
$$;
Copy

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

USE ROLE table_owner;

CREATE OR REPLACE TABLE table_with_different_owner (x NUMBER) AS SELECT 42;
Copy

如您调用存储过程并传入表的名称,存储过程会失败,因为存储过程的所有者没有足够的权限来访问此表:

USE ROLE table_owner;

CALL insert_row('table_with_different_owner');
Copy
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'));
Copy

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

  • 'TABLE' (表示对象的类型)。

  • 'table_with_different_owner' (表示表的名称)。

  • 'SESSION' (指示引用范围应限定为会话)。

  • 'INSERT' (作为对于对象执行操作所需的权限)。

指定引用范围

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

  • 如果在单独一条语句中创建并传递了对存储过程的引用,则该引用的可见性与在存储过程的最外层块中声明的变量相同:

    CALL select_from_table(SYSTEM$REFERENCE('TABLE', 'my_table');
    
    Copy
  • 如果创建引用并将引用 分配给会话变量,即便取消设置了会话变量,引用在会话期间依然保持有效:

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

若要将引用的范围指定为会话的持续时间,而不考虑创建引用的上下文,请将 'SESSION' 作为 SYSTEM$REFERENCE 函数的第三个实参 (session_scope) 传递。

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

在引用中授予额外的权限

默认情况下,引用根据所引用对象的类型授予权限的某个子集。例如,对表的引用为创建引用的用户的活动角色或辅助角色授予该表的 SELECT 权限。默认权限取决于对象类型。有关支持的对象、权限和默认权限的列表,请参阅 支持的引用对象类型和权限

若要授予额外权限,请将这些权限指定为 SYSTEM$REFERENCE 函数的附加实参。例如,要授予表的 INSERT、UPDATE 和 TRUNCATE 权限,请执行以下操作:

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

请注意,不能指定 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;
Copy

以下是使用 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);
$$;
Copy

存储过程会使用所有者的权限。如果存储过程所有者没有在 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');
Copy
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)
);
Copy
+-----------------+
| GET_NUM_RESULTS |
|-----------------|
|               1 |
+-----------------+

在存储过程中,可以添加对查询的 FROM 子句的查询引用。例如:

snowflake.execute({
  sqlText: "SELECT COUNT(*) FROM (" + QUERY + ");"
});
Copy

有关此函数的详细信息,请参阅 SYSTEM$QUERY_REFERENCE

有关创建和使用查询引用的限制,请参阅 当前限制

当前限制

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

  • GET_DDL 和 SYSTEM$GET_TAG 不支持将引用作为输入实参。

  • 只能创建对表、视图和函数的引用。

  • 在包含引用的查询中,不能使用计划缓存和结果缓存。

  • 对于查询引用:

    • 只能为用作内联视图的 SELECT 语句创建查询引用。

    • 创建查询引用时,不能指定绑定变量或会话变量。

    • 在存储过程中,只能在 SELECT 语句的 FROM 子句中使用查询引用。

语言: 中文