Cette rubrique explique comment développer et utiliser des fonctions définies par l'utilisateur (UDF) en Java.
Informations générales
À partir de la version 2.2.0, StarRocks prend en charge les fonctions définies par l'utilisateur (UDF) écrites en Java.
Depuis la version 3.0, StarRocks prend en charge les UDF globales. Il vous suffit d'ajouter le mot-clé GLOBAL aux instructions SQL pertinentes (CREATE, SHOW et DROP) pour les appliquer à l'échelle globale. Cela vous évite d'exécuter les instructions pour chaque base de données individuellement. Vous pouvez développer des fonctions personnalisées adaptées à vos scénarios métier afin d'étendre les capacités de StarRocks.
StarRocks prend actuellement en charge les types d'UDF suivants :
fonction scalaire définie par l'utilisateur (scalar UDF)
fonction d'agrégation définie par l'utilisateur (UDAF)
fonction de fenêtre définie par l'utilisateur (UDWF)
fonction table définie par l'utilisateur (UDTF)
Prérequis
Avant d'utiliser les UDF Java dans StarRocks, assurez-vous de remplir les conditions suivantes :
Installez Apache Maven pour créer et développer des projets Java.
Installez le JDK 1.8 sur votre serveur.
Activez la fonctionnalité UDF en définissant l'élément de configuration FE
enable_udfsurTRUEdans la page Instance Configuration. Redémarrez ensuite l'instance pour appliquer la modification.
Correspondance des types de données
|
Type SQL |
Type Java |
|
BOOLEAN |
java.lang.Boolean |
|
TINYINT |
java.lang.Byte |
|
SMALLINT |
java.lang.Short |
|
INT |
java.lang.Integer |
|
BIGINT |
java.lang.Long |
|
FLOAT |
java.lang.Float |
|
DOUBLE |
java.lang.Double |
|
STRING/VARCHAR |
java.lang.String |
Développer et utiliser des UDF
Vous allez créer un projet Maven et écrire la fonction en Java.
Étape 1 : Créer un projet Maven
Créez un projet Maven avec la structure de répertoire de base suivante.
project
|--pom.xml
|--src
| |--main
| | |--java
| | |--resources
| |--test
|--target
Étape 2 : Ajouter les dépendances
Ajoutez les dépendances suivantes au fichier pom.xml.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>udf</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
Étape 3 : Développer la UDF
Développez les UDF requises en Java.
Développer une scalar UDF
Une scalar UDF traite une ligne à la fois et renvoie une seule valeur pour chaque ligne. Lorsque vous utilisez une scalar UDF dans une requête, chaque ligne apparaît dans l'ensemble de résultats. Les fonctions scalaires typiques incluent UPPER, LOWER, ROUND et ABS.
L'exemple suivant montre comment extraire des données d'un objet JSON. Dans certains scénarios métier, la valeur d'un champ dans les données JSON peut être une chaîne JSON plutôt qu'un objet JSON. Pour extraire la chaîne JSON imbriquée, vous devez utiliser un appel imbriqué à la fonction GET_JSON_STRING, tel que GET_JSON_STRING(GET_JSON_STRING('{"key":"{\\"k0\\":\\"v0\\"}"}', "$.key"), "$.k0").
Pour simplifier l'instruction SQL, vous pouvez développer une UDF qui extrait directement la chaîne JSON, par exemple MY_UDF_JSON_GET('{"key":"{\\"k0\\":\\"v0\\"}"}', "$.key.k0").
package com.starrocks.udf.sample;
import com.alibaba.fastjson.JSONPath;
public class UDFJsonGet {
public final String evaluate(String jsonObj, String key) {
if (obj == null || key == null) return null;
try {
// The JSONPath library can fully expand the path, even if a field value is a JSON-formatted string.
return JSONPath.read(jsonObj, key).toString();
} catch (Exception e) {
return null;
}
}
}
Votre classe personnalisée doit implémenter la méthode suivante.
Les types de données des paramètres de requête et de la valeur de retour dans la méthode doivent correspondre à ceux déclarés dans l'instruction CREATE FUNCTION de l'étape 6. La correspondance des types de données doit respecter les règles décrites dans la section Correspondance des types de données.
|
Méthode |
Description |
|
TYPE1 evaluate(TYPE2, ...) |
La méthode |
Développer une UDAF
Une UDAF opère sur un groupe de lignes et renvoie une seule valeur. Les fonctions d'agrégation typiques incluent SUM, COUNT, MAX et MIN. Ces fonctions agrègent les données de plusieurs lignes au sein de chaque groupe GROUP BY et produisent un seul résultat pour chaque groupe.
L'exemple suivant utilise la fonction MY_SUM_INT. Contrairement à la fonction intégrée SUM, qui renvoie une valeur BIGINT, MY_SUM_INT accepte un argument INT et renvoie un INT.
package com.starrocks.udf.sample;
public class SumInt {
public static class State {
int counter = 0;
public int serializeLength() { return 4; }
}
public State create() {
return new State();
}
public void destroy(State state) {
}
public final void update(State state, Integer val) {
if (val != null) {
state.counter+= val;
}
}
public void serialize(State state, java.nio.ByteBuffer buff) {
buff.putInt(state.counter);
}
public void merge(State state, java.nio.ByteBuffer buffer) {
int val = buffer.getInt();
state.counter += val;
}
public Integer finalize(State state) {
return state.counter;
}
}
Votre classe personnalisée doit implémenter les méthodes suivantes.
Les types de données des paramètres de requête et de la valeur de retour dans les méthodes doivent correspondre à ceux déclarés dans l'instruction CREATE FUNCTION de l'étape 6. La correspondance des types de données doit respecter les règles décrites dans la section Correspondance des types de données.
|
Méthode |
Description |
|
State create() |
Crée un objet d'état. |
|
void destroy(State) |
Détruit l'objet d'état. |
|
void update(State, ...) |
Met à jour l'état. Le premier argument est l'objet d'état et les arguments suivants sont les paramètres de requête spécifiés dans la déclaration de la fonction. Un ou plusieurs paramètres de requête sont pris en charge. |
|
void serialize(State, ByteBuffer) |
Sérialise l'état. |
|
void merge(State, ByteBuffer) |
Fusionne un état sérialisé dans l'état actuel. |
|
TYPE finalize(State) |
Calcule et renvoie le résultat final à partir de l'état. |
Lors du développement d'une UDAF, vous devez utiliser la classe de tampon java.nio.ByteBuffer pour stocker le résultat intermédiaire et la méthode serializeLength pour spécifier la longueur du résultat intermédiaire sérialisé.
|
Classe et méthode |
Description |
|
java.nio.ByteBuffer() |
Cette classe de tampon stocke le résultat intermédiaire. Étant donné que le résultat est sérialisé pour le transfert entre les nœuds, vous devez utiliser |
|
serializeLength() |
La longueur en octets du résultat intermédiaire sérialisé. La méthode |
Tenez compte des exigences suivantes pour la sérialisation java.nio.ByteBuffer :
Ne vous fiez pas à la méthode
remaining()deByteBufferpour désérialiser l'état.N'appelez pas la méthode
clear()sur leByteBuffer.La valeur renvoyée par
serializeLength()doit correspondre à la longueur réelle des données écrites dans le tampon. Une incompatibilité entraîne des erreurs de sérialisation et de désérialisation.
Développer une UDWF
Une UDWF est un type spécial de fonction d'agrégation. Contrairement à une fonction d'agrégation classique, une fonction de fenêtre calcule une valeur sur un groupe de lignes (une fenêtre) et renvoie un résultat distinct pour chaque ligne. Généralement, une fonction de fenêtre inclut une clause OVER qui divise les lignes en partitions. La fonction calcule ensuite un résultat pour chaque ligne en fonction de la fenêtre de lignes à laquelle elle appartient.
L'exemple suivant utilise la fonction MY_WINDOW_SUM_INT. Contrairement à la fonction intégrée SUM, qui renvoie une valeur BIGINT, MY_WINDOW_SUM_INT accepte un argument INT et renvoie un INT.
package com.starrocks.udf.sample;
public class WindowSumInt {
public static class State {
int counter = 0;
public int serializeLength() { return 4; }
@Override
public String toString() {
return "State{" +
"counter=" + counter +
'}';
}
}
public State create() {
return new State();
}
public void destroy(State state) {
}
public void update(State state, Integer val) {
if (val != null) {
state.counter+=val;
}
}
public void serialize(State state, java.nio.ByteBuffer buff) {
buff.putInt(state.counter);
}
public void merge(State state, java.nio.ByteBuffer buffer) {
int val = buffer.getInt();
state.counter += val;
}
public Integer finalize(State state) {
return state.counter;
}
public void reset(State state) {
state.counter = 0;
}
public void windowUpdate(State state,
int peer_group_start, int peer_group_end,
int frame_start, int frame_end,
Integer[] inputs) {
for (int i = (int)frame_start; i < (int)frame_end; ++i) {
state.counter += inputs[i];
}
}
}
Étant donné qu'une fonction de fenêtre est un type spécial de fonction d'agrégation, votre classe personnalisée doit implémenter toutes les méthodes UDAF requises, ainsi que la méthode windowUpdate().
Les types de données des paramètres de requête et de la valeur de retour dans les méthodes doivent correspondre à ceux déclarés dans l'instruction CREATE FUNCTION de l'étape 6. La correspondance des types de données doit respecter les règles décrites dans la section Correspondance des types de données.
Méthode supplémentaire requise
|
Méthode |
Description |
|
|
Met à jour les données de la fenêtre. Pour plus d'informations sur les fonctions de fenêtre, consultez la section Fonctions de fenêtre. Pour chaque ligne d'entrée, les informations de fenêtre correspondantes sont récupérées pour mettre à jour le résultat intermédiaire.
|
Développer une UDTF
Une UDTF prend une seule ligne en entrée et produit une table de lignes en sortie. Les UDTF sont souvent utilisées pour des opérations telles que la division d'une colonne en plusieurs lignes.
Actuellement, les UDTF ne prennent en charge que le renvoi de plusieurs lignes avec une seule colonne.
L'exemple suivant utilise la fonction MY_UDF_SPLIT. Elle divise une chaîne en utilisant un espace comme séparateur. L'argument d'entrée et la valeur de retour sont tous deux de type STRING.
package com.starrocks.udf.sample;
public class UDFSplit{
public String[] process(String in) {
if (in == null) return null;
return in.split(" ");
}
}
Votre classe personnalisée doit implémenter la méthode suivante.
Les types de données des paramètres de requête et de la valeur de retour dans la méthode doivent correspondre à ceux déclarés dans l'instruction CREATE FUNCTION de l'étape 6. La correspondance des types de données doit respecter les règles décrites dans la section Correspondance des types de données.
|
Méthode |
Description |
|
TYPE[] process() |
La méthode |
Étape 4 : Empaqueter le projet Java
Exécutez la commande suivante pour empaqueter le projet Java.
mvn package
Cette commande génère deux fichiers JAR dans le répertoire target : udf-1.0-SNAPSHOT.jar et udf-1.0-SNAPSHOT-jar-with-dependencies.jar.
Étape 5 : Télécharger le projet
Téléchargez le fichier udf-1.0-SNAPSHOT-jar-with-dependencies.jar dans un compartiment OSS et accordez l'autorisation public-read au fichier JAR. Pour plus d'informations, consultez les sections Téléchargement simple et ACL du compartiment.
À l'étape 6, le FE valide le fichier JAR de la UDF et calcule une somme de contrôle. Le BE télécharge ensuite le fichier JAR et l'exécute.
Étape 6 : Créer la UDF dans StarRocks
StarRocks propose deux espaces de noms pour les UDF : au niveau de la base de données et au niveau global.
Si vous n'avez pas besoin d'isolation de visibilité pour vos UDF, vous pouvez créer une UDF globale. Lors de l'appel à une UDF globale, vous pouvez l'invoquer directement par son nom de fonction sans préfixe de catalogue ou de base de données, ce qui simplifie l'accès.
Si vous avez besoin d'isolation de visibilité ou si vous devez créer des UDF portant le même nom dans différentes bases de données, vous pouvez créer une UDF au niveau de la base de données. Si votre session actuelle se trouve dans cette base de données, vous pouvez appeler la fonction directement par son nom. Si votre session se trouve dans un catalogue ou une base de données différent, vous devez utiliser un nom complet, tel que
catalog.database.function.
La création d'une UDF globale nécessite l'autorisation CREATE GLOBAL FUNCTION au niveau du système. La création d'une UDF au niveau de la base de données nécessite l'autorisation CREATE FUNCTION sur la base de données. L'utilisation de toute UDF nécessite l'autorisation USAGE sur celle-ci. Pour savoir comment accorder des autorisations, consultez la documentation GRANT.
Après avoir téléchargé le fichier JAR, créez la UDF correspondante dans StarRocks. Pour créer une UDF globale, ajoutez simplement le mot-clé GLOBAL à l'instruction SQL.
Syntaxe
CREATE [GLOBAL][AGGREGATE | TABLE] FUNCTION function_name(arg_type [, ...])
RETURNS return_type
[PROPERTIES ("key" = "value" [, ...]) ]
Paramètres
|
Paramètre |
Obligatoire |
Description |
|
GLOBAL |
Non |
Spécifie que la UDF est une UDF globale. Pris en charge depuis StarRocks v3.0. |
|
AGGREGATE |
Non |
Spécifie que la fonction à créer est une UDAF ou une UDWF. |
|
TABLE |
Non |
Spécifie que la fonction à créer est une UDTF. |
|
function_name |
Oui |
Le nom de la fonction. Il peut inclure un nom de base de données, tel que |
|
arg_type |
Oui |
Le type de données d'un argument de fonction. Pour les types de données pris en charge, consultez la section Correspondance des types de données. |
|
return_type |
Oui |
Le type de données de la valeur de retour de la fonction. Pour les types de données pris en charge, consultez la section Correspondance des types de données. |
|
properties |
Oui |
Propriétés liées à la fonction. Différentes propriétés sont requises pour différents types d'UDF. Consultez les exemples suivants pour plus de détails. |
Créer une scalar UDF
Exécutez la commande suivante pour créer la scalar UDF de l'exemple précédent dans StarRocks.
CREATE [GLOBAL] FUNCTION MY_UDF_JSON_GET(string, string)
RETURNS string
PROPERTIES (
"symbol" = "com.starrocks.udf.sample.UDFJsonGet",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);
|
Paramètre |
Description |
|
symbol |
Le nom de classe complet de la UDF, au format |
|
type |
Le type de UDF. Pour les UDF Java, définissez cette valeur sur |
|
file |
Le chemin HTTP vers le fichier JAR de la UDF. Il doit s'agir de l'URL HTTP du fichier dans OSS, de préférence en utilisant un endpoint interne. Le format est |
Créer une UDAF
Exécutez la commande suivante pour créer la UDAF de l'exemple précédent dans StarRocks.
CREATE [GLOBAL] AGGREGATE FUNCTION MY_SUM_INT(INT)
RETURNS INT
PROPERTIES
(
"symbol" = "com.starrocks.udf.sample.SumInt",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);
Les paramètres de la clause PROPERTIES sont identiques à ceux décrits dans la section Créer une scalar UDF.
Créer une UDWF
Exécutez la commande suivante pour créer la UDWF de l'exemple précédent dans StarRocks.
CREATE [GLOBAL] AGGREGATE FUNCTION MY_WINDOW_SUM_INT(Int)
RETURNS Int
PROPERTIES
(
"analytic" = "true",
"symbol" = "com.starrocks.udf.sample.WindowSumInt",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);
La propriété analytic identifie la fonction comme une fonction de fenêtre. Pour une UDWF, définissez cette valeur sur true. Les autres paramètres sont identiques à ceux décrits dans la section Créer une scalar UDF.
Créer une UDTF
Exécutez la commande suivante pour créer la UDTF de l'exemple précédent dans StarRocks.
CREATE [GLOBAL] TABLE FUNCTION MY_UDF_SPLIT(string)
RETURNS string
PROPERTIES
(
"symbol" = "com.starrocks.udf.sample.UDFSplit",
"type" = "StarrocksJar",
"file" = "http://<YourBucketName>.oss-cn-xxxx-internal.aliyuncs.com/<YourPath>/udf-1.0-SNAPSHOT-jar-with-dependencies.jar"
);
Les paramètres de la clause PROPERTIES sont identiques à ceux décrits dans la section Créer une scalar UDF.
Étape 7 : Utiliser la UDF
Après avoir créé la UDF, vous pouvez la tester et l'utiliser.
Utiliser la scalar UDF
Exécutez la commande suivante pour utiliser la scalar UDF créée à l'étape 6.
SELECT MY_UDF_JSON_GET('{"key":"{\\"in\\":2}"}', '$.key.in');
Utiliser la UDAF
Exécutez la commande suivante pour utiliser la UDAF créée à l'étape 6.
SELECT MY_SUM_INT(col1);
Utiliser la UDWF
Exécutez la commande suivante pour utiliser la UDWF créée à l'étape 6.
SELECT MY_WINDOW_SUM_INT(intcol)
OVER (PARTITION BY intcol2
ORDER BY intcol3
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
FROM test_basic;
Utiliser la UDTF
Exécutez la commande suivante pour utiliser la UDTF de l'exemple précédent.
-- Assume that table t1 exists with columns a, b, and c1.
SELECT t1.a,t1.b,t1.c1 FROM t1;
> output:
1,2.1,"hello world"
2,2.2,"hello UDTF."
-- Use the MY_UDF_SPLIT() function.
SELECT t1.a,t1.b, MY_UDF_SPLIT FROM t1, MY_UDF_SPLIT(t1.c1);
> output:
1,2.1,"hello"
1,2.1,"world"
2,2.2,"hello"
2,2.2,"UDTF."
Dans la liste SELECT, le premier
MY_UDF_SPLITest l'alias de colonne par défaut pour la sortie de la fonctionMY_UDF_SPLIT.Actuellement, vous ne pouvez pas spécifier d'alias de table ou d'alias de colonne pour la sortie de la UDTF, par exemple avec
AS t2(f1).
Afficher les UDF
Exécutez la commande suivante pour afficher les informations sur les UDF.
SHOW [GLOBAL] FUNCTIONS;
Supprimer des UDF
Exécutez la commande suivante pour supprimer une UDF spécifiée.
DROP [GLOBAL] FUNCTION <function_name>(arg_type [, ...]);
FAQ
Q : Puis-je utiliser des variables statiques lors du développement d'une UDF ? Les variables statiques de différentes UDF interfèrent-elles entre elles ?
R : Oui, vous pouvez utiliser des variables statiques. Les variables statiques sont isolées entre différentes UDF et n'interfèrent pas entre elles, même si les UDF partagent le même nom de classe.