MaxCompute introduce tipos definidos por el usuario (UDT) basados en el motor SQL de nueva generación. Los UDT permiten hacer referencia a clases u objetos de lenguajes de programación de terceros en instrucciones SQL para invocar métodos u obtener datos.
Cuándo usar UDT frente a UDF
Tanto los UDT como las funciones definidas por el usuario (UDF) extienden MaxCompute SQL con lógica personalizada. Elija la opción adecuada según su flujo de trabajo:
|
Situación |
Enfoque recomendado |
|
Invocar directamente un método de clase integrado de Java (por ejemplo, |
UDT: no requiere definición de función |
|
Reutilizar una biblioteca de terceros directamente en una expresión SQL |
UDT: referencia la clase en línea sin necesidad de envolverla |
|
Incluir objetos compilados del lenguaje fuente en trabajos de múltiples etapas |
UDT: encapsula automáticamente el estado de la JVM entre etapas |
|
Implementar lógica de negocio reutilizable en varios proyectos |
UDF: el registro explícito de la función facilita su uso compartido |
Casos de uso
Invocar métodos de la biblioteca estándar de Java sin definir una función. Cuando una tarea necesita un método de clase integrado de Java que MaxCompute SQL no expone de forma nativa, un UDT permite invocarlo directamente en una expresión.
Hacer referencia a bibliotecas de terceros en línea. En lugar de envolver una función de biblioteca de terceros dentro de una UDF, haga referencia a la clase directamente en una instrucción SQL.
Insertar código fuente compilado en SQL. Para lenguajes como Java que requieren compilación, los UDT permiten referenciar objetos y clases en expresiones SQL sin un paso de registro separado. Consulte SELECT TRANSFORM para conocer alternativas basadas en scripts.
Requisitos previos
Antes de utilizar UDT, asegúrese de que:
JDK 1.8 esté disponible en su entorno. Es posible que las versiones posteriores a JDK 1.8 no sean compatibles.
Los nuevos tipos de datos estén habilitados si utiliza tipos como INT:
set odps.sql.type.system.odps2=true;
Funcionamiento
A diferencia de los UDT en otros motores SQL (que normalmente definen alias de tipos similares al tipo STRUCT), los UDT de MaxCompute funcionan como una instrucción CREATE TYPE: contienen tanto campos como métodos, y se referencian directamente en SQL sin escribir DDL.
El siguiente ejemplo ilustra la diferencia. Para acceder a Integer.MAX_VALUE del paquete java.lang de Java:
Uso de un UDT (referencia directa):
-- Enable new data types (required for types such as INTEGER).
set odps.sql.type.system.odps2=true;
SELECT java.lang.Integer.MAX_VALUE;
Dado que java.lang se importa automáticamente (como en Java), esto equivale a:
set odps.sql.type.system.odps2=true;
SELECT Integer.MAX_VALUE;
Resultado:
+-----------+
| max_value |
+-----------+
| 2147483647 |
+-----------+
Uso de una UDF (para comparación):
-
Escriba la clase UDF:
package com.aliyun.odps.test; public class IntegerMaxValue extends com.aliyun.odps.udf.UDF { public Integer evaluate() { return Integer.MAX_VALUE; } } -
Compile, cargue y registre:
add jar odps-test.jar; create function integer_max_value as 'com.aliyun.odps.test.IntegerMaxValue' using 'odps-test.jar'; -
Invoque la función:
select integer_max_value();
Los UDT reducen este proceso a una sola instrucción SQL.
Ejecución en múltiples etapas
Los objetos UDT fluyen naturalmente a través de las etapas de MapReduce. El siguiente ejemplo combina dos columnas BigInteger calculadas a partir de diferentes fuentes de datos:
-- 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
Este trabajo se ejecuta en tres etapas (M1, R2, J3). new java.math.BigInteger(x) se ejecuta en M1; java.math.BigInteger.valueOf(y) y x.add(y).toString() se ejecutan en J3, en diferentes procesos y máquinas físicas. El UDT encapsula esta operación para que todas las etapas se comporten como si se ejecutaran en la misma máquina virtual de Java (JVM).
El DAG de este trabajo SQL de ejemplo con UDT contiene tres etapas: M1 → R2_1 → J3_2. Todas las etapas están completas al 100%, con 1 fila de datos transferida entre cada etapa.
La columna x de la variable a es del tipo java.math.BigInteger en lugar de un tipo integrado. Este valor UDT puede pasarse a otros operadores y utilizarse en la redistribución de datos.
Referenciar paquetes JAR y configurar importaciones de Java
Todas las clases del SDK para Java están disponibles para los UDT de forma predeterminada. Para referenciar paquetes JAR adicionales o establecer rutas de importación predeterminadas, utilice los siguientes indicadores de sesión.
Referenciar un paquete 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();
Puede especificar varios recursos separados por comas: set odps.sql.session.resources=foo.sh,bar.txt;
odps.sql.session.resources controla tanto los UDT como SELECT TRANSFORM. Un JAR configurado aquí está disponible para ambas funcionalidades.
Establecer una ruta de importación de Java predeterminada:
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();
odps.sql.session.java.imports acepta una ruta de clase (por ejemplo, java.math.BigInteger) o un comodín (*). Las importaciones estáticas no son compatibles.
Operaciones compatibles
Los UDT admiten las siguientes operaciones en expresiones SQL:
Crear objetos mediante
new— ejemplo:new java.math.BigInteger('123')Crear arreglos mediante
newcon listas de inicialización — ejemplo:new Integer[] { 1, 2, 3 }Invocar métodos de instancia y estáticos
Acceder a campos públicos de instancia y estáticos
Solo se puede acceder a métodos y campos públicos. Todos los identificadores (nombres de paquetes, nombres de clases, nombres de métodos, nombres de campos) distinguen entre mayúsculas y minúsculas. No se admiten clases anónimas ni expresiones lambda. Las funciones que no devuelven valores no pueden invocarse en expresiones.
Tipos de datos
Mapeo de tipos
Los tipos de datos de Java se asignan a los tipos integrados de MaxCompute. El mismo mapeo utilizado en Java UDFs se aplica a los UDT.
Invoque métodos de tipos integrados directamente:
'123'.length(),1L.hashCode()Utilice UDT en funciones integradas:
chr(Long.valueOf('100'))—Long.valueOfdevuelvejava.lang.Long, que se asigna al tipo integrado BIGINTLos tipos primitivos de Java se convierten automáticamente a sus tipos de envoltura (boxing)
Para new built-in data types, agregue set odps.sql.type.system.odps2=true; antes de ejecutar la consulta.
Conversiones de tipos
Se admiten conversiones de tipos SQL:
cast(1 as java.lang.Object)No se admiten conversiones al estilo Java:
(Object)1Los objetos UDT pueden convertirse implícitamente a objetos de la clase base
Los objetos UDT pueden convertirse explícitamente (cast) a objetos de la clase base o subclase
La conversión entre dos tipos no relacionados sigue las mismas reglas que la conversión de tipos integrados. Por ejemplo, convertir
java.lang.Longajava.lang.Integeraplica las mismas reglas que convertir BIGINT a INT, lo cual puede provocar pérdida de datos.
Los objetos UDT no pueden guardarse en disco ni insertarse directamente en tablas (DDL no admite UDT como tipo de columna). Si el valor UDT puede convertirse implícitamente a un tipo integrado, puede escribirse en una tabla. BINARY admite serialización automática: los arreglosbyte[]pueden guardarse y deserializarse. Para conservar un UDT, conviértalo a BINARY mediante métodos de serialización y deserialización. Los valores UDT no pueden aparecer directamente en la salida final. InvoquetoString()para convertir cualquier UDT ajava.lang.Stringpara su visualización. Para convertir automáticamente toda la salida UDT a cadenas durante la depuración, use: Este indicador solo se aplica a las instrucciones PRINT, no a las instrucciones INSERT.
set odps.sql.udt.display.tostring=true;
Genéricos
Los UDT admiten genéricos de Java. El compilador infiere el parámetro de tipo a partir del argumento:
-- Returns java.util.List<java.math.BigInteger>
java.util.Arrays.asList(new java.math.BigInteger('1'))
Especifique los parámetros de tipo explícitamente en las llamadas al constructor o utilice 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 los operadores siguen la semántica de MaxCompute SQL, no la de Java:
Concatenación de cadenas:
String.valueOf(1) + String.valueOf(2)devuelve3(ambas cadenas se convierten implícitamente a DOUBLE y se suman). Para concatenar como cadenas, utilice en su lugar una función de concatenación de cadenas.Igualdad: El operador
=es un operador de comparación SQL, no una igualdad de referencia de Java. Utilice el métodoequalspara comprobar si dos objetos son equivalentes.
Igualdad de objetos y redistribución de datos
Los UDT no tienen una definición clara de igualdad de objetos. Durante la redistribución de datos, los objetos pueden transmitirse a través de procesos y máquinas físicas, lo que provoca que un solo objeto aparezca como dos referencias distintas. Utilice siempre el método equals —no =— para comparar objetos UDT.
Los objetos dentro de la misma fila o columna están correlacionados, pero no se garantiza la correlación entre filas o columnas.
Limitaciones
Los UDT no pueden usarse como claves de redistribución en cláusulas JOIN, GROUP BY, DISTRIBUTE BY, SORT BY, ORDER BY o CLUSTER BY. Los UDT son válidos en expresiones dentro de estas etapas, pero no pueden ser la salida. Por ejemplo:
group by new java.math.BigInteger('123')— no compatiblegroup by new java.math.BigInteger('123').hashCode()— compatible, porquehashCode()devuelveint.class, que se asigna al tipo integrado INT
Las UDF, las funciones de agregación definidas por el usuario (UDAF) y los UDT no pueden leer datos de los siguientes tipos de tablas:
Tablas en las que se ha realizado evolución de esquema
Tablas que contienen tipos de datos complejos
Tablas que contienen tipos de datos JSON
Tablas transaccionales
Acceso a recursos
En MaxCompute SQL, invoque el método estático com.aliyun.odps.udf.impl.UDTExecutionContext.get() para obtener el objeto ExecutionContext. Utilice este objeto para acceder al contexto de ejecución actual, incluidos archivos y tablas registrados como recursos.
Consideraciones de rendimiento
El rendimiento de los UDT es similar al de las UDF. El motor de computación optimizado ofrece mejoras adicionales en escenarios específicos:
Sin sobrecarga de serialización para operaciones locales. Cuando un objeto UDT se utiliza dentro del mismo proceso (sin necesidad de redistribución de datos, como en etapas JOIN o AGGREGATE), se omite la serialización y deserialización.
Entorno de ejecución basado en Codegen. Los UDT se ejecutan mediante Codegen en lugar de reflexión, por lo que no hay sobrecarga por reflexión. Varias llamadas UDT se agrupan en una sola llamada de función; por ejemplo,
values[x].add(values[y]).divide(java.math.BigInteger.valueOf(2))se invoca una sola vez, evitando la sobrecarga de interfaz por cada llamada.
Seguridad
Los UDT están sujetos al mismo modelo de Java sandbox que las UDF. Para realizar operaciones restringidas por el sandbox, cancele el aislamiento del sandbox para dichas operaciones o solicite su inclusión en la lista blanca del sandbox.
Funcionalidades por mejorar
Las siguientes funcionalidades están planificadas para versiones futuras:
Invocar funciones que no devuelven valores y funciones que utilizan directamente datos transferidos (donde se ignora el valor de retorno, como el método
addde la interfaz List).Utilizar clases anónimas y expresiones lambda.
Usar UDT como claves de redistribución.
Admitir más lenguajes de programación, como Python.
Pasos siguientes
Java UDFs — tabla de mapeo de tipos de datos y referencia de implementación de UDF
New built-in data types — tipos que requieren
set odps.sql.type.system.odps2=true;SELECT TRANSFORM — insertar scripts en instrucciones SQL
COLLECT_SET y other aggregate functions — uso conjunto con UDT para implementar comportamiento de funciones de agregación y funciones con valor de tabla
Java sandbox — modelo de sandbox y solicitud de lista blanca