Como passar referências de tabelas, exibições, funções e consultas para procedimentos armazenados

Nos casos em que você chama um procedimento armazenado e passa um identificador para uma tabela, exibição ou função para um procedimento armazenado, pode ser necessário:

  • Permitir que o procedimento armazenado execute ações SQL no objeto, mesmo que o procedimento armazenado use os direitos do proprietário.

  • Permitir que o procedimento armazenado resolva o nome totalmente qualificado do objeto, se o identificador não for qualificado ou for parcialmente qualificado.

Nesses casos, você pode criar e passar uma referência à tabela, exibição ou função. A referência é um identificador exclusivo para uma tabela, exibição ou função. Dentro do procedimento armazenado, quando você executa ações SQL em uma referência a um objeto, as ações são executadas usando a função ativa ou as funções secundárias do usuário que criou a referência. Além disso, se a tabela, exibição ou função não for totalmente qualificada, o nome do objeto será resolvido usando o banco de dados e o esquema atuais quando a referência foi criada (ou seja, o banco de dados e o esquema do usuário que criou a referência).

Da mesma forma, se você precisar passar uma consulta para um procedimento armazenado e usar essa consulta na cláusula FROM de uma instrução SELECT, você pode criar e passar uma referência de consulta. Dentro do procedimento armazenado, a consulta é executada usando a função ativa ou as funções secundárias do usuário que criou a referência de consulta. Como no caso das referências a tabelas, exibições e funções, se o nome do objeto na consulta não for totalmente qualificado, o nome do objeto será resolvido usando o banco de dados e o esquema que estavam em uso quando a referência da consulta foi criada.

Este tópico explica como criar e usar referências.

Um exemplo simples

Suponha que um procedimento armazenado de direitos do proprietário seja projetado para inserir linhas em uma tabela especificada por um argumento de entrada. Veja a seguir um exemplo escrito em Script Snowflake:

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

O seguinte é um exemplo semelhante escrito em 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

Suponha que você precise chamar este procedimento para uma tabela que pertence a uma função diferente:

USE ROLE table_owner;

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

Se você chamar o procedimento armazenado e passar o nome da tabela, o procedimento armazenado falhará porque o proprietário do procedimento armazenado não possui privilégios suficientes para acessar a tabela:

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.

Para permitir que o procedimento armazenado execute ações SQL na tabela como o chamador, crie uma referência à tabela e passe essa referência, em vez do nome da tabela.

Para criar a referência, chame a função SYSTEM$REFERENCE. Por exemplo:

USE ROLE table_owner;

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

O exemplo acima passa os seguintes argumentos para a função SYSTEM$REFERENCE:

  • 'TABLE' para o tipo do objeto.

  • 'table_with_different_owner' para o nome da tabela.

  • 'SESSION' para indicar que a referência deve ter como escopo a sessão.

  • 'INSERT' como o privilégio necessário para executar a ação no objeto.

Especificação do escopo da referência

A referência é válida tanto pela duração da chamada na qual a referência é passada quanto pela duração da sessão. O contexto em que a referência é criada determina o escopo:

  • Se você criar e passar uma referência a um procedimento armazenado em uma única instrução, a referência terá a mesma visibilidade de uma variável declarada no bloco mais externo do procedimento armazenado:

    CALL select_from_table(SYSTEM$REFERENCE('TABLE', 'my_table');
    
    Copy
  • Se você criar uma referência e atribuir a referência a uma variável de sessão, a referência será válida durante a sessão, mesmo que você desmarque a variável de sessão:

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

Para especificar que o escopo da referência deve ser a duração da sessão, independentemente do contexto em que a referência é criada, passe 'SESSION' para o terceiro argumento (session_scope) da função SYSTEM$REFERENCE:

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

Atribuição de privilégios adicionais em uma referência

Por padrão, uma referência atribui um subconjunto de privilégios, com base no tipo de objeto referenciado. Por exemplo, uma referência a uma tabela atribui o privilégio SELECT nessa tabela para a função ativa ou função secundária do usuário que criou a referência. Os privilégios padrão dependem do tipo de objeto. Para obter a lista de objetos, privilégios e privilégios padrão suportados, consulte Tipos de objetos e privilégios suportados para referências.

Para atribuir privilégios adicionais, especifique esses privilégios como argumentos adicionais para a função SYSTEM$REFERENCE. Por exemplo, para atribuir os privilégios INSERT, UPDATE e TRUNCATE em uma tabela:

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

Note que você não pode especificar OWNERSHIP ou ALL como privilégios.

Depois que uma referência é criada, as alterações nos privilégios do criador da referência são refletidas nos privilégios associados à referência. Por exemplo, se o privilégio INSERT for revogado para o criador de uma referência, o privilégio INSERT não estará mais associado à referência.

Uso de referências a tabelas e exibições com políticas de mascaramento

Quando você usa uma referência a uma tabela ou exibição que possui uma política de mascaramento, a função de referência é a função invocadora (a função retornada por INVOKER_ROLE), independentemente de a referência ser usada em uma consulta, procedimento armazenado ou função definida pelo usuário.

O uso de uma referência não altera a função atual (a função retornada por CURRENT_ROLE).

Criação de referências em procedimentos armazenados

Se você estiver escrevendo um procedimento armazenado de direitos do proprietário, não crie uma referência dentro do corpo do procedimento armazenado.

Uma referência criada em um procedimento armazenado de direitos do proprietário usa a função do proprietário do procedimento armazenado. As referências devem usar a função do usuário que chama o procedimento armazenado. Para um procedimento armazenado de direitos do proprietário, o usuário que chama o procedimento armazenado deve criar a referência e passá-la para o procedimento armazenado.

Se você estiver escrevendo um procedimento armazenado de direitos do chamador, poderá criar uma referência no corpo do procedimento armazenado.

Uso de referências de consulta

Se você precisar passar uma consulta que é usada na cláusula FROM de uma instrução SELECT em um procedimento armazenado, crie e transmita uma referência de consulta.

Por exemplo, suponha que um procedimento armazenado passe uma declaração SELECT que se destina a ser usada na cláusula FROM de outra declaração SELECT. No exemplo abaixo, o argumento de consulta destina-se a ser uma declaração SELECT. Este exemplo está no Script Snowflake:

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

O seguinte é um exemplo semelhante escrito em 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

O procedimento armazenado usa os direitos do proprietário. Se o proprietário do procedimento armazenado não tiver privilégios para consultar a tabela na instrução SELECT, a chamada para o procedimento armazenado falhará.

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.

Para permitir que o procedimento armazenado execute a consulta como o chamador, crie uma referência de consulta para a declaração SELECT, e passe essa referência, em vez da declaração SELECT.

Para criar a referência, chame a função SYSTEM$QUERY_REFERENCE, passando os seguintes argumentos:

  • 'SELECT x FROM table_with_different_owner' como a consulta.

    Note que se a instrução SELECT tiver aspas simples ou outros caracteres especiais (por exemplo, novas linhas), você deverá fazer o escape desses caracteres com barras invertidas.

  • true para indicar que a referência de consulta deve ter como escopo a sessão.

Por exemplo:

USE ROLE table_owner;

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

Dentro do procedimento armazenado, você pode adicionar uma referência de consulta à cláusula FROM de uma consulta. Por exemplo:

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

Para obter detalhes sobre esta função, consulte SYSTEM$QUERY_REFERENCE.

Para as limitações de criação e uso de referências de consulta, consulte Limitações atuais.

Limitações atuais

Atualmente, as referências têm as seguintes limitações:

  • GET_DDL e SYSTEM$GET_TAG não oferecem suporte às referências como argumentos de entrada.

  • Você só pode criar referências a tabelas, exibições e funções.

  • Nas consultas que contêm referências, o cache plano e o cache de resultado não são usados.

  • Para referências de consulta:

    • Você só pode criar referências de consulta para instruções SELECT que servem como exibições inline.

    • Ao criar uma referência de consulta, você não pode especificar uma variável de vinculação ou variável de sessão.

    • Em seu procedimento armazenado, você só pode usar uma referência de consulta na cláusula FROM de uma instrução SELECT.