Cette rubrique explique comment utiliser les fonctions définies par l'utilisateur (UDF) avec code intégré pour incorporer du code Java ou Python directement dans des scripts SQL.
Contexte
Les UDF avec code intégré de MaxCompute résolvent les problèmes suivants liés à l'implémentation et à la maintenance du code :
Complexité de l'implémentation : après avoir créé des UDF et développé le code, vous devez compiler le code Java, puis créer les ressources et les fonctions associées.
Maintenance difficile : il est impossible d'afficher directement la logique d'implémentation des UDF référencées dans les scripts SQL ou d'obtenir le code source des packages JAR.
-
Lisibilité réduite : pour implémenter des fonctions de bibliothèque Java à l'aide de types définis par l'utilisateur (UDT), vous devez convertir le code Java en expressions sur de longues lignes. De plus, certaines instructions Java ne peuvent pas être écrites sous forme d'expressions. Exemple :
Foo f = new Foo(); f.execute(); f.getResult();
Description
Les UDF avec code intégré vous permettent d'incorporer du code Java ou Python dans des scripts SQL. Lors de la compilation d'un script, Janino-compiler identifie et extrait le code intégré, le compile en Java, puis génère dynamiquement les ressources et crée des fonctions temporaires.
Vous pouvez placer les scripts SQL et les lignes de code tierces dans le même fichier source. Cette approche simplifie l'utilisation des UDT ou des UDF et facilite le développement et la maintenance au quotidien.
Limites
Seul Janino-compiler peut compiler le code Java intégré. La syntaxe du code Java intégré doit être un sous-ensemble de la syntaxe JDK standard. Le code Java intégré présente les limites suivantes :
Les expressions Lambda ne sont pas prises en charge.
Vous ne pouvez pas spécifier plusieurs types d'exceptions dans un seul bloc catch. Par exemple,
catch(Exception1 | Exception2 e)n'est pas autorisé.L'inférence automatique des arguments génériques n'est pas prise en charge. Par exemple,
Map map = new HashMap<>();n'est pas pris en charge.Les expressions d'inférence d'arguments de type sont ignorées. Vous devez utiliser des expressions de cast pour spécifier le type d'argument, comme
(String) myMap.get(key).Les assertions sont activées de force, même si l'option -ea de la machine virtuelle Java (JVM) est utilisée.
Le code programmé dans des versions ultérieures à Java 8 n'est pas pris en charge.
Référencer du code intégré dans un UDT
L'exemple suivant illustre cette procédure. Soumettez les instructions SQL en mode script. Pour plus d'informations, consultez SQL en mode script.
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;
#CODEindique le début du bloc de code intégré.#END CODEindique la fin du bloc de code intégré. Dans cet exemple, le bloc de code intégré est placé à la fin du script et s'applique à l'ensemble du script.'lang'='JAVA'indique que le code intégré est écrit en Java. JAVA peut être remplacé parPYTHONsi vous compilez du code en Python.Utilisez la syntaxe UDT dans le script SQL pour appeler
Foo.extractNumber.
Définir et appeler une UDF Java avec code intégré
L'exemple suivant illustre cette procédure. Soumettez les instructions SQL en mode script. Pour plus d'informations, consultez SQL en mode script.
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');
Le bloc de code intégré peut être placé après
USINGou à la fin d'un script. Le bloc de code placé aprèsUSINGs'applique uniquement à l'instructionCREATE TEMPORARY FUNCTION.La fonction créée par
CREATE TEMPORARY FUNCTIONest une fonction temporaire. Cette fonction temporaire prend effet uniquement pendant le processus d'exécution actuel et n'est pas stockée dans le système de métadonnées MaxCompute. Pour plus d'informations sur la création d'une fonction permanente et son stockage dans le système de métadonnées MaxCompute, consultez CREATE SQL FUNCTION.
Définir et appeler une UDTF Java avec code intégré
L'exemple suivant illustre cette procédure. Soumettez les instructions SQL en mode script. Pour plus d'informations, consultez SQL en mode script.
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);
La valeur de retour de @Resolve doit être de type string[]. Cependant, Janino-compiler ne peut pas identifier "string->string,string" comme string[]. Pour résoudre ce problème, les paramètres commentés par @Resolve doivent être entourés d'accolades {}. Lorsque vous créez une UDTF Java à l'aide d'une méthode courante, les accolades {} ne sont pas requises.
Définir et appeler une UDF Python avec code intégré
L'exemple suivant illustre cette procédure. Soumettez les instructions SQL en mode script. Pour plus d'informations, consultez SQL en mode script.
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);
L'indentation du code Python doit respecter les spécifications du langage Python.
Lorsque vous créez une UDF Python, le nom de classe qui suit la clause
ASdoit contenir le nom de fichier du code source Python. Vous pouvez utiliser'filename'='embedded'pour spécifier un nom de fichier virtuel.-
Pour plus d'informations sur le développement et l'utilisation des différentes versions de Python, consultez les rubriques suivantes :
Python 2 : Développer une UDF en Python 2
Python 3 : Développer une UDF en Python 3