Criação de UDFs de Java¶
Este tópico ajuda você a criar UDFs de Java.
Neste tópico:
Como escolher seus tipos de dados¶
Antes de escrever seu código:
Escolha os tipos de dados que sua função deve aceitar como argumentos e o tipo de dados que sua função deve retornar.
Leve em conta questões relacionadas ao fuso horário.
Decida como lidar com valores NULL.
Mapeamentos de tipos de dados SQL-Java para tipos de parâmetros e retorno¶
Para obter mais informações sobre como o Snowflake converte entre os tipos de dados Java e SQL, consulte Mapeamentos de tipos de dados entre linguagens do manipulador e SQL.
Valores TIMESTAMP_LTZ e fusos horários¶
Uma UDF de Java fica bastante isolada do ambiente no qual é chamada. Entretanto, o fuso horário é herdado do ambiente de chamada. Se a sessão do chamador definir um fuso horário padrão antes de chamar a UDF de Java, a UDF de Java tem o mesmo fuso horário padrão. A UDF de Java utiliza os mesmos dados da base de dados de fuso horário IANA usados pelo TIMEZONE SQL do Snowflake nativo (isso é, dados do lançamento 2021a do banco de dados de fuso horário).
Valores NULL¶
O Snowflake oferece suporte para dois valores NULL distintos: SQL NULL
e JSON de VARIANT null
. (Para obter mais informações sobre o VARIANT NULL do Snowflake, consulte Valores NULL).
O Java oferece suporte para um valor null
, que é apenas para tipos de dados não-primitivos.
Um argumento SQL NULL
para uma UDF de Java é convertido para o valor de Java null
, mas somente para tipos de dados de Java que oferecem suporte para null
.
Um valor de Java null
retornado é convertido de volta para um NULL
de SQL.
Arrays e número variável de argumentos¶
UDFs de Java podem receber arrays de qualquer um dos seguintes tipos de dados de Java:
Cadeia de caracteres
booleano
double
float
int
long
short
O tipo de dados dos valores de SQL passados deve ser compatível com o tipo de dados de Java correspondente. Para obter mais detalhes sobre compatibilidade de tipos de dados, consulte Mapeamentos de tipos de dados SQL-Java.
As seguintes regras adicionais se aplicam para cada um dos tipos de dados de Java especificados:
boolean: a ARRAY do Snowflake deve conter somente elementos BOOLEAN, e não deve conter nenhum valor NULL.
int/short/long: a ARRAY do Snowflake deve conter somente elementos de ponto fixo com uma escala de 0, e não deve conter nenhum valor NULL.
float/double: a ARRAY do Snowflake deve conter um dos seguintes:
Elementos FLOAT.
Elementos de ponto fixo (com qualquer escala).
A ARRAY não deve conter nenhum valor NULL.
Os métodos de Java podem receber essas arrays de uma das duas maneiras a seguir:
Usando o recurso de array do Java.
Usando o recurso varargs (número variável de argumentos) do Java.
Em ambos os casos, seu código de SQL deve passar uma ARRAY.
Passando por uma ARRAY¶
Declare o parâmetro de Java como uma array. Por exemplo, o terceiro parâmetro no método a seguir é uma array de cadeias de caracteres:
static int myMethod(int fixedArgument1, int fixedArgument2, String[] stringArray)
Abaixo está um exemplo completo:
Criar e carregar a tabela:
CREATE TABLE string_array_table(id INTEGER, a ARRAY); INSERT INTO string_array_table (id, a) SELECT 1, ARRAY_CONSTRUCT('Hello'); INSERT INTO string_array_table (id, a) SELECT 2, ARRAY_CONSTRUCT('Hello', 'Jay'); INSERT INTO string_array_table (id, a) SELECT 3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');Crie a UDF:
create or replace function concat_varchar_2(a ARRAY) returns varchar language java handler='TestFunc_2.concatVarchar2' target_path='@~/TestFunc_2.jar' as $$ class TestFunc_2 { public static String concatVarchar2(String[] strings) { return String.join(" ", strings); } } $$;Chame a UDF:
SELECT concat_varchar_2(a) FROM string_array_table ORDER BY id; +---------------------+ | CONCAT_VARCHAR_2(A) | |---------------------| | Hello | | Hello Jay | | Hello Jay Smith | +---------------------+
Como passar usando o Varargs¶
O uso do varargs é muito semelhante ao uso de uma array.
Em seu código de Java, use o estilo de declaração do varargs de Java:
static int myMethod(int fixedArgument1, int fixedArgument2, String ... stringArray)
Abaixo está um exemplo completo. A única diferença significativa entre este exemplo e o exemplo anterior (para arrays) é a declaração dos parâmetros para o método.
Criar e carregar a tabela:
CREATE TABLE string_array_table(id INTEGER, a ARRAY); INSERT INTO string_array_table (id, a) SELECT 1, ARRAY_CONSTRUCT('Hello'); INSERT INTO string_array_table (id, a) SELECT 2, ARRAY_CONSTRUCT('Hello', 'Jay'); INSERT INTO string_array_table (id, a) SELECT 3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');Crie a UDF:
create or replace function concat_varchar(a ARRAY) returns varchar language java handler='TestFunc.concatVarchar' target_path='@~/TestFunc.jar' as $$ class TestFunc { public static String concatVarchar(String ... stringArray) { return String.join(" ", stringArray); } } $$;Chame a UDF:
SELECT concat_varchar(a) FROM string_array_table ORDER BY id; +-------------------+ | CONCAT_VARCHAR(A) | |-------------------| | Hello | | Hello Jay | | Hello Jay Smith | +-------------------+
Criação de UDFs de Java que ficam dentro das restrições impostas pelo Snowflake¶
Para obter mais informações sobre a criação do código do manipulador que funciona bem no Snowflake, consulte Criação de manipuladores que ficam dentro das restrições impostas pelo Snowflake.
Criação da classe¶
Quando uma instrução de SQL chama sua UDF de Java, o Snowflake chama um método de Java que você escreveu. Seu método de Java é chamado de “método do manipulador”, ou “manipulador” abreviadamente.
Como em qualquer método de Java, seu método deve ser declarado como parte de uma classe. Seu método do manipulador pode ser um método estático ou um método de instância da classe. Se seu manipulador é um método de instância e sua classe definir um construtor sem argumentos, o Snowflake invocará seu construtor no momento da inicialização para criar uma instância de sua classe. Se seu manipulador for um método estático, sua classe não é obrigada a ter um construtor.
O manipulador é chamado uma vez para cada linha passada para a UDF de Java. (Nota: uma nova instância da classe não é criada para cada linha; o Snowflake pode chamar o mesmo método do manipulador da mesma instância mais de uma vez, ou chamar o mesmo método estático mais de uma vez).
Para otimizar a execução de seu código, o Snowflake supõe que a inicialização pode ser lenta, enquanto a execução do método do manipulador é rápida. O Snowflake define um tempo limite mais longo para executar a inicialização (incluindo o tempo para carregar sua UDF e o tempo para chamar o construtor do método do manipulador contendo a classe, se um construtor estiver definido) do que para executar o manipulador (o tempo para chamar seu manipulador com uma linha de entrada).
Informações adicionais sobre a criação da classe podem ser encontradas em Como criar um manipulador de UDF de Java.
Como otimizar a inicialização e controlar o estado global em UDFs escalares¶
A maioria dos manipuladores de função e procedimento deve seguir as diretrizes abaixo:
Se você precisar inicializar o estado compartilhado que não muda entre linhas, inicialize-o fora da função do manipulador, como no módulo e ou no construtor.
Escreva seu método ou função do manipulador de forma a ser thread-safe.
Evite armazenar e compartilhar o estado dinâmico em linhas diferentes.
Se sua UDF não puder seguir essas diretrizes, ou se você quiser entender mais profundamente as razões para essas diretrizes, leia as próximas subseções.
Entendendo a paralelização de UDF de Java¶
Para melhorar o desempenho, o Snowflake paraleliza tanto entre JVMs diferente quanto dentro de JVMs.
Entre JVMs diferentes:
O Snowflake paralela entre os trabalhadores em um warehouse. Cada trabalhador executa uma (ou mais) JVMs. Isso significa que não existe um estado global compartilhado. No máximo, o estado só pode ser compartilhado dentro de uma única JVM.
Dentro de JVMs:
Cada JVM pode executar vários threads que podem chamar o mesmo método do manipulador da mesma instância em paralelo. Isso significa que cada método do manipulador precisa ser thread-safe.
Se uma UDF é IMMUTABLE e uma instrução SQL chama a UDF mais de uma vez com os mesmos argumentos para a mesma linha, a UDF retorna o mesmo valor para cada chamada para aquela linha. Por exemplo, o código a seguir retorna o mesmo valor duas vezes para cada linha se a UDF for IMMUTABLE:
select my_java_udf(42), my_java_udf(42) from table1;
Se você quiser que múltiplas chamadas retornem valores independentes mesmo quando passarem os mesmos argumentos não quiser declarar a função como VOLATILE, ligue diferentes chamadas a UDFs separadas ao mesmo método do manipulador. Por exemplo:
Crie um arquivo JAR chamado
@java_udf_stage/rand.jar
com o código:class MyClass { private double x; // Constructor public MyClass() { x = Math.random(); } // Handler public double myHandler() { return x; } }
Crie UDFs de Java da forma mostrada abaixo. Essas UDFs têm nomes diferentes, mas usam o mesmo arquivo JAR e o mesmo manipulador dentro desse arquivo JAR.
create function my_java_udf_1() returns double language java imports = ('@java_udf_stage/rand.jar') handler = 'MyClass.myHandler'; create function my_java_udf_2() returns double language java imports = ('@java_udf_stage/rand.jar') handler = 'MyClass.myHandler';
O código a seguir chama ambas as UDFs. As UDFs apontam para o mesmo arquivo e manipulador JAR. Essas chamadas criam duas instâncias da mesma classe. Cada instância retorna um valor independente, portanto o exemplo abaixo retorna dois valores independentes em vez de retornar o mesmo valor duas vezes:
select my_java_udf_1(), my_java_udf_2() from table1;
Como armazenar informações de estado de uma JVM¶
Uma razão para evitar confiar no estado dinâmico compartilhado é que as linhas não são necessariamente processadas em uma ordem previsível. Cada vez que uma instrução SQL é executada, o Snowflake pode variar o número de lotes, a ordem em que os lotes são processados e a ordem das linhas dentro de um lote. Se uma UDF escalar for projetada para que uma linha afete o valor de retorno para uma linha posterior, a UDF pode retornar resultados diferentes cada vez que a UDF for executada.
Tratamento de erros¶
Um método de Java usado como uma UDF pode usar as técnicas normais de tratamento de exceções de Java para detectar erros dentro do método.
Se uma exceção ocorre dentro do método e não é capturada pelo método, o Snowflake gera um erro que inclui o traço da pilha para a exceção. Quando o registro de exceções não tratadas está ativado, o Snowflake registra dados sobre exceções não tratadas em uma tabela de eventos.
Você pode gerar uma exceção explicitamente sem capturá-la para encerrar a consulta e produzir um erro de SQL. Por exemplo:
if (x < 0) {
throw new IllegalArgumentException("x must be non-negative.");
}
Ao depurar, você pode incluir valores no texto da mensagem de erro do SQL. Para fazer isso, coloque todo um corpo de método de Java em um bloco try-catch; anexe valores de argumento à mensagem do erro capturado; e gere uma exceção com a mensagem estendida. Para evitar revelar dados sensíveis, remova valores de argumento antes de enviar arquivos JAR para um ambiente de produção.
Práticas recomendadas¶
Escreva código independente de plataforma.
Evite códigos que suponham uma arquitetura de CPU específica (por exemplo, x86).
Evite códigos que suponham um sistema operacional específico.
Se você precisar executar o código de inicialização e não quiser incluí-lo no método que você chama, você pode colocar o código de inicialização em um bloco de inicialização estático.
Sempre que possível ao usar um manipulador em linha, especifique um valor para o parâmetro CREATE FUNCTION ou CREATE PROCEDURE TARGET_PATH. Isso solicitará que o Snowflake reutilize a saída do código do manipulador gerado anteriormente, em vez de recompilar para cada chamada. Para obter mais informações, consulte Uso de um manipulador inline.
Consulte também:
Boas práticas de segurança¶
Para ajudar a garantir que seu manipulador funcione de forma segura, consulte as práticas recomendadas descritas em Práticas de segurança para UDFs e procedimentos.