リモートサービスの入力および出力データ形式

Snowflakeがリモートサービスにデータを送信する場合、またはリモートサービスからデータを受信する場合、データは正しくフォーマットされている必要があります。このトピックでは、適切なデータ形式に関する情報を提供します。

Snowflakeとの間で送受信されるデータも、 適切なデータ型 である必要があります。

このトピックの内容:

Snowflakeから送信されたデータ形式

Snowflakeからの各HTTPリクエストは、POSTまたはGETです。

POSTリクエストには、ヘッダーとリクエスト本文が含まれています。リクエストの本文には、行の バッチ が含まれています。

GET にはヘッダーのみが含まれ、リモートサービスが 非同期的 に結果を返す場合のポーリングのみに使用されます。

ヘッダー情報は通常、キーと値のペアのセットとしてリモートサービスで利用できます。ヘッダーの情報には次が含まれます。

  • 次の HTTP ヘッダー:

    • リクエストの本文でデータがシリアル化される方法を説明するヘッダー:

      • 「sf-external-function-format」:現在、常に「json」に設定されています。

      • 「sf-external-function-format-version」:現在、常に「1.0」に設定されています。

    • 「sf-external-function-current-query-id」:これには、この外部関数を呼び出したクエリのクエリ ID が含まれています。これを使用して、Snowflakeクエリをリモートサービスの呼び出しに関連付け、たとえば問題のデバッグに役立てることができます。

    • 「sf-external-function-query-batch-id」: バッチ ID は、このリクエストで処理された行の特定のバッチを一意に識別します。リモートサービスでは、この ID を使用して、処理中のバッチのステータスを追跡できます。エラーが原因でリクエストが再試行された場合、IDはべき等トークンとしても使用できます。IDは、リモートサービスによるリクエストのログ記録/トレースにも使用できます。

      GETのバッチIDは、対応するPOSTのバッチIDと同じです。

      バッチIDは、Snowflakeによって生成された不透明な値です。形式は将来のリリースで変更される可能性があるため、リモートサービスは特定の形式に依存したり、値を解釈しようとしたりしないでください。

    • SQL クエリで呼び出された外部関数の署名(名前と引数の型)と戻り値の型を説明するヘッダー。これらの値には、Snowflake 識別子 の標準文字ではない文字を含めることができるため、情報のBase64バージョンが含まれ、非標準文字は、非Base64バージョンの空白に置き換えられます。具体的なヘッダーは次のとおりです。

      • sf-external-function-name

      • sf-external-function-name-base64

      • sf-external-function-signature

      • sf-external-function-signature-base64

      • sf-external-function-return-type

      • sf-external-function-return-type-base64

      たとえば、関数 ext_func(n integer)  returns varchar に送信されるヘッダーは次のとおりです。

      • sf-external-function-name: ext_func

      • sf-external-function-name-base64: <base64 value>

      • sf-external-function-signature: (N NUMBER)

      • sf-external-function-signature-base64: <base64 value>

      • sf-external-function-return-type: VARCHAR(16777216)

      • sf-external-function-return-type-base64: <base64 value>

      SQL INTEGER 値は SQL NUMBER として扱われるため、型 INTEGER として宣言された SQL 引数は、型 NUMBER として記述されることに注意してください。

  • CREATE EXTERNAL FUNCTION の「ヘッダー」および「コンテキストヘッダー」プロパティで説明されている追加のオプションメタデータ。

たとえば、ヘッダーをPythonディクショナリとして受け取る、Pythonで記述された AWS Lambda関数内から「sf-external-function-signature」ヘッダーを抽出するには、次を実行します。

def handler(event, context):

    request_headers = event["headers"]
    signature = request_headers["sf-external-function-signature"]

詳細は、他の言語や他のクラウドプラットフォームにより異なります。

(AWS で開発されたリモートサービスの場合の、ヘッダーとラムダプロキシ統合の詳細については、 API Gatewayのドキュメント を参照。)

POST リクエストの本文には、 JSON 形式でシリアル化されたデータが含まれます。JSON のスキーマは次のとおりです。

  • トップレベルは JSON オブジェクト(名前と値のペアのセットで、「ディクショナリ」とも呼ばれる)です。

  • 現在、オブジェクトには1つのアイテムしかありません。そのアイテムのキーの名前は「データ」です。

  • その「データ」アイテムの値は JSON 配列で、配列の各要素は1行のデータです。

  • データの各行は、1つ以上の列の JSON 配列です。

  • 最初の列は常に行番号です(つまり、バッチ内における行の0ベースインデックス)。

  • 残りの列には、関数の引数が含まれています。

  • データ型は次のようにシリアル化されます。

    • 番号は JSON 番号としてシリアル化されます。

    • ブール値は JSON ブール値としてシリアル化されます。

    • 文字列は JSON 文字列としてシリアル化されます。

    • バリアントは JSON オブジェクトとしてシリアル化されます。

    • サポートされている他のすべてのデータ型は、 JSON 文字列としてシリアル化されます。

    • NULL は JSON nullとしてシリアル化されます。

これは、署名 f(integer, varchar, timestamp) を持つ外部関数のシリアル化されたリクエストの例です。最初の列はバッチ内の行番号であり、次の3つの値は外部関数への引数であることに注意してください。

{
    "data": [
                [0, 10, "Alex", "2014-01-01 16:00:00"],
                [1, 20, "Steve", "2015-01-01 16:00:00"],
                [2, 30, "Alice", "2016-01-01 16:00:00"],
                [3, 40, "Adrian", "2017-01-01 16:00:00"]
            ]
}

データ抽出の例は、各プラットフォームでリモートサービスを作成するためのドキュメントに含まれています。

オプションで、ネットワークを介して送信するために JSON を圧縮できます。圧縮については、 CREATE EXTERNAL FUNCTION に記載されています。

Snowflakeはこのデータを直接リモートサービスではなく、プロキシサービスに送信します。したがって、プロキシサービスは、Snowflake互換の形式でデータを受信(および返す)する必要があります。通常、プロキシサービスはデータを変更せずに渡しますが、リモートサービスとSnowflakeの両方のニーズを満たすために、プロキシはデータを再フォーマット(送信と受信の両方)できます。

簡単にするためとSnowflakeが送受信する形式を説明するために、このセクションのほとんどの例では、リモートサービスがSnowflakeが期待する形式と同じ形式でデータを読み書きし、プロキシサービスが両方向でデータを変更せずに渡すことを想定しています。

Snowflakeが受信するデータ形式

リモートサービスがバッチの処理を終了すると、リモートサービスはSnowflakeによって送信されたデータの形式と同様の形式でSnowflakeにデータを送信する必要があります。戻り値はJSON形式です。このような応答のデータセクションの例を次に示します。

{
    "data":
        [
            [ 0, 1995 ],
            [ 1, 1974 ],
            [ 2, 1983 ],
            [ 3, 2001 ]
        ]
}

Snowflakeに返されるJSON応答には、Snowflakeによって送信された各行に1行が含まれている必要があります。返された各行には2つの値が含まれます。

  • 行番号(つまり、バッチ内における行の0ベースインデックス)。

  • その行の関数から返された値。値は複合値(たとえば、 VARIANT)にすることができますが、Snowflakeのスカラー関数すべて(外部またはその他)が単一の値を返すため、正確に単一の値でなければなりません。

返されるデータの行番号は、Snowflakeが送信したデータの行番号に対応している必要があり、受信した順序と同じ順序で返される必要があります。

以下のコードは、行番号の後に VARIANT 値を含む例を示しています。

{
    "data":
        [
            [ 0, { "City" : "Warsaw",  "latitude" : 52.23, "longitude" :  21.01 } ],
            [ 1, { "City" : "Toronto", "latitude" : 43.65, "longitude" : -79.38 } ]
        ]
}

Pythonでは、 VARIANT 値を含む1つの返された行を構成するコードは、次のようになります。

...
row_number = 0
output_value = {}

output_value["city"] = "Warsaw"
output_value["latitude"] = 21.01
output_value["longitude"] = 52.23
row_to_return = [row_number, output_value]
...

SQL ステートメントで返された VARIANT の要素にアクセスするには、 半構造化データの走査 で説明されているのと同じ表記法を使用します。例:

select val:city, val:latitude, val:longitude
    from (select ext_func_city_lat_long(city_name) as val from table_of_city_names);

応答には、 HTTP ステータスコードも含まれています。Snowflakeは、次の HTTP ステータスコードを認識します。

コード

説明

200

バッチが正常に処理されました。

202

バッチを受信し、まだ処理中です。

その他の値はエラーとして扱われます。

このステータスコードのリストは、時間の経過とともに拡大する可能性があります。

応答には、次のオプションの HTTP ヘッダーを含めることもできます。

  • Content-MD5: Snowflakeは、オプションのContent-MD5 ヘッダーを使用して、応答の整合性をチェックします。このヘッダーが応答に含まれている場合、Snowflakeは、応答本文で MD5 チェックサムを計算して、返されたヘッダーの対応するチェックサムと一致することを確認します。値が一致しない場合、 SQL クエリは失敗します。チェックサムは、ヘッダーで返される前に、Base64表現でエンコードする必要があります。以下のサンプルコードをご参照ください。

オプションで、ネットワークを介して送信するために JSON を圧縮できます。圧縮については、 CREATE EXTERNAL FUNCTION に記載されています。

タイムアウトと再試行の詳細については、 タイムアウトエラーの説明 および リモートサービスが各行に1回だけ渡されると思い込まない をご参照ください。

以下のPythonコードの例は、 HTTP 応答コード、処理されたデータ、および MD5 ヘッダー(オプション)を含む適切な応答を返します。(これは、Python 3.8を使用して記述。)

この例は、 AWS Lambda関数に基づいています。一部のコードには、プラットフォームごとにカスタマイズが必要な場合があります。

import json
import hashlib
import base64

def handler(event, context):

    # The return value should contain an array of arrays (one inner array per input row for a scalar function).
    array_of_rows_to_return = [ ]

    ...

    json_compatible_string_to_return = json.dumps({"data" : array_of_rows_to_return})

    # Calculate MD5 checksum for the response
    md5digest = hashlib.md5(json_compatible_string_to_return.encode('utf-8')).digest()
    response_headers = {
        'Content-MD5' : base64.b64encode(md5digest)
    }

    # Return the HTTP status code, the processed data, and the headers (including the Content-MD5 header).
    return {
        'statusCode': 200,
        'body': json_compatible_string_to_return,
        'headers': response_headers
    }