リモートサービスに対するデータのあるリクエストトランスレーターおよび応答トランスレーターの使用

リクエストトランスレーターと応答トランスレーターにより、外部関数が使用するリモートサービスとの間で送受信されるデータの形式を変更できます。

このトピックの内容:

目的

Snowflakeがリモートサービスにデータを送信すると、Snowflakeは これらのルール に従ってデータをフォーマットします。同様に、Snowflakeがリモートサービスからデータを受信すると、Snowflakeはデータが同じルールに従ってフォーマットされることを予測します。

多くのリモートサービスでは、異なる形式でデータを処理することが予測されます。リクエストトランスレーターと応答トランスレーターを使用すると、次の処理が簡単になります。

  • Snowflakeの形式からリモートサービスのネイティブ入力形式(リクエストトランスレーター)にデータを変換。

  • リモートサービスのネイティブ出力形式からSnowflakeの形式(応答トランスレーター)にデータを変換。

SQL 実装

Snowflakeの形式とリモートサービスのネイティブ入力形式の間におけるデータの変換には、 JavaScript UDFs (ユーザー定義関数)を使用します。ほとんどの場合で UDFs をペアで記述します。1つはリクエストを変換し、もう1つは応答を変換します。

Snowflakeは、各外部関数呼び出しの一部としてこれらの関数を呼び出します。たとえば、リモートサービスのリクエストの場合、Snowflakeは、リクエストトランスレーター関数を呼び出してSnowflake形式のデータを渡し、返されたデータを取得してリモートサービスに送信します。リモートサービスがデータを返すと、Snowflakeは応答トランスレーター関数を呼び出して、データをSnowflakeが理解できる形式に変換し直します。

ユーザーの観点からは、トランスレーターが変換しているときに外部関数を呼び出すことは、トランスレーターなしで外部関数を呼び出すことと同じです。 CREATE EXTERNAL FUNCTION ステートメントの一部としてトランスレーターを指定すると、それらは自動的に呼び出されます。

外部関数は、一度に最大1つのリクエストトランスレーターと1つの応答トランスレーターを持つことができます。

リクエストトランスレーターと応答トランスレーターの UDFs は、 セキュア UDFs にすることができます。

トランスレーター関数の外部関数への割り当て

トランスレーターとして使用するユーザー定義関数を指定するには、 CREATE EXTERNAL FUNCTION ステートメントに REQUEST_TRANSLATOR 句と RESPONSE_TRANSLATOR 句を含めます。それぞれが、実行時に使用するトランスレーター関数の名前を取得します。

例:

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

CREATE EXTERNAL FUNCTION ステートメントの一部としてトランスレーターを指定するための構文を以下に示します。

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

条件:

request_translator_udf_name

リクエストトランスレーター関数の名前。

response_translator_udf_name

応答トランスレーター関数の名前。

REQUEST_TRANSLATOR および RESPONSE_TRANSLATOR パラメーターは、それぞれが型 OBJECT の1つのパラメーターを取ります。

ALTER FUNCTION コマンドでリクエストトランスレーターまたは応答トランスレーターを指定することもできます。できること:

  • 外部関数にトランスレーターがない場合に、トランスレーターを追加します。

  • 既存のトランスレーターを置換します。

  • トランスレーターを削除します。

SET キーワードを使用して、新しいトランスレーターを追加するか、既存のトランスレーターを置換します。

トランスレーターを追加または置換するには、

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

条件

udf_name

以前に作成した JavaScript UDF の名前。

トランスレーターを削除するには、

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

SQL の要件

  • CREATE EXTERNAL FUNCTION または ALTER FUNCTION ステートメントのトランスレーター関数の名前は次のいずれかである必要があります。

    • 修飾名(例: MyDatabase.MySchema.MyJavaScriptUDF)。

    • それらを使用する外部関数と同じデータベースおよびスキーマで定義されます。

  • トランスレーターが CREATE EXTERNAL FUNCTION または ALTER FUNCTION ステートメントで指定されている場合、トランスレーター UDF はすでに存在している必要があります。(UDF を作成する前に外部関数を呼び出さない場合でも、最初に名前を指定して、後で UDF を作成することはできません。)

  • トランスレーターで使用されるリクエストトランスレーター UDF または応答トランスレーターは、それを使用するすべての外部関数から最初に削除することなくドロップしないでください。(外部関数が呼び出されたときに、リクエストトランスレーターまたは応答トランスレーターが存在しない場合、Snowflakeはエラーを発生して失敗します。)

  • リクエストトランスレーターまたは応答トランスレーター UDF が(ALTER FUNCTION を介して)変更された場合は、同じインターフェイス要件を保持する必要があります。インターフェイス要件が保持されていない場合は、外部関数を実行する前に例外が発生します。

JavaScript 実装

実行時に、 SQL は OBJECT をトランスレーター UDF に渡します。JavaScript コードは、これを JavaScript オブジェクトとして受け取ります。

リクエストトランスレーターの実装

リクエストトランスレーターの入力プロパティ

トランスレーター UDF は、 event という名前の JavaScript オブジェクトを受け取ります。オブジェクトには、次のプロパティが含まれています。

  • body: data フィールドの形式は、既存のSnowflake行セットバッチ(つまり、行の配列)と同じです。

    例:

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

    既存のデータは本文外側の下にネストされます。

  • serviceUrl: 呼び出す外部関数の定義された URL です。

  • contextHeaders: コンテキスト関連のすべてのヘッダーを含むオブジェクトで、名前はフィールド名です。たとえば、オブジェクトにはフィールド名「SF_CONTEXT_CURRENT_DATABASE」を含めることができ、対応する値は現在のデータベース名を含む文字列になります。

リクエストトランスレーターの出力プロパティ

リクエストトランスレーターは、外部サービス API ゲートウェイとの通信に使用されるフィールドを持つオブジェクトを返します。そのオブジェクトには、次の3つのオプションフィールドがあります。

  • body: サービスに渡される実際の本文を定義します。これが定義されていない場合、本文はありません。 body 値は、リモートサービスが期待する形式の文字列または JSON オブジェクトである必要があります。値が文字列の場合、その文字列には内部構造を含めることができます(例: JSON 互換)。値が JSON オブジェクトの場合、そのオブジェクトは文字列に変換され、 HTTP POST コマンド文字列の一部として含めることができます。

  • urlSuffix: サービス URL のサフィックスを設定し、 serviceUrl 値の末尾に追加します。このサフィックスには、クエリパラメーター含めることもできます。パラメーター名と値は URL でエンコードする必要があります。たとえば、 a という名前のパラメーター値 my param に設定する場合は、スペース文字の URL エンコードが必要なため、パラメーターは ?a=my%20param になります。

  • translatorData: リクエストトランスレーターから応答トランスレーターに渡されます。このフィールドは、入力本文、サービス URL またはサフィックス、コンテキストヘッダーなどのコンテキスト情報を渡すことができます。

3つのフィールドはすべてオプションです。ただし、実際問題として、ほとんどのリクエストトランスレーターは少なくとも本文データを返します。

応答トランスレーターの実装

応答トランスレーターの入力プロパティ

応答トランスレーター関数の入力パラメーターはオブジェクトです。以下の例では、 EVENT を使用しています。これには2つのプロパティが含まれています。

  • body: 外部サービス応答からデコードされる応答です。

  • translatorData: このフィールドがリクエストトランスレーターによって返される場合、Snowflakeはそれを応答トランスレーターに渡します。

応答トランスレーターの出力プロパティ

応答トランスレーターの応答は、 body 要素の下のオブジェクトとして返されます。形式は、既存の外部関数形式(行の配列)です。例:

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

トランスレーター関数の要件

各トランスレーター UDF は、次の要件を満たしている必要があります。

  • JavaScript UDF である必要があります。

  • 行のバッチを表す型 OBJECT のパラメーターを1つだけ取る必要があります。

  • 型 OBJECT の値を1つ返す必要があります。これは、行のバッチも表します。

  • スカラー UDF である必要があります(渡された行(OBJECT)ごとに1行を返す)。

    注釈

    トランスレーターはスカラーですが、トランスレーターに渡される OBJECT では、 OBJECT の JSON 内に(通常は)複数の行を埋め込むことができます。

  • 応答トランスレーター UDF によって返される行の数と順序(OBJECT 内)は、リクエストトランスレーター UDF に渡される行の数と順序(OBJECT 内)と同じである必要があります。

リクエストトランスレーターと応答トランスレーターの例

次の例は、感情分析を行う外部サービス、 Amazon Comprehend BatchDetectSentiment に必要な形式にデータを変換するために使用されている、リクエストトランスレーターと応答トランスレーターを示しています。リクエストトランスレーターは、バックエンドサービスが期待する形式に一致するように HTTP リクエストを形成します。

トランスレーターを使用するには、 API ゲートウェイが必要です。この例では、感情分析サービスと通信するようにすでに構成されている API ゲートウェイを使用します。バックエンドとしてAmazon Web Services(AWS)サービスと統合する方法の詳細については、 AWS ドキュメント内の API Gatewayコンソールを使用した API 統合の設定 をご参照ください。

トランスレーターを追加する前に、 API 統合を正常に機能させると便利です。

設定

デモデータを保持するデータベースを設定します。

外部関数を作成する権限を持つロールを選択します。

USE ROLE ACCOUNTADMIN;
Copy

使用するウェアハウス、データベース、およびスキーマを指定します。

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

テスト文を保持するテーブルを作成します。

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

翻訳前に本文をリクエストする

この外部関数には、リクエストトランスレーターまたは応答トランスレーターがありません。

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

デモテーブルのテストデータを使用して外部関数を呼び出すことができます。

SELECT ComprehendSentiment(vc), vc FROM demo;
Copy

生成されたリクエスト本文は、Snowflake外部関数データ形式を使用します。

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

ただし、外部感情分析サービスは、言語と文字列の配列を指定する別の形式を想定しています。

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

次のセクションでは、リクエストトランスレーターを追加して、リクエスト本文を必要な形式に変更する方法について説明します。

リクエスト本文を変換する

リクエストトランスレーターを使用すると、上記のデフォルトの入力(Snowflakeデータ形式)を外部サービスが必要とする形式に変換できます。

次の SQL は、 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

リクエストトランスレーター関数のコードは、

  • 各入力行を介してループします。行ごとに、 row[1] にある文字列を textlist 配列に追加します。 row[0] の値は行番号であり、無視できます。

  • 外部サービスの要件に一致する言語コードとテキストリストを持つ JSON 本文を返します。

  • translatorData フィールドを介してデータを返します。これは応答トランスレーターによって使用されます。この例では、元の入力データを送信しています。応答トランスレーターの入力データの長さを使用して、入力リクエストがいくつあったかを確認します。

リクエストトランスレーターを直接呼び出すことでテストできます。

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

リクエストトランスレーターは、ボディを外部サービスが期待する形状にします。

{"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

応答トランスレーターを追加する前の応答本文

外部サービスからの応答本文は次のようになります。

{"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

応答本文を変換する

応答トランスレーターは、外部サービスから返される結果を処理します。結果には、 ErrorList のエラーと ResultList の結果の組み合わせが含まれます。

応答トランスレーターコードは、これらの結果を組み合わせて、外部サービスに渡された行の順序に一致する完全なセットを作成します。応答トランスレーターは、結果をSnowflake形式で返します。

次の SQL は、 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

応答トランスレーター関数のコードは、

  • translatorData 配列の長さからの入力サイズで responses という配列を初期化します。テスト文字列の元のリストを渡すために、リクエストトランスレーターから応答トランスレーターに translatorData を送信しました。

  • エラー以外の各結果をループして、結果リストに入れます。

  • エラー結果をループして結果リストに入れます。結果リストには、それがどのエントリであるかを示すインデックス位置があります。生成される結果の順序は、入力の順序と一致する必要があります。結果リストには、感情情報も含まれています。

すべての応答が収集された後、Snowflakeが期待する形式で JSON 本文に返されます。

次の直接テストでは、正しい形式の JSON 本文が返されます。

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

トランスレーターを外部関数に割り当て

外部関数に、関数名を値として request_translator および response_translator パラメーターに割り当てて、リクエストトランスレーターと応答トランスレーターの関数を追加します。この方法で、これらは外部関数の実行時に自動的に呼び出されます。

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

関数を記述して、それに関する情報を取得できます。

DESCRIBE FUNCTION ComprehendSentiment(VARCHAR);
Copy

外部関数を呼び出す

外部関数を単一の文で呼び出してテストします。

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

感情分析の結果が表示されます。

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

外部関数を複数の文で呼び出してテストします。以前に作成したものと同じ demo テーブルを使用します。

SELECT ComprehendSentiment(vc), vc FROM demo;
Copy

感情分析の結果が表示されます。

A table showing the sentiment analysis results.

外部関数が呼び出されると、リクエストトランスレーターはデータを外部サービスが必要とする形式に自動で変換しました。次に、応答トランスレーターは、外部サービスからの応答をSnowflakeが必要とする形式に自動で変換し直しました。

リクエストトランスレーターと応答トランスレーターをテストするためのヒント

  • テストケースの値は通常、 OBJECT 値(キーと値のペアのコレクション)です。これらは、 これらのルール にある要件を満たすようにフォーマットする必要があります。

  • 文字列に変換された入力例を渡すことで、リクエストトランスレーターまたは応答トランスレーターのテストを開始できます。例:

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

    PARSE_JSON() への入力は、 JSON 形式の文字列である必要があります。)

  • 適切かどうかを NULL 値でテストします。

    • テストケースに少なくとも1つの SQL NULL 値を含めます。

    • テストケースに少なくとも1つの JSON NULL 値を含めます。

  • リクエストの翻訳と応答の翻訳は、しばしば逆のプロセスです。概念的に、

    my_response_translator_udf(my_request_translator_udf(x)) = x
    
    Copy

    データ形式が一致するかどうかにおいて、この特性を使用すると、リクエストトランスレーターと応答トランスレーターをテストするために役立ちます。適切なテスト値を使用してテーブルを作成し、次のようなコマンドを実行します。

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

    クエリは行を返さないはずです。

    リクエストの翻訳と応答の翻訳は、必ずしも 正確に 逆であるとは限らないことに注意してください。逆関係ではない可能性のある例については、 TO_JSON() 関数 ドキュメントの「使用上の注意」セクションにある、逆関数の説明をご参照ください。