MaxCompute introduit les types définis par l'utilisateur (UDT) basés sur le moteur SQL de nouvelle génération. Les UDT vous permettent de référencer des classes ou des objets de langages de programmation tiers dans des instructions SQL pour appeler des méthodes ou récupérer des données.
Quand utiliser les UDT plutôt que les UDF ?
Les UDT et les fonctions définies par l'utilisateur (UDF) étendent toutes deux MaxCompute SQL avec une logique personnalisée. Le choix dépend de votre flux de travail :
| Situation | Approche recommandée |
|---|---|
Appeler directement une méthode de classe Java intégrée (par exemple, Integer.MAX_VALUE) |
UDT — aucune définition de fonction n'est requise |
| Réutiliser directement une bibliothèque tierce dans une expression SQL | UDT — référencez la classe en ligne sans encapsulation |
| Inclure des objets compilés de langage source dans des travaux en plusieurs étapes | UDT — encapsule automatiquement l'état JVM entre les étapes |
| Implémenter une logique métier réutilisable dans plusieurs projets | UDF — l'enregistrement explicite de la fonction permet son partage |
Cas d'utilisation
Appeler des méthodes de la bibliothèque standard Java sans définir de fonction. Lorsqu'une tâche nécessite une méthode de classe Java intégrée que MaxCompute SQL n'expose pas nativement, un UDT vous permet de l'appeler directement dans une expression.
Référencer des bibliothèques tierces en ligne. Au lieu d'encapsuler une fonction de bibliothèque tierce dans une UDF, référencez la classe directement dans une instruction SQL.
Intégrer du code source compilé dans SQL. Pour les langages comme Java qui nécessitent une compilation, les UDT vous permettent de référencer des objets et des classes dans des expressions SQL sans étape d'enregistrement distincte. Consultez SELECT TRANSFORM pour découvrir les alternatives basées sur des scripts.
Prérequis
Avant d'utiliser les UDT, assurez-vous que :
Le JDK 1.8 est disponible dans votre environnement. Les versions ultérieures au JDK 1.8 peuvent ne pas être prises en charge.
Les nouveaux types de données sont activés si vous utilisez des types tels que INT :
set odps.sql.type.system.odps2=true;
Fonctionnement
Contrairement aux UDT d'autres moteurs SQL (qui définissent généralement des alias de type similaires au type STRUCT), les UDT MaxCompute fonctionnent comme une instruction CREATE TYPE : ils contiennent à la fois des champs et des méthodes, et vous les référencez directement dans SQL sans écrire de DDL.
L'exemple suivant illustre cette différence. Pour accéder à Integer.MAX_VALUE depuis le package java.lang de Java :
Utilisation d'un UDT (référence directe) :
-- Enable new data types (required for types such as INTEGER).
set odps.sql.type.system.odps2=true;
SELECT java.lang.Integer.MAX_VALUE;
Étant donné que java.lang est importé automatiquement (comme en Java), cela équivaut à :
set odps.sql.type.system.odps2=true;
SELECT Integer.MAX_VALUE;
Résultat :
+-----------+
| max_value |
+-----------+
| 2147483647 |
+-----------+
Utilisation d'une UDF (à titre de comparaison) :
-
Rédigez la classe UDF :
package com.aliyun.odps.test; public class IntegerMaxValue extends com.aliyun.odps.udf.UDF { public Integer evaluate() { return Integer.MAX_VALUE; } } -
Compilez, téléchargez et enregistrez :
add jar odps-test.jar; create function integer_max_value as 'com.aliyun.odps.test.IntegerMaxValue' using 'odps-test.jar'; -
Appelez la fonction :
select integer_max_value();
Les UDT permettent de réduire cette procédure à une seule instruction SQL.
Exécution en plusieurs étapes
Les objets UDT circulent naturellement entre les étapes MapReduce. L'exemple suivant joint deux colonnes BigInteger calculées à partir de sources de données différentes :
-- 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
Ce travail s'exécute en trois étapes (M1, R2, J3). new java.math.BigInteger(x) s'exécute lors de l'étape M1 ; java.math.BigInteger.valueOf(y) et x.add(y).toString() s'exécutent lors de l'étape J3 sur des processus et des machines physiques différents. Le UDT encapsule ce fonctionnement de sorte que toutes les étapes se comportent comme si elles s'exécutaient sur la même machine virtuelle Java (JVM).
Le DAG de ce travail SQL utilisant des UDT contient trois étapes : M1 → R2_1 → J3_2. Toutes les étapes sont terminées à 100 %, avec 1 ligne de données transmise entre chaque étape.
La colonne x de la variable a est de type java.math.BigInteger plutôt qu'un type intégré. Cette valeur UDT peut être transmise à d'autres opérateurs et utilisée lors du brassage des données.
Référence des packages JAR et configuration des imports Java
Toutes les classes du SDK pour Java sont disponibles par défaut pour les UDT. Pour référencer des packages JAR supplémentaires ou définir des chemins d'importation par défaut, utilisez les indicateurs de session suivants.
Référencer un package 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();
Vous pouvez spécifier plusieurs ressources séparées par des virgules : set odps.sql.session.resources=foo.sh,bar.txt;
odps.sql.session.resources contrôle à la fois les UDT et SELECT TRANSFORM. Un JAR défini ici est disponible pour les deux fonctionnalités.
Définir un chemin d'importation Java par défaut :
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 accepte un classpath (par exemple, java.math.BigInteger) ou un caractère générique (*). Les imports statiques ne sont pas pris en charge.
Opérations prises en charge
Les UDT prennent en charge les opérations suivantes dans les expressions SQL :
Créer des objets à l'aide de
new— exemple :new java.math.BigInteger('123')Créer des tableaux à l'aide de
newavec des listes d'initialisation — exemple :new Integer[] { 1, 2, 3 }Appeler des méthodes d'instance et statiques
Accéder aux champs d'instance et statiques publics
Seules les méthodes publiques et les champs publics sont accessibles. Tous les identifiants (noms de package, noms de classe, noms de méthode, noms de champ) sont sensibles à la casse. Les classes anonymes et les expressions lambda ne sont pas prises en charge. Les fonctions qui ne renvoient pas de valeurs ne peuvent pas être appelées dans des expressions.
Types de données
Mappage des types
Les types de données Java correspondent aux types intégrés de MaxCompute. Le même mappage utilisé dans les UDF Java s'applique aux UDT.
Appelez directement les méthodes des types intégrés :
'123'.length(),1L.hashCode()Utilisez les UDT dans les fonctions intégrées :
chr(Long.valueOf('100'))—Long.valueOfrenvoiejava.lang.Long, qui correspond au type intégré BIGINTLes types primitifs Java sont automatiquement convertis en leurs types d'encapsulation (boxing)
Pour les nouveaux types de données intégrés, ajoutez set odps.sql.type.system.odps2=true; avant d'exécuter la requête.
Conversions de types
Les conversions de type SQL sont prises en charge :
cast(1 as java.lang.Object)Les casts de style Java ne sont pas pris en charge :
(Object)1Les objets UDT peuvent être implicitement convertis en objets de classe de base
Les objets UDT peuvent être explicitement convertis (cast) en objets de classe de base ou de sous-classe
La conversion entre deux types non apparentés suit les mêmes règles que la conversion des types intégrés. Par exemple, la conversion de
java.lang.Longenjava.lang.Integerapplique les mêmes règles que la conversion de BIGINT en INT, ce qui peut entraîner une perte de données.
Les objets UDT ne peuvent pas être enregistrés sur disque ni insérés directement dans des tables (le DDL ne prend pas en charge les UDT comme type de colonne). Si la valeur UDT peut être implicitement convertie en un type intégré, elle peut être écrite dans une table. Le type BINARY prend en charge la sérialisation automatique : les tableauxbyte[]peuvent être enregistrés et désérialisés. Pour persister un UDT, convertissez-le en BINARY à l'aide de méthodes de sérialisation et de désérialisation. Les valeurs UDT ne peuvent pas apparaître directement dans la sortie finale. AppeleztoString()pour convertir n'importe quel UDT enjava.lang.Stringafin de l'afficher. Pour convertir automatiquement toutes les sorties UDT en chaînes lors du débogage, utilisez : Cet indicateur s'applique uniquement aux instructions PRINT, pas aux instructions INSERT.
set odps.sql.udt.display.tostring=true;
Génériques
Les UDT prennent en charge les génériques Java. Le compilateur déduit le paramètre de type à partir de l'argument :
-- Returns java.util.List<java.math.BigInteger>
java.util.Arrays.asList(new java.math.BigInteger('1'))
Spécifiez explicitement les paramètres de type dans les appels de constructeur ou utilisez 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'))
Sémantique des opérateurs
Tous les opérateurs suivent la sémantique de MaxCompute SQL, et non celle de Java :
Concaténation de chaînes :
String.valueOf(1) + String.valueOf(2)renvoie3(les deux chaînes sont implicitement converties en DOUBLE et additionnées). Pour concaténer sous forme de chaînes, utilisez plutôt une fonction de concaténation de chaînes.Égalité : L'opérateur
=est un opérateur de comparaison SQL, et non une égalité de référence Java. Utilisez la méthodeequalspour vérifier si deux objets sont équivalents.
Égalité des objets et brassage des données
Les UDT n'ont pas de définition claire de l'égalité des objets. Lors du brassage des données, les objets peuvent être transmis entre des processus et des machines physiques, ce qui fait qu'un seul objet apparaît comme deux références distinctes. Utilisez toujours la méthode equals — et non = — pour comparer les objets UDT.
Les objets au sein d'une même ligne ou colonne sont corrélés, mais la corrélation entre les lignes ou les colonnes n'est pas garantie.
Limitations
Les UDT ne peuvent pas être utilisés comme clés de brassage dans les clauses JOIN, GROUP BY, DISTRIBUTE BY, SORT BY, ORDER BY ou CLUSTER BY . Les UDT sont valides dans les expressions à ces étapes, mais ne peuvent pas constituer la sortie. Par exemple :
group by new java.math.BigInteger('123')— non pris en chargegroup by new java.math.BigInteger('123').hashCode()— pris en charge, carhashCode()renvoieint.class, qui correspond au type intégré INT
Les UDF, les fonctions d'agrégation définies par l'utilisateur (UDAF) et les UDT ne peuvent pas lire les données des types de tables suivants :
Tables sur lesquelles une évolution de schéma est effectuée
Tables contenant des types de données complexes
Tables contenant des types de données JSON
Tables transactionnelles
Accès aux ressources
Dans MaxCompute SQL, appelez la méthode statique com.aliyun.odps.udf.impl.UDTExecutionContext.get() pour obtenir l'objet ExecutionContext . Utilisez cet objet pour accéder au contexte d'exécution actuel, y compris les fichiers et les tables enregistrés en tant que ressources.
Considérations relatives aux performances
Les performances des UDT sont similaires à celles des UDF. Le moteur de calcul optimisé offre des améliorations supplémentaires dans des scénarios spécifiques :
Aucune surcharge de sérialisation pour les opérations locales. Lorsqu'un objet UDT est utilisé au sein du même processus (aucun brassage de données requis, comme dans les étapes JOIN ou AGGREGATE), la sérialisation et la désérialisation sont ignorées.
Exécution basée sur Codegen. Les UDT s'exécutent via Codegen plutôt que par réflexion, il n'y a donc aucune surcharge liée à la réflexion. Plusieurs appels UDT sont regroupés en un seul appel de fonction — par exemple,
values[x].add(values[y]).divide(java.math.BigInteger.valueOf(2))est appelé une seule fois, évitant ainsi la surcharge d'interface par appel.
Sécurité
Les UDT sont soumis au même modèle de sandbox Java que les UDF. Pour effectuer des opérations restreintes par la sandbox, annulez l'isolation de la sandbox pour ces opérations ou faites une demande pour rejoindre la liste blanche de la sandbox.
Fonctionnalités à améliorer
Les fonctionnalités suivantes sont prévues pour les versions futures :
Appeler des fonctions qui ne renvoient pas de valeurs, et des fonctions qui utilisent directement des données transférées (où la valeur de retour est ignorée, comme la méthode
addde l'interface List).Utiliser des classes anonymes et des expressions lambda.
Utiliser les UDT comme clés de brassage.
Prendre en charge davantage de langages de programmation, tels que Python.
Étapes suivantes
UDF Java — tableau de mappage des types de données et référence d'implémentation des UDF
Nouveaux types de données intégrés — types qui nécessitent
set odps.sql.type.system.odps2=true;SELECT TRANSFORM — intégrer des scripts dans des instructions SQL
COLLECT_SET et autres fonctions d'agrégation — utiliser avec les UDT pour implémenter un comportement de fonction d'agrégation et de fonction table
Sandbox Java — modèle de sandbox et demande d'ajout à la liste blanche