Uso de tradutores de solicitação e resposta com dados para um serviço remoto

Com tradutores de solicitação e resposta, você pode alterar o formato dos dados enviados e recebidos de serviços remotos utilizados por funções externas.

Neste tópico:

Objetivo

Quando o Snowflake envia dados para um serviço remoto, o Snowflake formata os dados de acordo com estas regras. Da mesma forma, quando o Snowflake recebe dados de um serviço remoto, o Snowflake espera que os dados estejam formatados de acordo com as mesmas regras.

Muitos serviços remotos esperam tratar dados em um formato diferente. Com tradutores de solicitação e resposta, você tem a conveniência de:

  • Converter dados do formato do Snowflake para o formato de entrada nativo do serviço remoto (tradutor de solicitação).

  • Converter dados do formato de saída nativo do serviço remoto para o formato do Snowflake (tradutor de resposta).

Implementação do SQL

Para traduzir dados entre o formato do Snowflake e o formato de entrada nativo do serviço remoto, você usa JavaScript UDFs (funções definidas pelo usuário). Você quase sempre escreve um par de UDFs: uma para traduzir a solicitação e outra para traduzir a resposta.

O Snowflake chama essas funções como parte de cada chamada de função externa. Por exemplo, para uma solicitação a um serviço remoto, o Snowflake chama a função do tradutor de solicitação, passa os dados no formato Snowflake, depois pega os dados retornados e os envia para o serviço remoto. Quando o serviço remoto retorna os dados, o Snowflake chama a função do tradutor de resposta para converter os dados de volta para o formato que o Snowflake entende.

Da perspectiva do usuário, chamar uma função externa quando um tradutor está convertendo é o mesmo que chamar uma função externa sem um tradutor. Depois de especificar os tradutores como parte da instrução CREATE EXTERNAL FUNCTION, eles são chamados automaticamente.

Uma função externa pode ter no máximo um tradutor de solicitação e um tradutor de resposta de cada vez.

As UDFs do tradutor de solicitação e resposta podem ser UDFs seguras.

Atribuição de uma função de tradutor a uma função externa

Para especificar qual função definida pelo usuário usar como tradutor, inclua cláusulas REQUEST_TRANSLATOR e RESPONSE_TRANSLATOR na instrução CREATE EXTERNAL FUNCTION. Cada uma leva o nome da função do tradutor para usar durante a execução.

Por exemplo:

CREATE EXTERNAL FUNCTION f(...)
    RETURNS OBJECT
    ...
    REQUEST_TRANSLATOR = my_request_translator_udf
    RESPONSE_TRANSLATOR = my_response_translator_udf
    ...
    AS <url_of_proxy_and_resource>;
Copy

A sintaxe para especificar tradutores como parte de uma instrução CREATE EXTERNAL FUNCTION é mostrada abaixo:

CREATE EXTERNAL FUNCTION f(...)
    RETURNS OBJECT
    ...
    [ REQUEST_TRANSLATOR = <request_translator_udf_name> ]
    [ RESPONSE_TRANSLATOR = <response_translator_udf_name> ]
    ...
Copy

onde:

request_translator_udf_name

O nome da função do tradutor de solicitação.

response_translator_udf_name

O nome da função do tradutor de resposta.

Cada um dos parâmetros REQUEST_TRANSLATOR e RESPONSE_TRANSLATOR toma um parâmetro do tipo OBJECT.

Você também pode especificar um tradutor de solicitação ou resposta em um comando ALTER FUNCTION. Você pode:

  • Adicionar um tradutor se a função externa ainda não tiver um.

  • Substituir um tradutor existente.

  • Remover um tradutor.

Use a palavra-chave SET para adicionar um novo tradutor ou para substituir um tradutor existente.

Para adicionar ou substituir um tradutor:

ALTER FUNCTION ...
    SET [REQUEST_TRANSLATOR | RESPONSE_TRANSLATOR] = <udf_name>;
Copy

onde

udf_name

O nome de uma JavaScript UDF previamente criada.

Para remover um tradutor:

ALTER FUNCTION ...
    UNSET [REQUEST_TRANSLATOR | RESPONSE_TRANSLATOR];
Copy

Requisitos para o SQL

  • O nome da função do tradutor na instrução CREATE EXTERNAL FUNCTION ou ALTER FUNCTION deve ser uma das seguintes opções:

    • Um nome qualificado (por exemplo, MyDatabase.MySchema.MyJavaScriptUDF).

    • Definido no mesmo banco de dados e esquema que a função externa que o utiliza.

  • Quando o tradutor é especificado em uma instrução CREATE EXTERNAL FUNCTION ou ALTER FUNCTION, a UDF do tradutor já deve existir. Você não pode especificar o nome primeiro e criar a UDF depois, mesmo se você não chamar a função externa antes de criar a UDF.

  • Uma UDF usada como tradutor não deve ser descartada sem antes removê-la de todas as funções externas que a utilizam. (No momento em que a função externa é chamada, o Snowflake apresenta falha com um erro se o tradutor não existir).

  • Se a UDF do tradutor for modificada (via ALTER FUNCTION), ela deve manter os mesmos requisitos de interface. Se ela não mantiver os requisitos de interface, uma exceção é apresentada antes de executar a função externa.

Implementação do JavaScript

No momento da execução, o SQL passa um OBJECT para a UDF do tradutor. O código JavaScript recebe isso como um objeto JavaScript.

Implementação de um tradutor de solicitação

Propriedades de entrada do tradutor de solicitação

Uma UDF de tradutor recebe um objeto JavaScript chamado event. O objeto contém as seguintes propriedades:

  • body: o formato do campo data é o mesmo que o do lote do conjunto de linhas do Snowflake existente (ou seja, uma matriz de linhas).

    Por exemplo,

    {
      "body": {
              "data": [
                        [0,"cat"],
                        [1,"dog"]
                      ]
              }
    }
    
    Copy

    Os dados existentes estão aninhados sob o corpo externo.

  • serviceUrl: a URL definida da função externa a chamar.

  • contextHeaders: um objeto que contém todos os cabeçalhos relacionados ao contexto, em que os nomes são os nomes dos campos. Por exemplo, o objeto pode conter o nome de campo “SF_CONTEXT_CURRENT_DATABASE”, e o valor correspondente será uma cadeia de caracteres contendo o nome atual do banco de dados.

Propriedades de saída do tradutor de solicitação

O tradutor de solicitação retorna um objeto com campos utilizados para se comunicar com o gateway de API do serviço externo. Esse objeto tem três campos opcionais:

  • body: define o corpo real a ser passado para o serviço. Se isso não for definido, não há corpo. O valor body deve ser uma cadeia de caracteres ou um objeto JSON no formato esperado pelo serviço remoto. Se o valor for uma cadeia de caracteres, essa cadeia pode conter estrutura interna (por exemplo, ser compatível com JSON). Se o valor for um objeto JSON, esse objeto é convertido em uma cadeia de caracteres para que possa ser incluído como parte da cadeia de comando HTTP POST.

  • urlSuffix: define o sufixo da URL de serviço, que é adicionado ao final do valor serviceUrl. Esse sufixo também pode conter parâmetros de consulta. Os nomes e valores dos parâmetros devem ser codificados de URL. Por exemplo, se você quiser definir um parâmetro chamado a para o valor my param, você precisa fazer a codificação de URL do caractere de espaço, então o parâmetro seria ?a=my%20param.

  • translatorData: passados do tradutor de solicitação para o tradutor de resposta. Esse campo pode passar informações de contexto, como o corpo de entrada, a URL de serviço ou o sufixo, ou cabeçalhos de contexto.

Todos os três campos são opcionais. Entretanto, como questão prática, a maioria dos tradutores de solicitação retornam pelo menos os dados do corpo.

Implementação de um tradutor de resposta

Propriedades de entrada do tradutor de resposta

O parâmetro de entrada para a função do tradutor de resposta é um objeto. O exemplo abaixo usa EVENT, que contém duas propriedades:

  • body: a resposta a ser decodificada a partir da resposta do serviço externo.

  • translatorData: se este campo for retornado pelo tradutor de solicitação, o Snowflake o passa para o tradutor de resposta.

Propriedades de saída do tradutor de resposta

A resposta do tradutor de resposta é retornada como um objeto sob o elemento body; o formato é o formato da função externa existente (matriz de linhas). Por exemplo:

{
  "body": {
          "data": [
                    [0, "Life"],
                    [1, "the universe"],
                    [2, "and everything"]
                  ]
           }
}
Copy

Requisitos para a função de tradutor

Cada UDF de tradutor deve atender aos seguintes requisitos:

  • Deve ser uma JavaScript UDF.

  • Deve tomar exatamente um parâmetro do tipo OBJECT, que representa um lote de linhas.

  • Deve retornar um valor do tipo OBJECT, que também representa um lote de linhas.

  • Deve ser uma UDF escalar (retornando uma linha para cada linha (OBJECT) passada).

    Nota

    Embora o tradutor seja escalar, o OBJECT passado para o tradutor pode ter (e normalmente tem) várias linhas embutidas dentro do JSON no OBJECT.

  • O número e a ordem das linhas (dentro do OBJECT) retornadas pela UDF do tradutor de resposta devem ser os mesmos que o número e a ordem das linhas passadas para a UDF do tradutor de solicitação (dentro do OBJECT).

Exemplo de tradutor de solicitação e tradutor de resposta

O exemplo a seguir mostra um tradutor de solicitação e um tradutor de resposta sendo usados para converter dados no formato exigido por um serviço externo que faz análise de sentimento, Amazon Comprehend BatchDetectSentiment. O tradutor de solicitação cria a solicitação HTTP para corresponder ao formato que o serviço de back-end espera.

Para utilizar tradutores, você precisará de um gateway de API. Este exemplo usa um gateway de API que já está configurado para se comunicar com o serviço de análise de sentimento. Para obter mais informações sobre como fazer a integração com um serviço da Amazon Web Services (AWS) como back-end, consulte Configuração de uma solicitação de integração de API usando o console do API Gateway na documentação da AWS.

É importante ter a integração de API funcionando corretamente antes de adicionar tradutores.

Configuração

Configure um banco de dados para armazenar dados de demonstração.

Escolha uma função que tenha permissão para criar funções externas:

USE ROLE ACCOUNTADMIN;
Copy

Especifique qual warehouse, banco de dados e esquema usar:

USE WAREHOUSE w;
USE DATABASE a;
USE SCHEMA b;
Copy

Crie uma tabela para armazenar suas frases de teste:

CREATE TABLE demo(vc varchar);
INSERT INTO demo VALUES('Today is a good day'),('I am feeling mopey');
Copy

Solicitação do corpo antes da tradução

Esta função externa não tem um tradutor de solicitação ou de resposta:

CREATE OR REPLACE EXTERNAL FUNCTION ComprehendSentiment(thought varchar)
RETURNS VARIANT
API_INTEGRATION = aws_comprehend_gateway
AS 'https://<MY_GATEWAY>.execute-api.us-east-1.amazonaws.com/test/comprehend_proxy';
Copy

Você pode chamar a função externa com seus dados de teste a partir da tabela de demonstração:

SELECT ComprehendSentiment(vc), vc FROM demo;
Copy

O corpo de solicitação gerado usa o formato de dados da função externa do Snowflake:

{"body":{"data:" [[0, "Today is a good day"],[1,"I am feeling mopey"]]}}
Copy

Entretanto, o serviço de análise de sentimento externo espera um formato diferente que especifique o idioma e uma matriz de cadeias de caracteres:

{"body": { Language: "en", TextList: [ "Today is a good day", "I am feeling mopey"]}}
Copy

A próxima seção descreve como você pode adicionar um tradutor de solicitação para mudar o corpo de solicitação para o formato exigido.

Converter o corpo de solicitação

Ao usar um tradutor de solicitação, você pode converter a entrada padrão descrita acima (no formato de dados do Snowflake) para o formato que o serviço externo exige.

O seguinte SQL cria uma função de tradutor awscomprehendrequest_translator.

CREATE OR REPLACE FUNCTION AWSComprehendrequest_translator(EVENT OBJECT)
RETURNS OBJECT
LANGUAGE JAVASCRIPT AS
'
var textlist = []
for(i = 0; i < EVENT.body.data.length; i++) {
   let row = EVENT.body.data[i];
   // row[0] is the row number and row[1] is the input text.
   textlist.push(row[1]); //put text into the textlist
}
// create the request for the service. Also pass the input request as part of the output.
return { "body": { "LanguageCode": "en", "TextList" : textlist }, "translatorData": EVENT.body }
';
Copy

Na função do tradutor de solicitação, o código:

  • Executa loops por cada uma das linhas de entrada. Para cada linha, adiciona a cadeia de caracteres, que está em row[1], à matriz textlist. O valor em row[0] é o número da linha e pode ser ignorado.

  • Retorna um corpo JSON que possui o código do idioma e a lista de texto que corresponde aos requisitos do serviço externo.

  • Retorna os dados pelo campo translatorData. Isso é usado pelo tradutor de resposta. Neste exemplo, você está enviando os dados de entrada originais. Você usará o comprimento dos dados de entrada no tradutor de resposta para saber quantas solicitações de entrada foram feitas.

Você pode testar o tradutor de solicitação chamando-o diretamente.

SELECT AWSComprehendrequest_translator(parse_json('{"body":{"data": [[0, "I am so happy we got a sunny day for my birthday."], [1, "$$$$$."], [2, "Today is my last day in the old house."]]}}'));
Copy

O tradutor de solicitação coloca o corpo na forma esperada pelo serviço externo.

{"body":{
   "LanguageCode": "en",
   "TextList": [
      "I am so happy we got a sunny day for my birthday.",
      "$$$$$.",
      "Today is my last day in the old house."
               ]
         },
   "translatorData": {
      "data": [[0, "I am so happy we got a sunny day for my birthday."],
               [1, "$$$$$."],
               [2, "Today is my last day in the old house."]]
                     }
}
Copy

Corpo de resposta antes de adicionar um tradutor de resposta

Um corpo de resposta do serviço externo é semelhante a isto.

{"body":{
   "ErrorList": [ { "ErrorCode": 57, "ErrorMessage": "Language unknown", "Index": 1} ],
   "ResultList":[ { "Index": 0, "Sentiment": "POSITIVE",
                    "SentimentScore": { "Mixed": 25, "Negative": 5, "Neutral": 1, "Positive": 90 }},
                  { "Index": 2, "Sentiment": "NEGATIVE",
                    "SentimentScore": { "Mixed": 25, "Negative": 75, "Neutral": 30, "Positive": 20 }}
                ]
         }
}
Copy

Conversão do corpo de resposta

O tradutor de resposta processa os resultados que você recebe de volta do serviço externo. Os resultados contêm uma combinação de erros na ErrorList e resulta na ResultList.

O código do tradutor de resposta combina esses resultados para fazer um conjunto completo que corresponde à ordem das linhas que foram passadas para o serviço externo. O tradutor de resposta retorna os resultados no formato do Snowflake.

O seguinte SQL cria uma função de tradutor awscomprehendresponse_translator.

CREATE OR REPLACE FUNCTION AWSComprehendresponse_translator(EVENT OBJECT)
RETURNS OBJECT
LANGUAGE JAVASCRIPT AS
'
// Combine the scored results and the errors into a single list.
var responses = new Array(EVENT.translatorData.data.length);
// output format: array of {
// "Sentiment": (POSITIVE, NEUTRAL, MIXED, NEGATIVE, or ERROR),
// "SentimentScore": <score>, "ErrorMessage": ErrorMessage }.
// If error, set ErrorMessage; otherwise, set SentimentScore.
// Insert good results into proper position.
for(i = 0; i < EVENT.body.ResultList.length; i++) {
   let row = EVENT.body.ResultList[i];
   let result = [row.Index, {"Sentiment": row.Sentiment, "SentimentScore": row.SentimentScore}]
   responses[row.Index] = result
}
// Insert errors.
for(i = 0; i < EVENT.body.ErrorList.length; i++) {
   let row = EVENT.body.ErrorList[i];
   let result = [row.Index, {"Sentiment": "Error", "ErrorMessage": row.ErrorMessage}]
   responses[row.Index] = result
}
return { "body": { "data" : responses } };
';
Copy

Na função do tradutor de resposta, o código:

  • Inicializa uma matriz chamada responses com o tamanho da entrada a partir do comprimento da matriz translatorData. Você enviou translatorData do tradutor de solicitação para o tradutor de resposta para passar a lista original das cadeias de caracteres de teste.

  • Executa loops por cada um dos resultados sem erro e os coloca na lista de resultados.

  • Executa loops pelos resultados de erros e os coloca na lista de resultados. A lista de resultados tem uma posição de índice que informa cada registro. A ordem dos resultados produzidos deve corresponder à ordem de entrada. A lista de resultados também contém as informações sobre sentimentos.

Após todas as respostas terem sido coletadas, elas são retornadas em um corpo JSON no formato esperado pelo Snowflake.

O seguinte teste direto retornará um corpo JSON com o formato correto.

SELECT AWSComprehendresponse_translator(
    parse_json('{
        "translatorData": {
            "data": [[0, "I am so happy we got a sunny day for my birthday."],
                    [1, "$$$$$."],
                    [2, "Today is my last day in the old house."]]
                          }
        "body": {
            "ErrorList":  [ { "ErrorCode": 57,  "ErrorMessage": "Language unknown",  "Index": 1 } ],
            "ResultList": [
                            { "Index": 0,  "Sentiment": "POSITIVE",
                              "SentimentScore": { "Mixed": 25,  "Negative": 5,  "Neutral": 1,  "Positive": 90 }
                            },
                            { "Index": 2, "Sentiment": "NEGATIVE",
                              "SentimentScore": { "Mixed": 25,  "Negative": 75,  "Neutral": 30,  "Positive": 20 }
                            }
                          ]
            },
        }'
    )
);
Copy

Atribuição de tradutores à função externa

À função externa, adicione as funções de tradutor de solicitação e resposta, atribuindo os nomes das funções como valores aos parâmetros request_translator e response_translator. Dessa forma, eles serão chamados automaticamente quando a função externa for executada.

CREATE OR REPLACE EXTERNAL FUNCTION ComprehendSentiment(thought varchar)
RETURNS VARIANT
API_INTEGRATION = aws_comprehend_gateway
request_translator = db_name.schema_name.AWSComprehendrequest_translator
response_translator = db_name.schema_name.AWSComprehendresponse_translator
AS 'https://<MY_GATEWAY>.execute-api.us-east-1.amazonaws.com/test/comprehend_proxy';
Copy

Você pode descrever a função para obter informações sobre ela.

DESCRIBE FUNCTION ComprehendSentiment(VARCHAR);
Copy

Chamada da função externa

Teste a função externa chamando-a com uma única frase.

SELECT ComprehendSentiment('Today is a good day');
Copy

Você vê os resultados da análise de sentimento.

{"Sentiment": "POSITIVE",
 "SentimentScore":{"Mixed":0.002436627633869648,
                   "Negative":0.0014803812373429537,
                   "Neutral":0.015923455357551575,
                   "Positive": 0.9801595211029053}}
Copy

Teste a função externa chamando-a com múltiplas frases. Use a mesma tabela demo que você criou anteriormente.

SELECT ComprehendSentiment(vc), vc FROM demo;
Copy

Os resultados da análise de sentimento são exibidos.

A table showing the sentiment analysis results.

Quando a função externa foi chamada, o tradutor de solicitação converteu automaticamente os dados no formato exigido pelo serviço externo. Então, o tradutor de resposta converteu automaticamente a resposta do serviço externo no formato exigido pelo Snowflake.

Dicas para testar tradutores de solicitação e resposta

  • Os valores dos casos de teste são geralmente valores de OBJECT (coleções de pares chave-valor). Estes devem ser formatados para atender aos requisitos destas regras.

  • Você pode começar a testar seu tradutor de solicitação ou tradutor de resposta passando um exemplo de entrada convertido em uma cadeia de caracteres. Por exemplo:

    select my_request_translator_function(parse_json('{"body": {"data": [ [0,"cat",867], [1,"dog",5309] ] } }'));
    
    Copy

    (A entrada para PARSE_JSON() deve ser uma cadeia de caracteres formatada com JSON).

  • Teste com valores NULL se apropriado.

    • Inclua pelo menos um valor SQL NULL em seus casos de teste.

    • Inclua pelo menos um valor JSON NULL em seus casos de teste.

  • A tradução de uma solicitação e a tradução de uma resposta são processos muitas vezes inversos. Conceitualmente:

    my_response_translator_udf(my_request_translator_udf(x)) = x
    
    Copy

    Você pode usar essa característica para ajudar a testar seu tradutor de solicitação e tradutor de resposta se os formatos dos dados corresponderem. Crie uma tabela com bons valores de teste, depois execute um comando semelhante a:

    SELECT test_case_column
        FROM test_table
        WHERE my_response_translator_udf(my_request_translator_udf(x)) != x;
    
    Copy

    A consulta não deve retornar nenhuma linha.

    Note que traduzir uma solicitação e traduzir uma resposta nem sempre são processos exatamente inversos. Para um exemplo de situações em que eles podem não ser inversos, consulte a discussão de funções inversas na seção “Notas de uso” da documentação da função TO_JSON().