Exemplos de manipuladores de UDF em Scala

Este tópico inclui exemplos simples de código de manipulador de UDF escrito em Scala.

Para obter mais informações sobre como usar o Scala para criar um manipulador escalar de UDF, consulte Como escrever uma UDF escalar em Scala. Para obter diretrizes gerais de codificação, consulte Diretrizes de codificação do manipulador de UDF de Scala.

Como criar e chamar uma UDF de Scala inline simples

As seguintes instruções criam e chamam uma UDF de Scala inline. Esse código retorna o VARCHAR passado a ele.

Essa função é declarada com a cláusula opcional CALLED ON NULL INPUT para indicar que a função é chamada mesmo que o valor da entrada seja NULL. (Essa função retornaria NULL com ou sem a cláusula, mas você poderia modificar o código para tratar NULL de outra forma, por exemplo, para retornar uma cadeia de caracteres vazia).

Criação da UDF

CREATE OR REPLACE FUNCTION echo_varchar(x VARCHAR)
RETURNS VARCHAR
LANGUAGE SCALA
CALLED ON NULL INPUT
RUNTIME_VERSION = 2.12
HANDLER='Echo.echoVarchar'
AS
$$
class Echo {
  def echoVarchar(x : String): String = {
    return x
  }
}
$$;
Copy

Chamada da UDF

SELECT echo_varchar('Hello');
Copy

Como passar um NULL para uma UDF de Scala inline

Isso utiliza a echo_varchar() UDF definida acima. O valor SQL NULL é convertido implicitamente em nulo Scala, e o Null Scala é retornado e convertido implicitamente de volta em SQL NULL:

Chame a UDF:

SELECT echo_varchar(NULL);
Copy

Como retornar NULL explicitamente a partir de uma UDF inline

O código a seguir mostra como retornar um valor NULL explicitamente. O valor Scala Null é convertido em SQL NULL.

Criação da UDF

CREATE OR REPLACE FUNCTION return_a_null()
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER='TemporaryTestLibrary.returnNull'
AS
$$
class TemporaryTestLibrary {
  def returnNull(): String = {
    return null
  }
}
$$;
Copy

Chamada da UDF

SELECT return_a_null();
Copy

Como passar um OBJECT para uma UDF de Scala inline

O exemplo a seguir usa o tipo de dados SQL OBJECT e o tipo de dados de Scala correspondente (Map[String, String]), e extrai um valor do OBJECT. Esse exemplo também mostra que você pode passar parâmetros múltiplos para uma UDF em Scala.

Como criar e carregar uma tabela que contenha uma coluna do tipo OBJECT:

CREATE TABLE objectives (o OBJECT);
INSERT INTO objectives SELECT PARSE_JSON('{"outer_key" : {"inner_key" : "inner_value"} }');
Copy

Criação da UDF

CREATE OR REPLACE FUNCTION extract_from_object(x OBJECT, key VARCHAR)
RETURNS VARIANT
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER='VariantLibrary.extract'
AS
$$
import scala.collection.immutable.Map

class VariantLibrary {
  def extract(m: Map[String, String], key: String): String = {
    return m(key)
  }
}
$$;
Copy

Chamada da UDF

SELECT extract_from_object(o, 'outer_key'),
  extract_from_object(o, 'outer_key')['inner_key'] FROM OBJECTIVES;
Copy

Como passar um ARRAY para uma UDF de Scala inline

O exemplo a seguir utiliza o tipo de dados SQL ARRAY.

Criação da UDF

CREATE OR REPLACE FUNCTION generate_greeting(greeting_words ARRAY)
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER='StringHandler.handleStrings'
AS
$$
class StringHandler {
  def handleStrings(strings: Array[String]): String = {
    return concatenate(strings)
  }
  private def concatenate(strings: Array[String]): String = {
    var concatenated : String = ""
    for (newString <- strings)  {
        concatenated = concatenated + " " + newString
    }
    return concatenated
  }
}
$$;
Copy

Como ler um arquivo com uma UDF de Scala

Você pode ler o conteúdo de um arquivo com o código do manipulador. Por exemplo, talvez você queira ler um arquivo para processar dados não estruturados com o manipulador.

O arquivo deve estar em um estágio do Snowflake que esteja disponível para seu manipulador.

Para ler o conteúdo dos arquivos preparados, seu manipulador pode ler um arquivo especificado dinamicamente chamando métodos da classe SnowflakeFile ou da classe InputStream.

Você pode fazer isso se precisar acessar um arquivo especificado pelo chamador. Para obter mais informações, consulte o seguinte neste tópico:

SnowflakeFile fornece recursos não disponíveis com InputStream, conforme descrito na tabela a seguir.

Classe

Entrada

Notas

SnowflakeFile

Formatos de URL:

  • URL com escopo para reduzir o risco de ataques de injeção de arquivo quando o chamador da função não for também seu proprietário.

  • URL do arquivo ou caminho da cadeia de caracteres para arquivos que o proprietário da UDF tenha acesso.

O arquivo deve estar localizado em um estágio interno ou em um estágio externo nomeado.

Acesso fácil a atributos adicionais do arquivo, como o tamanho do arquivo.

InputStream

Formatos de URL:

  • URL com escopo para reduzir o risco de ataques de injeção de arquivo quando o chamador da função não for também seu proprietário.

O arquivo deve estar localizado em um estágio interno ou em um estágio externo nomeado.

Nota

O proprietário da UDF deve ter acesso a todos os arquivos cujos locais não estão em URLs com escopo. Você pode ler esses arquivos preparados fazendo com que o código do manipulador chame o método SnowflakeFile.newInstance com um valor boolean para um novo parâmetro requireScopedUrl.

O exemplo a seguir usa SnowflakeFile.newInstance ao especificar que um URL com escopo não é necessário.

var filename = "@my_stage/filename.txt"
var sfFile = SnowflakeFile.newInstance(filename, false)
Copy

Como ler um arquivo especificado dinamicamente com SnowflakeFile

Usando métodos da classe SnowflakeFile, você pode ler arquivos de um estágio com seu código do manipulador. A classe SnowflakeFile está incluída no classpath disponível para os manipuladores de UDF de Scala no Snowflake.

Nota

Para tornar seu código resistente a ataques de injeção de arquivos, use sempre uma URL com escopo ao passar a localização de um arquivo para uma UDF, especialmente quando o chamador da função não for também seu proprietário. Você pode criar uma URL com escopo em SQL usando a função interna BUILD_SCOPED_FILE_URL. Para obter mais informações sobre o que o BUILD_SCOPED_FILE_URL faz, consulte Introdução ao carregamento de dados não estruturados.

Para desenvolver seu código da UDF localmente, adicione o JAR do Snowpark contendo SnowflakeFile ao caminho da classe do seu código. Para obter mais informações sobre snowpark.jar, consulte Configuração do seu ambiente de desenvolvimento para o Snowpark Scala. Observe que os aplicativos clientes do Snowpark não podem usar essa classe.

Quando você usa SnowflakeFile, não é necessário especificar também o arquivo preparado ou o JAR contendo SnowflakeFile com uma cláusula IMPORTS quando você cria a UDF, como no SQL com uma instrução CREATE FUNCTION.

Criação da UDF

O código no exemplo a seguir usa SnowflakeFile para ler um arquivo a partir de um local de estágio especificado. Usando um InputStream do método getInputStream, ele lê o conteúdo do arquivo em uma variável String.

CREATE OR REPLACE FUNCTION sum_total_sales_snowflake_file(file string)
RETURNS INTEGER
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
PACKAGES=('com.snowflake:snowpark:latest')
HANDLER='SalesSum.sumTotalSales'
AS
$$
import java.io.InputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import com.snowflake.snowpark_java.types.SnowflakeFile

object SalesSum {
  @throws(classOf[IOException])
  def sumTotalSales(filePath: String): Int = {
    var total = -1

    // Use a SnowflakeFile instance to read sales data from a stage.
    val file = SnowflakeFile.newInstance(filePath)
    val stream = file.getInputStream()
    val contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8)

    // Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.

    return total
  }
}
$$;
Copy

Chamada da UDF

SELECT sum_total_sales_input_stream(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

Como ler um arquivo especificado dinamicamente com InputStream

Você pode ler o conteúdo do arquivo diretamente em um java.io.InputStream tornando o argumento de sua função de manipulador uma variável InputStream. Isto pode ser útil quando o executor da função quiser passar um caminho de arquivo como um argumento.

Nota

Para tornar seu código resistente a ataques de injeção de arquivo, URLs com escopo são necessários ao passar a localização de um arquivo para uma UDF. Você pode criar uma URL com escopo em SQL usando a função interna BUILD_SCOPED_FILE_URL. Para obter mais informações sobre o que o BUILD_SCOPED_FILE_URL faz, consulte Introdução ao carregamento de dados não estruturados.

Criação da UDF

O código no exemplo a seguir tem uma função de manipulador sumTotalSales que obtém um InputStream e retorna um Int. Durante a execução, o Snowflake atribui automaticamente o conteúdo do arquivo no caminho da variável file para a variável de argumento stream.

CREATE OR REPLACE FUNCTION sum_total_sales_input_stream(file STRING)
RETURNS NUMBER
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER = 'SalesSum.sumTotalSales'
PACKAGES = ('com.snowflake:snowpark:latest')
AS $$
import com.snowflake.snowpark.types.Variant
import java.io.InputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
object SalesSum {
  @throws(classOf[IOException])
  def sumTotalSales(stream: InputStream): Int = {
    val total = -1
    val contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8)

    // Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.

    return total
  }
}
$$;
Copy

Chamada da UDF

SELECT sum_total_sales_input_stream(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy