Passing References for Tables, Views, Functions, and Queries to Stored Procedures

In cases in which you call a stored procedure and pass an identifier for a table, view, or function to a stored procedure, you might need to:

  • 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 table, view, or function. A reference is a unique identifier for a table, view, or function. 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 table, view, or function is not fully qualified, the name of the object is resolved by using the current database and schema when the reference was created (i.e. 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 tables, views, and functions, 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.

This topic explains how to create and use references.

A Simple Example

Suppose that an owner’s rights stored procedure is designed to insert rows into a table specified by an input argument. The following is an example written in 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

The following is a similar example written in 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

Suppose that you need to call this procedure for a table that is owned by a different role:

USE ROLE table_owner;

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

If you call the stored procedure and pass in the name of the table, the stored procedure will fail because the owner of the stored procedure does not have sufficient privileges to access the table:

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.

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'));
Copy

The example above passes in the following arguments to the SYSTEM$REFERENCE function:

  • 'TABLE' for the type of the object.

  • 'table_with_different_owner' for the name of the table.

  • 'SESSION' to indicate that the reference should be scoped to the session.

  • 'INSERT' as the privilege needed to perform the action on the object.

Specifying the Scope of the Reference

The reference is valid for either the duration of the call in which the reference is passed or the duration of the session. The context in which the reference is created determines the scope:

  • 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');
    
    Copy
  • 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);
    
    Copy

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'));
Copy

Conferring Additional Privileges in a Reference

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');
Copy

Note that you cannot specify OWNERSHIP or ALL as privileges.

After a reference is created, changes to the privileges of the creator of the reference are reflected in the privileges associated with the reference. For example, if the INSERT privilege is revoked for the creator of a reference, the INSERT privilege is no longer associated with the reference.

Using References to Tables and Views with Masking Policies

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).

Creating References in Stored Procedures

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

A reference created in an owner’s rights stored procedure uses the role of the owner of the stored procedure. References should use the role of the user calling the stored procedure. For an owner’s rights stored procedure, the user calling the stored procedure should create the reference and pass it in to the stored procedure.

If you are writing a caller’s rights stored procedure, you can create a reference within the body of the stored procedure.

Using Query References

If you need to pass in a query that is used in the FROM clause of a SELECT statement in a stored procedure, create and pass in a query reference.

For example, suppose that a stored procedure passes in a SELECT statement that is intended to be used in the FROM clause of another SELECT statement. In the example below, the query argument is intended to be a SELECT statement. This example is in 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

The following is a similar example written in 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

The stored procedure uses owner’s rights. If the stored procedure owner does not have the privileges to query the table in the SELECT statement, the call to the stored procedure fails.

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.

To enable the stored procedure to execute the query as the caller, create a query reference for the SELECT statement, and pass in that reference, rather than the SELECT statement.

To create the reference, call the SYSTEM$QUERY_REFERENCE function, passing in the following arguments:

  • '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.

For example:

USE ROLE table_owner;

CALL get_num_results(
  SYSTEM$QUERY_REFERENCE('SELECT x FROM table_with_different_owner', true)
);
Copy
+-----------------+
| GET_NUM_RESULTS |
|-----------------|
|               1 |
+-----------------+

Within the stored procedure, you can add a query reference to the FROM clause of a query. For example:

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

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

For the limitations with creating and using query references, refer to Current Limitations.

Current Limitations

Currently, references have the following limitations:

  • GET_DDL and SYSTEM$GET_TAG do not support references as input arguments.

  • You can only create references to tables, views, and functions.

  • In queries that contain references, plan cache and result caching are not used.

  • For query references:

    • You can only create query references for SELECT statements that serve as inline views.

    • When you create a query reference, you cannot specify a bind variable or session variable.

    • In your stored procedure, you can only use a query reference in the FROM clause of a SELECT statement.