Formatos de dados de entrada e saída de serviços remotos

Quando o Snowflake envia dados para um serviço remoto, ou recebe dados de um serviço remoto, os dados devem ser formatados corretamente. Este tópico fornece informações sobre os formatos de dados apropriados. Os dados recebidos e retornados ao Snowflake também devem ser de um tipo de dados apropriado.

Ao executar uma função externa, por exemplo, o Snowflake envia e espera dados no formato aqui descrito. Ele envia os dados para um serviço de proxy, e não diretamente para o serviço remoto (para saber mais, consulte Introdução às funções externas). Portanto, o serviço de proxy deve receber (e retornar) os dados em um formato compatível com o Snowflake. Embora normalmente o serviço de proxy passe dados inalterados, o proxy pode reformatar dados (tanto o envio quanto o recebimento) para atender às necessidades tanto do serviço remoto quanto do Snowflake.

Para simplificar, e para ajudar a ilustrar os formatos que Snowflake espera enviar e receber, a maioria dos exemplos nesta seção considera que o serviço remoto lê e grava os dados no mesmo formato esperado pelo Snowflake, e o serviço de proxy passa os dados inalterados em ambas as direções.

Neste tópico:

Formato dos dados enviados pelo Snowflake

Cada solicitação de HTTP do Snowflake é um POST ou um GET.

  • Uma solicitação POST contém cabeçalhos e um corpo de solicitação. O corpo da solicitação inclui um lote de linhas.

  • Um GET contém somente cabeçalhos e é usado apenas para sondagens quando o serviço remoto retorna resultados de forma assíncrona.

Formato do corpo

O corpo da solicitação POST contém os dados, serializados no formato JSON.

O esquema para JSON é:

  • O nível superior é um objeto JSON (um conjunto de pares de nome/valor, também chamado de “dicionário”).

  • Atualmente, há exatamente um item nesse objeto; a chave para esse item é denominada “dados”.

  • O valor desse item “dados” é uma matriz JSON, na qual:

    • Cada elemento é uma linha de dados.

    • Cada linha de dados é uma matriz JSON de uma ou mais colunas.

    • A primeira coluna é sempre o número da linha (ou seja, o índice de base 0 da linha dentro do lote).

    • As demais colunas contêm os argumentos para a função.

  • Os tipos de dados são serializados da seguinte forma:

    • Os números são serializados como números JSON.

    • Os booleanos são serializados como booleanos JSON.

    • As cadeias de caracteres são serializadas como cadeias JSON.

    • Os objetos são serializados como objetos JSON.

    • Todos os outros tipos de dados compatíveis são serializados como cadeias de caracteres JSON.

    • NULL é serializado como null JSON.

Para exemplos de extração de dados em um serviço remoto em cada plataforma, consulte:

Opcionalmente, o JSON pode ser comprimido para transmissão pela rede. A compressão é documentada em CREATE EXTERNAL FUNCTION.

Exemplo de corpo

Aqui está um exemplo de uma solicitação serializada para uma função externa com a assinatura f(integer, varchar, timestamp). Observe que a primeira coluna é o número da linha dentro do lote, e os três valores seguintes são os argumentos para a função externa.

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

Formato do cabeçalho

As informações de cabeçalho estão geralmente disponíveis para o serviço remoto como um conjunto de pares chave/valor. As informações do cabeçalho incluem:

  • Os seguintes cabeçalhos HTTP:

    • Cabeçalhos que descrevem como os dados são serializados no corpo da solicitação:

      • “sf-external-function-format”:: atualmente, está sempre definido como “json”.

      • “sf-external-function-format-version”: atualmente, está sempre definido como “1.0”.

    • “sf-external-function-current-query-id”: contém o ID de consulta da consulta que chamou essa função externa. Você pode usar isso para correlacionar as consultas do Snowflake com as chamadas do serviço remoto, por exemplo, para ajudar na depuração de problemas.

    • “sf-external-function-query-batch-id”: o ID de lote identifica de forma única o lote específico de linhas processadas com essa solicitação. O serviço remoto pode usar esse ID para rastrear o status de um lote que está sendo processado. O ID também pode ser usado como um token de idempotência se as solicitações forem tentadas novamente devido a um erro. O ID também pode ser usado para o registro/rastreamento de solicitações pelo serviço remoto.

      O ID do lote em um GET é o mesmo que o ID do lote no POST correspondente.

      O ID do lote é um valor opaco gerado pelo Snowflake. O formato pode mudar em versões futuras, portanto, os serviços remotos não devem depender de um formato específico ou tentar interpretar o valor.

    • Cabeçalhos que descrevem a assinatura (nome e tipos de argumento) e tipo de retorno da função externa que foi chamada na consulta SQL. Esses valores podem ter caracteres que não são caracteres padrão para identificadores do Snowflake, portanto, versões base64 das informações são incluídas, e caracteres não padrão são substituídos por um espaço em branco nas versões que não são base64.

      Os cabeçalhos específicos são:

      • 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

      Por exemplo, os cabeçalhos enviados para a função ext_func(n integer)  returns varchar são:

      • 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>

      Observe que como os valores SQL INTEGER são tratados como SQL NUMBER, o argumento SQL declarado como tipo INTEGER é descrito como tipo NUMBER.

  • Metadados adicionais opcionais descritos nas propriedades de “cabeçalhos” e “context_headers” de CREATE EXTERNAL FUNCTION.

Exemplo de acesso ao cabeçalho

Para extrair o cabeçalho “sf-external-function-signature” de dentro de uma função AWS Lambda escrita em Python, que recebe os cabeçalhos como um dicionário Python, execute o seguinte:

def handler(event, context):

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

Os detalhes serão diferentes para outras linguagens e em outras plataformas de nuvem.

Para serviços remotos desenvolvidos na AWS, mais informações sobre cabeçalhos e integração de proxy do lambda estão disponíveis na documentação de Gateway AWS API .

Formato dos dados recebidos pelo Snowflake

Formato do corpo

Quando um serviço remoto termina de processar um lote, o serviço remoto deve enviar os dados de volta ao Snowflake em formato JSON semelhante ao formato dos dados enviados pelo Snowflake.

A resposta JSON devolvida ao Snowflake deve conter uma linha para cada linha enviada pelo Snowflake. Cada linha retornada contém dois valores:

  • O número da linha (ou seja, o índice de base 0 da linha dentro do lote).

  • O valor retornado da função para aquela linha. O valor pode ser um valor composto (por exemplo, um OBJECT), mas deve ser exatamente um valor porque todas as funções escalares (externas ou não) do Snowflake retornam um único valor.

Para que o Snowflake possa correlacionar a resposta com a solicitação, os números de linhas nos dados retornados devem corresponder aos números de linhas nos dados que o Snowflake enviou e devem ser devolvidos na mesma ordem em que foram recebidos.

Exemplo de acesso ao corpo

O seguinte exemplo JSON mostra duas linhas contendo um valor OBJECT, cada uma precedida por um número de linha:

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

Para compor uma dessas linhas devolvidas com Python, você pode usar o seguinte código:

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

Para acessar o valor OBJECT de uma linha retornada com SQL, use a notação descrita em Como percorrer dados semiestruturados. Por exemplo:

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

Formato do cabeçalho

A resposta também pode conter os seguintes cabeçalhos HTTP opcionais:

  • MD5 de conteúdo: o Snowflake usa o cabeçalho MD5 de conteúdo opcional para verificar a integridade da resposta. Se esse cabeçalho estiver incluído na resposta, o Snowflake calcula uma soma de verificação MD5 no corpo de resposta para garantir que ele corresponda à soma de verificação associada no cabeçalho retornado. Se os valores não corresponderem, a consulta SQL falha. A soma de verificação deve ser codificada em uma representação de base64 antes de ser devolvida no cabeçalho. Consulte o código de exemplo abaixo.

Opcionalmente, o JSON pode ser comprimido para transmissão pela rede. A compressão é documentada em CREATE EXTERNAL FUNCTION.

Para obter mais informações sobre tempo limite e novas tentativas, consulte Considerar erros de tempo limite e Não presumir que cada linha é passada para o serviço remoto somente uma vez.

Código de status

A resposta também contém um código de status HTTP. O Snowflake reconhece os seguintes códigos de status HTTP:

Código

Descrição

200

Lote processado com sucesso.

202

Lote recebido e ainda em processamento.

Outros valores são tratados como erros.

Exemplo de criação de resposta

O exemplo de código Python abaixo retorna uma resposta adequada, incluindo o código de resposta HTTP, os dados processados e um cabeçalho MD5 (que é opcional). (Isto foi escrito usando Python 3.8.)

Este exemplo é baseado em uma função AWS Lambda. É possível que alguns códigos precisem ser personalizados para diferentes plataformas.

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
    }
Copy