Eingabe- und Ausgabedatenformate von Remotediensten

Wenn Snowflake Daten an einen Remotedienst sendet oder Daten von einem Remotedienst empfängt, müssen die Daten korrekt formatiert werden. Unter diesem Thema finden Sie Informationen zu den richtigen Datenformaten.

Daten, die von Snowflake empfangen und an Snowflake zurückgegeben werden, müssen ebenfalls einen geeigneten Datentyp haben.

Unter diesem Thema:

Von Snowflake gesendetes Datenformat

Jede HTTP-Anforderung von Snowflake ist ein POST oder ein GET.

Eine POST-Anforderung enthält Header und einen Anforderungstext. Der Anforderungstext enthält einen Batch von Zeilen.

Ein GET enthält nur Header und wird nur für das Abrufen verwendet, wenn der Remotedienst Ergebnisse asynchron zurückgibt.

Die Header-Informationen stehen dem Remotedienst in der Regel als ein Satz von Schlüssel/Wert-Paaren zur Verfügung. Zu diesen Header-Informationen gehören:

  • Es gibt folgende HTTP-Header:

    • Header, die beschreiben, wie Daten im Body der Anforderung serialisiert sind:

      • „sf-external-function-format“: Dies ist derzeit immer auf „json“ eingestellt.

      • „sf-external-function-format-version“: Dies ist derzeit immer auf „1.0“ eingestellt.

    • „sf-external-function-current-query-id“: Enthält die Abfrage-ID der Abfrage, die diese externe Funktion aufgerufen hat. Sie können diesen Header verwenden, um Snowflake-Abfragen mit Aufrufen des Remotedienstes zu korrelieren, um beispielsweise Probleme zu beheben.

    • „sf-external-function-query-batch-id“: Die Batch-ID ermöglicht die eindeutige Identifizierung des spezifischen Batches von Zeilen, die mit dieser Anforderung verarbeitet werden. Der Remotedienst kann diese ID verwenden, um den Status eines in Bearbeitung befindlichen Batches zu verfolgen. Die ID kann auch als Idempotenztoken verwendet werden, wenn Anforderungen aufgrund eines Fehlers erneut gesendet werden. Die ID kann auch für die Protokollierung/Ablaufverfolgung von Anforderungen durch den Remotedienst verwendet werden.

      Die Batch-ID in einem GET ist die gleiche wie die Batch-ID in dem zugehörigen POST.

      Die Batch-ID ist ein nicht transparenter Wert, der von Snowflake generiert wird. Das Format kann sich in zukünftigen Versionen ändern, sodass sich die Remotedienste nicht auf ein bestimmtes Format verlassen oder versuchen sollten, den Wert zu interpretieren.

    • Header, die die Signatur (Name und Argumenttypen) und den Rückgabetyp der externen Funktion beschreiben, die in der SQL-Abfrage aufgerufen wurde. Diese Werte können Zeichen enthalten, die keine Standardzeichen für Snowflake-Bezeichner sind. Daher sind Base64-Versionen der Informationen enthalten, und Nicht-Standardzeichen werden in den Nicht-Base64-Versionen durch ein Leerzeichen ersetzt. Die spezifischen Header sind:

      • 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

      Die für die Funktion ext_func(n integer)  returns varchar gesendeten Header sind zum Beispiel:

      • sf-external-function-name: ext_func

      • sf-external-function-name-base64: <Base64-Wert>

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

      • sf-external-function-signature-base64: <Base64-Wert>

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

      • sf-external-function-return-type-base64: <Base64-Wert>

      Beachten Sie, dass das vom Typ INTEGER deklarierte SQL-Argument als Typ NUMBER beschrieben wird, da SQL INTEGER-Werte wie SQL NUMBER-Wert behandelt werden.

  • Zusätzliche optionale Metadaten, die in den Eigenschaften „headers“ und „context_headers“ von CREATE EXTERNAL FUNCTION beschrieben sind.

Um beispielsweise den Header „sf-external-function-signature“ aus dem Inneren einer AWS Lambda-Funktion zu extrahieren, die in Python geschrieben ist und die die Header als Python-Wörterbuch empfängt, führen Sie Folgendes aus:

def handler(event, context):

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

Die Details werden sich für andere Sprachen und andere Cloudplattformen unterscheiden.

(Für Remotedienste, die auf AWS entwickelt wurden, finden Sie weitere Informationen zu Header und Lambda-Proxyintegration in der API Gateway-Dokumentation.)

Der Textköper der POST-Anforderung enthält die Daten, die im JSON-Format serialisiert sind. Das Schema für JSON lautet:

  • Die oberste Ebene ist ein JSON-Objekt (eine Menge von Name/Wert-Paaren, auch „Wörterbuch“ oder „Dictionary“ genannt).

  • Derzeit gibt es genau einen Eintrag im Wörterbuch. Der Schlüssel für dieses Element heißt „data“.

  • Der Wert dieses „data“-Elements ist ein JSON-Array, in dem jedes Array-Element eine Datenzeile ist.

  • Jede Datenzeile ist ein JSON-Array aus einer oder mehreren Spalten.

  • Die erste Spalte ist immer die Zeilennummer (d. h. der 0-basierte Index der Zeile innerhalb des Batches).

  • Die verbleibenden Spalten enthalten die Argumente für die Funktion.

  • Datentypen werden wie folgt serialisiert:

    • Nummern werden als JSON-Nummern serialisiert.

    • Boolesche Werte werden als Boolesche JSON-Werte serialisiert.

    • Zeichenfolgen werden als JSON-Zeichenfolgen serialisiert.

    • Variant-Werte werden als JSON-Objekte serialisiert.

    • Alle anderen unterstützten Datentypen werden als JSON-Zeichenfolgen serialisiert.

    • NULL wird als JSON-Null serialisiert.

Hier ist ein Beispiel für eine serialisierte Anforderung für eine externe Funktion mit der Signatur f(integer, varchar, timestamp). Beachten Sie, dass die erste Spalte die Zeilennummer innerhalb des Batches ist und die nächsten drei Werte die Argumente für die externe Funktion sind.

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

Beispiele für das Extrahieren von Daten finden Sie in der Dokumentation zum Erstellen eines Remotedienstes auf der jeweiligen Plattform:

Optional können JSON-Daten für die Übertragung über das Netzwerk komprimiert werden. Die Komprimierung ist in CREATE EXTERNAL FUNCTION dokumentiert.

Snowflake sendet diese Daten an den Proxydienst und nicht direkt an den Remotedienst. Daher muss der Proxydienst Daten in einem Snowflake-kompatiblen Format empfangen (und zurückgeben). Obwohl der Proxydienst normalerweise Daten unverändert weiterleitet, kann der Proxy zu sendende und zu empfangende Daten neu formatieren, um die Anforderungen sowohl des Remotedienstes als auch von Snowflake zu erfüllen.

Der Einfachheit halber und zur Veranschaulichung der Formate, die Snowflake voraussichtlich senden und empfangen wird, wird in den meisten Beispielen in diesem Abschnitt davon ausgegangen, dass der Remotedienst Daten im gleichen Format liest und schreibt, das Snowflake erwartet, und dass der Proxydienst Daten unverändert in beide Richtungen weiterleitet.

Von Snowflake empfangenes Datenformat

Wenn der Remotedienst die Verarbeitung eines Batches abgeschlossen hat, sollte der Remotedienst Daten in einem Format an Snowflake senden, das dem Format der von Snowflake gesendeten Daten ähnelt. Der zurückgegebene Wert hat das JSON-Format. Hier ist ein Beispiel für den Datenteil einer solchen Antwort:

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

Die an Snowflake zurückgegebene JSON-Antwort sollte eine Zeile für jede von Snowflake gesendete Zeile enthalten. Jede zurückgegebene Zeile enthält zwei Werte:

  • Die Zeilennummer (d. h. der 0-basierte Index der Zeile innerhalb des Batches).

  • Der von der Funktion für diese Zeile zurückgegebene Wert. Der Wert kann ein zusammengesetzter Wert sein (z. B. ein VARIANT), muss jedoch genau ein Wert sein, da alle skalaren Snowflake-Funktionen (extern oder anderweitig) einen einzelnen Wert zurückgeben.

Die Zeilennummern in den zurückgegebenen Daten müssen den Zeilennummern in den von Snowflake gesendeten Daten entsprechen und in derselben Reihenfolge zurückgegeben werden, in der sie empfangen wurden.

Der folgende Code zeigt ein Beispiel, das einen VARIANT-Wert nach der Zeilennummer enthält:

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

In Python würde der Code zum Zusammensetzen der einen zurückgegebenen Zeile, einschließlich ihres VARIANT-Wertes, etwa wie folgt aussehen:

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

Um auf die Elemente des zurückgegebenen VARIANT-Werts in einer SQL-Anweisung zuzugreifen, verwenden Sie dieselbe Notation wie unter Durchsuchen semistrukturierter Daten beschrieben. Beispiel:

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

Die Antwort enthält außerdem einen HTTP-Statuscode. Snowflake erkennt die folgenden HTTP-Statuscodes:

Code

Beschreibung

200

Batch erfolgreich verarbeitet.

202

Batch erhalten und noch in Bearbeitung.

Andere Werte werden als Fehler behandelt.

Diese Liste der Statuscodes wird im Laufe der Zeit möglicherweise erweitert.

Die Antwort kann auch die folgenden optionalen HTTP-Header enthalten:

  • Content-MD5: Snowflake verwendet den optionalen Content-MD5-Header, um die Integrität der Antwort zu prüfen. Wenn dieser Header in der Antwort enthalten ist, berechnet Snowflake eine MD5-Prüfsumme für den Textkörper der Antwort, um sicherzustellen, dass er mit der entsprechenden Prüfsumme im zurückgegebenen Header übereinstimmt. Wenn die Werte nicht übereinstimmen, schlägt die SQL-Abfrage fehl. Die Prüfsumme sollte in einer Base64-Darstellung kodiert werden, bevor sie im Header zurückgegeben wird. Betrachten Sie dazu den Beispielcode unten.

Optional können JSON-Daten für die Übertragung über das Netzwerk komprimiert werden. Die Komprimierung ist in CREATE EXTERNAL FUNCTION dokumentiert.

Weitere Informationen zu Timeouts und Wiederholungsversuchen finden Sie unter Konto für Timeout-Fehler und Berücksichtigen, dass Remotedienst mehr als eine Zeile übergeben kann.

Der folgende Python-Beispielcode gibt eine korrekte Antwort zurück, einschließlich HTTP-Antwortcode, verarbeitete Daten und MD5-Header (der optional ist). (Dies wurde mit Python 3.8 geschrieben.)

Dieses Beispiel basiert auf einer AWS Lambda-Funktion. Einige Codes müssen möglicherweise für andere Plattformen angepasst werden.

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
    }