Este tema describe cómo usar funciones definidas por el usuario (UDF) con código incrustado para integrar código Java o Python directamente en scripts SQL.
Contexto
Las UDF con código incrustado de MaxCompute resuelven los siguientes problemas relacionados con la implementación y el mantenimiento del código:
Implementación de código compleja: después de crear las UDF y desarrollar el código, es necesario compilarlo en Java, además de crear recursos y funciones por separado.
Mantenimiento de código difícil: no es posible consultar directamente la lógica de implementación de las UDF a las que se hace referencia en los scripts SQL ni obtener el código fuente de los paquetes JAR.
-
Baja legibilidad del código: para implementar funciones de biblioteca de Java mediante tipos definidos por el usuario (UDT), se debe convertir el código Java en expresiones dentro de líneas de código extensas. Además, es posible que no se logre expresar cierto código Java como una expresión. Por ejemplo:
Foo f = new Foo(); f.execute(); f.getResult();
Descripción
Las UDF con código incrustado permiten integrar código Java o Python en scripts SQL. Al compilar un script, Janino-compiler identifica y extrae el código incrustado, lo compila en Java y genera recursos de forma dinámica para crear funciones temporales.
Es posible colocar scripts SQL y líneas de código de terceros en el mismo archivo de código fuente. Esto simplifica el uso de UDT o UDF y facilita el desarrollo y mantenimiento diario.
Límites
Solo se puede utilizar Janino-compiler para compilar código Java incrustado. La sintaxis del código Java incrustado debe ser un subconjunto de la sintaxis estándar de JDK. El código Java incrustado tiene los siguientes límites:
No son compatibles las expresiones lambda.
No se pueden especificar varios tipos de excepciones en un solo bloque catch. Por ejemplo, no se permite
catch(Exception1 | Exception2 e).No infiere automáticamente argumentos genéricos. Por ejemplo, no es compatible con
Map map = new HashMap<>();.Se ignoran las expresiones para la inferencia de tipos de argumento. Debe usar expresiones de conversión para especificar el tipo de argumento, como
(String) myMap.get(key).Las aserciones se habilitan forzosamente, incluso si se utiliza la opción -ea de la máquina virtual de Java (JVM).
No es compatible con código programado en versiones posteriores a Java 8.
Referenciar código incrustado en un UDT
A continuación se muestra un ejemplo. Debe enviar las instrucciones SQL en modo script. Para más información, consulte SQL in script mode.
SELECT
s,
com.mypackage.Foo.extractNumber(s)
FROM VALUES ('abc123def'),('apple') AS t(s);
#CODE ('lang'='JAVA')
package com.mypackage;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Foo {
final static Pattern compile = Pattern.compile(".*?([0-9]+).*");
public static String extractNumber(String input) {
final Matcher m = compile.matcher(input);
if (m.find()) {
return m.group(1);
}
return null;
}
}
#END CODE;
#CODEindica el inicio del bloque de código incrustado.#END CODEindica el final del bloque de código incrustado. En este ejemplo, el bloque de código incrustado se coloca al final del script y aplica a todo el script.'lang'='JAVA'indica que el código incrustado está escrito en Java. JAVA puede reemplazarse porPYTHONsi compila código en Python.Puede usar la sintaxis UDT en el script SQL para llamar a
Foo.extractNumber.
Definir y llamar a una UDF de Java con código incrustado
A continuación se muestra un ejemplo. Debe enviar las instrucciones SQL en modo script. Para más información, consulte SQL in script mode.
CREATE TEMPORARY FUNCTION foo AS 'com.mypackage.Reverse' USING
#CODE ('lang'='JAVA')
package com.mypackage;
import com.aliyun.odps.udf.UDF;
public class Reverse extends UDF {
public String evaluate(String input) {
if (input == null) return null;
StringBuilder ret = new StringBuilder();
for (int i = input.toCharArray().length - 1; i >= 0; i--) {
ret.append(input.toCharArray()[i]);
}
return ret.toString();
}
}
#END CODE;
SELECT foo('abdc');
El bloque de código incrustado puede colocarse después de
USINGo al final de un script. El bloque de código colocado después deUSINGaplica únicamente a la instrucciónCREATE TEMPORARY FUNCTION.La función creada mediante
CREATE TEMPORARY FUNCTIONes una función temporal. Esta función temporal surte efecto solo durante el proceso de ejecución actual y no se almacena en el sistema de metadatos de MaxCompute. Para más información sobre cómo crear una función permanente y almacenarla en el sistema de metadatos de MaxCompute, consulte CREATE SQL FUNCTION.
Definir y llamar a una UDTF de Java con código incrustado
A continuación se muestra un ejemplo. Debe enviar las instrucciones SQL en modo script. Para más información, consulte SQL in script mode.
CREATE TEMPORARY FUNCTION foo AS 'com.mypackage.Reverse' USING
#CODE ('lang'='JAVA', 'filename'='embedded.jar')
package com.mypackage;
import com.aliyun.odps.udf.UDTF;
import com.aliyun.odps.udf.UDFException;
import com.aliyun.odps.udf.annotation.Resolve;
@Resolve({"string->string,string"})
public class Reverse extends UDTF {
@Override
public void process(Object[] objects) throws UDFException {
String str = (String) objects[0];
String[] split = str.split(",");
forward(split[0], split[1]);
}
}
#END CODE;
SELECT foo('ab,dc') AS (a,b);
El valor de retorno de @Resolve debe ser del tipo string[]. Sin embargo, Janino-compiler no puede identificar "string->string,string" como string[]. Para solucionar este problema, los parámetros comentados por @Resolve deben encerrarse entre llaves {}. Cuando se crea una UDTF de Java mediante un método común, no se requieren las llaves {}.
Definir y llamar a una UDF de Python con código incrustado
A continuación se muestra un ejemplo. Debe enviar las instrucciones SQL en modo script. Para más información, consulte SQL in script mode.
CREATE TEMPORARY FUNCTION foo AS 'embedded.UDFTest' USING
#CODE ('lang'='PYTHON', 'filename'='embedded')
from odps.udf import annotate
@annotate("bigint->bigint")
class UDFTest(object):
def evaluate(self, a):
return a * a
#END CODE;
SELECT foo(4);
La indentación del código Python debe cumplir con las especificaciones del lenguaje Python.
Al crear una UDF de Python, el nombre de clase que sigue a la cláusula
ASdebe contener el nombre del archivo del código fuente de Python. Puede usar'filename'='embedded'para especificar un nombre de archivo virtual.-
Para más información sobre el desarrollo y uso de diferentes versiones de Python, consulte los siguientes temas:
Python 2: Develop a UDF in Python 2
Python 3: Develop a UDF in Python 3