O MaxCompute introduziu os tipos definidos pelo usuário (UDTs) com base no mecanismo SQL de nova geração. Os UDTs permitem referenciar classes ou objetos de linguagens de programação de terceiros em instruções SQL para chamar métodos ou obter dados.
Quando usar UDTs em vez de UDFs
Tanto os UDTs quanto as funções definidas pelo usuário (UDFs) estendem o SQL do MaxCompute com lógica personalizada. Escolha a abordagem adequada conforme seu fluxo de trabalho:
|
Situação |
Abordagem recomendada |
|
Chamar diretamente um método de classe Java integrado (por exemplo, |
UDT — não requer definição de função |
|
Reutilizar uma biblioteca de terceiros diretamente em uma expressão SQL |
UDT — referencia a classe inline sem encapsulamento |
|
Incluir objetos compilados da linguagem source em jobs de múltiplos estágios |
UDT — encapsula automaticamente o estado da JVM entre estágios |
|
Implementar lógica de negócios reutilizável em vários projetos |
UDF — o registro explícito da função facilita o compartilhamento |
Casos de uso
Chame métodos da biblioteca padrão Java sem definir uma função. Quando uma tarefa precisa de um método de classe Java integrado que o MaxCompute SQL não expõe nativamente, o UDT permite chamá-lo diretamente em uma expressão.
Referencie bibliotecas de terceiros inline. Em vez de encapsular uma função de biblioteca de terceiros dentro de uma UDF, referencie a classe diretamente na instrução SQL.
Incorpore código source compilado no SQL. Para linguagens como Java que exigem compilação, os UDTs permitem referenciar objetos e classes em expressões SQL sem uma etapa separada de registro. Consulte SELECT TRANSFORM para alternativas baseadas em scripts.
Pré-requisitos
Antes de usar UDTs, certifique-se de que:
O JDK 1.8 esteja disponível no seu ambiente. Versões posteriores ao JDK 1.8 podem não ser suportadas.
Os novos tipos de dados estejam ativados caso utilize tipos como INT:
set odps.sql.type.system.odps2=true;
Como funciona
Diferentemente dos UDTs em outros mecanismos SQL (que geralmente definem aliases de tipos semelhantes ao tipo STRUCT), os UDTs do MaxCompute funcionam como uma instrução CREATE TYPE — eles contêm tanto campos quanto métodos, e você os referencia diretamente no SQL sem escrever DDL.
O exemplo a seguir ilustra essa diferença. Para acessar Integer.MAX_VALUE do pacote java.lang do Java:
Usando um UDT (referência direta):
-- Enable new data types (required for types such as INTEGER).
set odps.sql.type.system.odps2=true;
SELECT java.lang.Integer.MAX_VALUE;
Como o pacote java.lang é importado automaticamente (assim como no Java), isso equivale a:
set odps.sql.type.system.odps2=true;
SELECT Integer.MAX_VALUE;
Resultado:
+-----------+
| max_value |
+-----------+
| 2147483647 |
+-----------+
Usando uma UDF (para comparação):
-
Escreva a classe da UDF:
package com.aliyun.odps.test; public class IntegerMaxValue extends com.aliyun.odps.udf.UDF { public Integer evaluate() { return Integer.MAX_VALUE; } } -
Compile, faça o upload e registre:
add jar odps-test.jar; create function integer_max_value as 'com.aliyun.odps.test.IntegerMaxValue' using 'odps-test.jar'; -
Execute a chamada:
select integer_max_value();
Os UDTs reduzem esse processo a uma única instrução SQL.
Execução em múltiplos estágios
Objetos UDT fluem naturalmente entre os estágios do MapReduce. O exemplo a seguir une duas colunas BigInteger calculadas a partir de fontes de dados diferentes:
-- Sample data.
@table1 := select * from values ('100000000000000000000') as t(x);
@table2 := select * from values (100L) as t(y);
-- Create an object with the new method.
@a := select new java.math.BigInteger(x) x from @table1;
-- Call a static method.
@b := select java.math.BigInteger.valueOf(y) y from @table2;
-- Call an instance method across the join.
select /*+mapjoin(b)*/ x.add(y).toString() from @a a join @b b;
-- Output:
100000000000000000100
Esse job é executado em três estágios (M1, R2, J3). A expressão new java.math.BigInteger(x) roda no M1; já java.math.BigInteger.valueOf(y) e x.add(y).toString() rodam no J3, em processos e máquinas físicas distintos. O UDT encapsula essa complexidade para que todos os estágios se comportem como se estivessem rodando na mesma Java Virtual Machine (JVM).
O DAG deste job SQL de exemplo com UDT contém três estágios: M1 → R2_1 → J3_2. Todos os estágios estão 100% concluídos, com 1 linha de dados transmitida entre cada estágio.
A coluna x da variável a é do tipo java.math.BigInteger, e não um tipo integrado. Esse valor UDT pode ser passado para outros operadores e usado no redistribuição de dados.
Referenciar pacotes JAR e definir imports Java
Todas as classes do SDK for Java estão disponíveis para UDTs por padrão. Para referenciar pacotes JAR adicionais ou definir caminhos de importação padrão, utilize os seguintes sinalizadores de sessão.
Referenciar um pacote JAR:
set odps.sql.type.system.odps2=true;
set odps.sql.session.resources=odps-test.jar;
-- The JAR must be uploaded to the project beforehand.
select new com.aliyun.odps.test.IntegerMaxValue().evaluate();
É possível especificar vários recursos separados por vírgulas: set odps.sql.session.resources=foo.sh,bar.txt;
O parâmetro odps.sql.session.resources controla tanto UDTs quanto SELECT TRANSFORM. Um JAR definido aqui fica disponível para ambos os recursos.
Definir um caminho de importação Java padrão:
set odps.sql.type.system.odps2=true;
set odps.sql.session.resources=odps-test.jar;
set odps.sql.session.java.imports=com.aliyun.odps.test.*;
-- With the import set, you can omit the full package prefix.
select new IntegerMaxValue().evaluate();
O parâmetro odps.sql.session.java.imports aceita um classpath (por exemplo, java.math.BigInteger) ou um curinga (*). Imports estáticos não são suportados.
Operações suportadas
Os UDTs suportam as seguintes operações em expressões SQL:
Criação de objetos usando
new— exemplo:new java.math.BigInteger('123')Criação de arrays usando
newcom listas de inicialização — exemplo:new Integer[] { 1, 2, 3 }Chamada de métodos de instância e estáticos
Acesso a campos públicos de instância e estáticos
Apenas métodos e campos públicos são acessíveis. Todos os identificadores (nomes de pacotes, classes, métodos e campos) diferenciam maiúsculas de minúsculas. Classes anônimas e expressões lambda não são suportadas. Funções que não retornam valores não podem ser chamadas em expressões.
Tipos de dados
Mapeamento de tipos
Os tipos de dados Java são mapeados para os tipos integrados do MaxCompute. O mesmo mapeamento utilizado nas UDFs Java aplica-se aos UDTs.
Chame métodos de tipos integrados diretamente:
'123'.length(),1L.hashCode()Utilize UDTs em funções integradas:
chr(Long.valueOf('100'))—Long.valueOfretornajava.lang.Long, que é mapeado para o tipo integrado BIGINTOs tipos primitivos Java são convertidos automaticamente para seus tipos de boxing correspondentes
Para utilizar os novos tipos de dados integrados, adicione set odps.sql.type.system.odps2=true; antes de executar a consulta.
Conversões de tipo
Conversões de tipo SQL são suportadas:
cast(1 as java.lang.Object)Casts no estilo Java não são suportados:
(Object)1Objetos UDT podem ser convertidos implicitamente para objetos da classe base
Objetos UDT podem ser convertidos explicitamente (cast) para objetos da classe base ou subclasses
A conversão entre dois tipos não relacionados segue as mesmas regras da conversão de tipos integrados. Por exemplo, converter
java.lang.Longparajava.lang.Integeraplica as mesmas regras da conversão de BIGINT para INT, o que pode resultar em perda de dados.
Objetos UDT não podem ser salvos em disco nem inseridos diretamente em tabelas (o DDL não suporta UDTs como tipo de coluna). Se o valor UDT puder ser convertido implicitamente para um tipo integrado, ele poderá ser gravado em uma tabela. O tipo BINARY suporta serialização automática — arraysbyte[]podem ser salvos e desserializados. Para persistir um UDT, converta-o para BINARY usando métodos de serialização e desserialização. Valores UDT não podem aparecer diretamente na saída final. ChametoString()para converter qualquer UDT emjava.lang.Stringpara exibição. Para converter toda a saída UDT em strings automaticamente durante a depuração, use: Este sinalizador aplica-se apenas a instruções PRINT, não a instruções INSERT.
set odps.sql.udt.display.tostring=true;
Genéricos
Os UDTs suportam genéricos Java. O compilador infere o parâmetro de tipo a partir do argumento:
-- Returns java.util.List<java.math.BigInteger>
java.util.Arrays.asList(new java.math.BigInteger('1'))
Especifique os parâmetros de tipo explicitamente nas chamadas de construtor ou use java.lang.Object:
-- ArrayList<Object>
new java.util.ArrayList(java.util.Arrays.asList('1', '2'))
-- ArrayList<String>
new java.util.ArrayList<String>(java.util.Arrays.asList('1', '2'))
Semântica de operadores
Todos os operadores seguem a semântica do MaxCompute SQL, e não a do Java:
Concatenação de strings:
String.valueOf(1) + String.valueOf(2)retorna3(ambas as strings são convertidas implicitamente para DOUBLE e somadas). Para concatenar como strings, utilize uma função de concatenação de strings.Igualdade: O operador
=é um operador de comparação SQL, não uma verificação de igualdade de referência Java. Use o métodoequalspara verificar se dois objetos são equivalentes.
Igualdade de objetos e redistribuição de dados
Os UDTs não possuem uma definição clara de igualdade de objetos. Durante a redistribuição de dados, os objetos podem ser transmitidos entre processos e máquinas físicas, fazendo com que um único objeto apareça como duas referências distintas. Sempre use o método equals — e não = — para comparar objetos UDT.
Objetos dentro da mesma linha ou coluna são correlacionados, mas a correlação entre linhas ou colunas diferentes não é garantida.
Limitações
Os UDTs não podem ser usados como chaves de redistribuição nas cláusulas JOIN, GROUP BY, DISTRIBUTE BY, SORT BY, ORDER BY ou CLUSTER BY. Os UDTs são válidos em expressões nessas etapas, mas não podem ser a saída. Por exemplo:
group by new java.math.BigInteger('123')— não suportadogroup by new java.math.BigInteger('123').hashCode()— suportado, poishashCode()retornaint.class, que é mapeado para o tipo integrado INT
UDFs, funções de agregação definidas pelo usuário (UDAFs) e UDTs não podem ler dados dos seguintes tipos de tabela:
Tabelas nas quais foi realizada evolução de schema
Tabelas que contêm tipos de dados complexos
Tabelas que contêm tipos de dados JSON
Tabelas transacionais
Acessar recursos
No MaxCompute SQL, chame o método estático com.aliyun.odps.udf.impl.UDTExecutionContext.get() para obter o objeto ExecutionContext. Utilize esse objeto para acessar o contexto de execução atual, incluindo arquivos e tabelas registrados como recursos.
Considerações sobre desempenho
O desempenho dos UDTs é semelhante ao das UDFs. O mecanismo de computação otimizado oferece melhorias adicionais em cenários específicos:
Sem sobrecarga de serialização para operações locais. Quando um objeto UDT é usado dentro do mesmo processo (sem necessidade de redistribuição de dados, como em estágios JOIN ou AGGREGATE), a serialização e a desserialização são ignoradas.
Runtime baseado em Codegen. Os UDTs são executados via Codegen em vez de reflexão, eliminando a sobrecarga associada à reflexão. Múltiplas chamadas UDT são agrupadas em uma única chamada de função — por exemplo,
values[x].add(values[y]).divide(java.math.BigInteger.valueOf(2))é chamado apenas uma vez, evitando a sobrecarga de interface por chamada.
Segurança
Os UDTs estão sujeitos ao mesmo modelo de sandbox Java que as UDFs. Para realizar operações restritas pela sandbox, cancele o isolamento da sandbox para essas operações ou solicite inclusão na lista de permissões da sandbox.
Recursos a serem aprimorados
Os seguintes recursos estão planejados para versões futuras:
Chamada de funções que não retornam valores e funções que usam diretamente os dados transferidos (onde o valor de retorno é ignorado, como o método
addda interface List).Uso de classes anônimas e expressões lambda.
Uso de UDTs como chaves de redistribuição.
Suporte a mais linguagens de programação, como Python.
Próximos passos
UDFs Java — tabela de mapeamento de tipos de dados e referência de implementação de UDF
Novos tipos de dados integrados — tipos que exigem
set odps.sql.type.system.odps2=true;SELECT TRANSFORM — incorpore scripts em instruções SQL
COLLECT_SET e outras funções de agregação — use com UDTs para implementar comportamento de funções de agregação e funções com valor de tabela
Sandbox Java — modelo de sandbox e solicitação de lista de permissões