Formats des données d’entrée et de sortie des services à distance

Lorsque Snowflake envoie des données à un service distant ou reçoit des données d’un service distant, les données doivent être formatées correctement. Ce chapitre fournit des informations sur les formats de données appropriés.

Les données reçues de Snowflake et renvoyées à Snowflake doivent également être d’un type de données approprié .

Dans ce chapitre :

Format de données envoyé par Snowflake

Chaque requête HTTP de Snowflake est de type POST ou GET.

Une requête POST contient des en-têtes et un corps de requête. Le corps de la requête comprend un lot de lignes.

Un GET ne contient que des en-têtes, et n’est utilisé que pour le sondage lorsque le service à distance renvoie des résultats de manière asynchrone.

Les informations d’en-tête sont généralement accessibles au service à distance sous la forme d’un ensemble de paires clé/valeur. Les informations d’en-tête comprennent :

  • Les en-têtes HTTP suivants :

    • En-têtes qui décrivent comment les données sont sérialisées dans le corps de la requête :

      • « sf-external-function-format » : ceci est actuellement toujours réglé sur « json ».

      • « sf-external-function-format-version » : ceci est actuellement toujours défini sur « 1.0 ».

    • « sf-external-function-current-query-id » : ceci contient l’ID de requête de la requête qui a appelé cette fonction externe. Vous pouvez l’utiliser pour corréler les requêtes Snowflake aux appels du service distant, par exemple pour aider au débogage des problèmes.

    • Le « sf-external-function-query-batch-id » : l’ID de lot identifie de manière unique le lot spécifique de lignes traitées avec cette requête. Le service à distance peut utiliser cet ID pour suivre le statut d’un lot en cours de traitement. L” ID peut également être utilisé comme un jeton d’impuissance si les requêtes sont réessayées en raison d’une erreur. L” ID peut également être utilisé pour l’enregistrement et le traçage des demandes par le service à distance.

      L’ID du lot dans un GET est le même que l’ID du lot dans le POST correspondant.

      L” ID de lot est une valeur opaque générée par Snowflake. Le format pourrait changer dans les prochaines versions, les services à distance ne doivent donc pas se fier à un format spécifique ou essayer d’interpréter la valeur.

    • Les en-têtes qui décrivent la signature (nom et types d’arguments) et le type de retour de la fonction externe qui a été appelée dans la requête SQL. Ces valeurs peuvent comporter des caractères qui ne sont pas des caractères standard pour les identificateurs Snowflake, de sorte que les versions de base64 des informations sont incluses, et les caractères non standard sont remplacés par un espace blanc dans les versions hors base64. Les en-têtes spécifiques sont les suivants :

      • 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

      Par exemple, les en-têtes envoyés pour la fonction ext_func(n integer)  returns varchar sont :

      • sf-external-function-name : ext_func

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

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

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

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

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

      Notez que parce que les valeurs INTEGER SQL sont traitées comme des NUMBER SQL , l’argument SQL déclaré comme de type INTEGER est décrit comme de type NUMBER.

  • Métadonnées facultatives supplémentaires décrites dans les propriétés « headers » et « context_headers » de CREATE EXTERNAL FUNCTION.

Par exemple, pour extraire l’en-tête « sf-external-function-signature » de l’intérieur d’une fonction AWS Lambda écrite en Python, qui reçoit les en-têtes comme un dictionnaire python, exécutez ce qui suit :

def handler(event, context):

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

Les détails seront différents pour d’autres langues et sur d’autres plates-formes Cloud.

(Pour les services à distance développés sur AWS, de plus amples informations sur les en-têtes et l’intégration du proxy lambda sont disponibles dans la documentation sur API Gateway .)

Le corps de la requête POST contient les données, sérialisées au format JSON. Le schéma pour JSON est :

  • Le niveau supérieur est un objet JSON (un ensemble de paires nom/valeur, également appelé « dictionnaire »).

  • Actuellement, il y a exactement un élément dans cet objet ; la clé de cet élément est nommée « données ».

  • La valeur de cet élément « données » est un tableau JSON, dans lequel chaque élément du tableau est une ligne de données.

  • Chaque ligne de données est un tableau JSON d’une ou plusieurs colonnes.

  • La première colonne est toujours le numéro de ligne (c’est-à-dire l’index basé sur 0 de la ligne dans le lot).

  • Les colonnes restantes contiennent les arguments de la fonction.

  • Les types de données sont sérialisés comme suit :

    • Les nombres sont sérialisés en tant que nombres JSON.

    • Les booléens sont sérialisés en tant que booléens JSON.

    • Les chaînes sont sérialisées en tant que chaînes JSON.

    • Les variantes sont sérialisées en tant qu’objets JSON.

    • Tous les autres types de données pris en charge sont sérialisés en tant que chaînes JSON.

    • NULL est sérialisé en tant que JSON null.

Voici un exemple de demande sérialisée pour une fonction externe avec la signature f(integer, varchar, timestamp). Notez que la première colonne est le numéro de ligne dans le lot, et les 3 valeurs suivantes sont les arguments de la fonction externe.

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

Des exemples d’extraction de données sont inclus dans la documentation pour la création d’un service à distance sur chaque plateforme :

Facultativement, le JSON peut être compressé pour la transmission sur le réseau. La compression est documentée dans CREATE EXTERNAL FUNCTION.

Snowflake envoie ces données au service proxy, pas directement au service distant. Par conséquent, le service proxy doit recevoir (et renvoyer) les données dans un format compatible avec Snowflake. Bien que le service proxy transmette généralement des données inchangées, le proxy peut reformater les données (à la fois en envoi et en réception) pour répondre aux besoins du service distant et de Snowflake.

Par souci de simplicité et pour illustrer les formats que Snowflake s’attend à envoyer et à recevoir, la plupart des exemples de cette section supposent que le service distant lit et écrit des données dans le même format que Snowflake le prévoit et que le service proxy transmet les données sans les modifier dans les deux directions.

Format de données reçu par Snowflake

Lorsque le service à distance termine le traitement d’un lot, le service à distance doit envoyer les données à Snowflake dans un format similaire à celui des données envoyées par Snowflake. La valeur renvoyée est au format JSON. Voici un exemple de la section de données d’une telle réponse :

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

La réponse JSON renvoyée à Snowflake doit contenir une ligne pour chaque ligne envoyée par Snowflake. Chaque ligne renvoyée contient deux valeurs :

  • Le numéro de ligne (c’est-à-dire l’index basé sur 0 de la ligne dans le lot).

  • La valeur renvoyée par la fonction pour cette ligne. La valeur peut être une valeur composée (par exemple, un VARIANT), mais elle doit être exactement une valeur car toutes les fonctions scalaires Snowflake (externes ou autres) renvoient une seule valeur.

Les numéros de ligne dans les données renvoyées doivent correspondre aux numéros de ligne dans les données envoyées par Snowflake et doivent être renvoyés dans le même ordre qu’ils ont été reçus.

Le code ci-dessous montre un exemple qui contient une valeur VARIANT après le numéro de ligne :

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

En Python, le code pour composer une ligne renvoyée, y compris sa valeur VARIANT, serait similaire à ce qui suit :

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

Pour accéder aux éléments de la valeur VARIANT renvoyée dans une instruction SQL, utilisez la même notation que celle décrite dans Parcours de données semi-structurées. Par exemple :

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

La réponse contient également un code de statut HTTP. Snowflake reconnaît les codes de statut HTTP suivants :

Code

Description

200

Traitement par lots réussi.

202

Lot reçu et toujours en cours de traitement.

Les autres valeurs sont traitées comme des erreurs.

Cette liste de codes de statut peut s’étendre avec le temps.

La réponse peut également contenir les en-têtes HTTP facultatifs suivants :

  • Content-MD5 : Snowflake utilise l’en-tête optionnel Content-MD5 pour vérifier l’intégrité de la réponse. Si cet en-tête est inclus dans la réponse, Snowflake calcule une somme de contrôle MD5 sur le corps de la réponse pour s’assurer qu’il correspond à la somme de contrôle correspondante dans l’en-tête renvoyé. Si les valeurs ne correspondent pas, la requête SQL échoue. La somme de contrôle doit être encodée dans une représentation base64 avant d’être renvoyée dans l’en-tête. Voir l’exemple de code ci-dessous.

Facultativement, le JSON peut être compressé pour la transmission sur le réseau. La compression est documentée dans CREATE EXTERNAL FUNCTION.

Pour plus d’informations sur les délais d’expiration et les tentatives, voir Tenir compte des erreurs d’expiration de délai et Ne supposez pas que le service distant a validé chaque ligne exactement une fois.

L’exemple de code Python ci-dessous renvoie une réponse correcte, comprenant le code de réponse HTTP, les données traitées et un en-tête MD5 (qui est facultatif). (Ceci a été écrit en utilisant Python 3.8.)

Cet exemple est basé sur une fonction AWS Lambda. Certains codes peuvent nécessiter une personnalisation pour différentes plates-formes.

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
    }