Exemples de gestionnaires d’UDF Java

Cette rubrique comprend des exemples simples de code de gestionnaire d’UDF écrits en Java.

Pour en savoir plus sur l’utilisation de Java pour créer un gestionnaire d’UDF, voir Création d’UDFs Java.

Dans ce chapitre :

Création et appel d’une simple UDF Java en ligne

Les instructions suivantes créent et appellent une UDF Java en ligne. Ce code renvoie le VARCHAR qui lui est transmis.

Cette fonction est déclarée avec la clause facultative CALLED ON NULL INPUT pour indiquer que la fonction est appelée même si la valeur de l’entrée est NULL. (Cette fonction renverrait NULL avec ou sans cette clause, mais vous pourriez modifier le code pour traiter NULL d’une autre manière, par exemple, pour renvoyer une chaîne vide).

Créez l’UDF :

create or replace function echo_varchar(x varchar)
returns varchar
language java
called on null input
handler='TestFunc.echoVarchar'
target_path='@~/testfunc.jar'
as
'class TestFunc {
  public static String echoVarchar(String x) {
    return x;
  }
}';

Appelez l’UDF :

SELECT echo_varchar('Hello');
+-----------------------+
| ECHO_VARCHAR('HELLO') |
|-----------------------|
| Hello                 |
+-----------------------+

Transmission d’un NULL à une UDF Java en ligne

Ceci utilise les echo_varchar() UDF définies ci-dessus. La valeur SQL NULL est implicitement convertie en null Java, et cette null Java est retournée et implicitement reconvertie au format SQL NULL :

Appelez l’UDF :

SELECT echo_varchar(NULL);
+--------------------+
| ECHO_VARCHAR(NULL) |
|--------------------|
| NULL               |
+--------------------+

Renvoyer NULL explicitement à partir d’une UDF en ligne

Le code suivant montre comment retourner une valeur NULL de manière explicite. La valeur Java null est convertie au format SQL NULL.

Créez l’UDF :

create or replace function return_a_null()
returns varchar
null
language java
handler='TemporaryTestLibrary.returnNull'
target_path='@~/TemporaryTestLibrary.jar'
as
$$
class TemporaryTestLibrary {
  public static String returnNull() {
    return null;
  }
}
$$;

Appelez l’UDF :

SELECT return_a_null();
+-----------------+
| RETURN_A_NULL() |
|-----------------|
| NULL            |
+-----------------+

Transmission d’un OBJECT à une UDF Java en ligne

L’exemple suivant utilise le type de données SQL OBJECT et le type de données Java correspondant (Map<chaîne, chaîne>), et extrait une valeur à partir de OBJECT. Cet exemple montre également que vous pouvez transmettre plusieurs paramètres à une UDF Java.

Créer et charger une table qui contient une colonne de type OBJECT :

CREATE TABLE objectives (o OBJECT);
INSERT INTO objectives SELECT PARSE_JSON('{"outer_key" : {"inner_key" : "inner_value"} }');

Créez l’UDF :

create or replace function extract_from_object(x OBJECT, key VARCHAR)
returns variant
language java
handler='VariantLibrary.extract'
target_path='@~/VariantLibrary.jar'
as
$$
import java.util.Map;
class VariantLibrary {
  public static String extract(Map<String, String> m, String key) {
    return m.get(key);
  }
}
$$;

Appelez l’UDF :

SELECT extract_from_object(o, 'outer_key'), 
       extract_from_object(o, 'outer_key')['inner_key'] FROM objectives;
+-------------------------------------+--------------------------------------------------+
| EXTRACT_FROM_OBJECT(O, 'OUTER_KEY') | EXTRACT_FROM_OBJECT(O, 'OUTER_KEY')['INNER_KEY'] |
|-------------------------------------+--------------------------------------------------|
| {                                   | "inner_value"                                    |
|   "inner_key": "inner_value"        |                                                  |
| }                                   |                                                  |
+-------------------------------------+--------------------------------------------------+

Transmission d’un ARRAY à une UDF Java en ligne

L’exemple suivant utilise le type de données SQL ARRAY.

Créez l’UDF :

create or replace function multiple_functions_in_jar(array1 array)
returns varchar
language java
handler='TemporaryTestLibrary.handleStrings'
target_path='@~/TemporaryTestLibrary.jar'
as
$$
class TemporaryTestLibrary {
  public static String handleStrings(String[] strings) {
    return concatenate(strings);
  }
  public static String concatenate(String[] strings) {
    int numberOfStrings = strings.length;
    String concatenated = "";
    for (int i = 0; i < numberOfStrings; i++)  {
        concatenated = concatenated + " " + strings[i];
    }
    return concatenated;
  }
}
$$;

Appelez l’UDF :

SELECT multiple_functions_in_jar(ARRAY_CONSTRUCT('Hello', 'world'));
+--------------------------------------------------------------+
| MULTIPLE_FUNCTIONS_IN_JAR(ARRAY_CONSTRUCT('HELLO', 'WORLD')) |
|--------------------------------------------------------------|
|  Hello world                                                 |
+--------------------------------------------------------------+

Transmission d’une valeur GEOGRAPHY à une UDF Java en ligne

L’exemple suivant utilise le type de données SQL GEOGRAPHY.

Créez l’UDF :

create or replace function geography_equals(x geography, y geography)
returns boolean
language java
packages=('com.snowflake:snowpark:1.2.0')
handler='TestGeography.compute'
as
$$
import com.snowflake.snowpark_java.types.Geography;

class TestGeography {
  public static boolean compute(Geography geo1, Geography geo2) {
    return geo1.equals(geo2);
  }
}
$$;

Vous pouvez utiliser la clause PACKAGES pour spécifier un paquet système Snowflake, comme le package Snowpark. Lorsque vous le faites, il n’est pas nécessaire d’inclure également le fichier JAR de Snowpark comme valeur d’une clause IMPORTS. Pour en savoir plus sur PACKAGES, voir Paramètres facultatifs CREATE FUNCTION.

Créer des données et appeler l’UDF avec ces données :

create table geocache_table (id INTEGER, g1 GEOGRAPHY, g2 GEOGRAPHY);

insert into geocache_table (id, g1, g2) select
    1, TO_GEOGRAPHY('POINT(-122.35 37.55)'), TO_GEOGRAPHY('POINT(-122.35 37.55)');
insert into geocache_table (id, g1, g2) select
    2, TO_GEOGRAPHY('POINT(-122.35 37.55)'), TO_GEOGRAPHY('POINT(90.0 45.0)');

select id, g1, g2, geography_equals(g1, g2) as "EQUAL?"
    from geocache_table
    order by id;

La sortie devrait ressembler à :

+----+--------------------------------------------------------+---------------------------------------------------------+--------+
| ID | G1                                                     | G2                                                      | EQUAL? |
+----+--------------------------------------------------------|---------------------------------------------------------+--------+
| 1  | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | { "coordinates": [ -122.35,  37.55 ], "type": "Point" } | TRUE   |
| 2  | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | { "coordinates": [   90.0,   45.0  ], "type": "Point" } | FALSE  |
+----+--------------------------------------------------------+---------------------------------------------------------+--------+

Transmission d’une valeur VARIANT à une UDF Java en ligne

Lorsque vous passez une valeur de type SQL VARIANT à une UDF Java, Snowflake peut convertir la valeur en type Variant fourni avec le paquet Snowpark. Notez que Variant est pris en charge à partir de la version 1.4.0 du paquet Snowpark et des versions ultérieures.

Le type Variant de Snowpark fournit des méthodes pour convertir des valeurs entre Variant et d’autres types.

Pour utiliser le type Variant Snowpark, utilisez la clause PACKAGES pour spécifier le paquet Snowpark lors de la création de l’UDF. Lorsque vous le faites, il n’est pas nécessaire d’inclure également le fichier JAR de Snowpark comme valeur d’une clause IMPORTS. Pour en savoir plus sur les PACKAGES, voir Paramètres facultatifs CREATE FUNCTION.

Le code de l’exemple suivant reçoit des données JSON stockées sous le type VARIANT, puis utilise le type Variant de la bibliothèque Snowpark pour extraire la valeur price du JSON. Le JSON reçu a une structure similaire au JSON affiché dans Échantillon de données utilisé dans des exemples.

create or replace function retrieve_price(v variant)
returns integer
language java
packages=('com.snowflake:snowpark:1.4.0')
handler='VariantTest.retrievePrice'
as
$$
import java.util.Map;
import com.snowflake.snowpark_java.types.Variant;

public class VariantTest {
  public static Integer retrievePrice(Variant v) throws Exception {
    Map<String, Variant> saleMap = v.asMap();
    int price = saleMap.get("vehicle").asMap().get("price").asInt();
    return price;
  }
}
$$;

Lecture d’un fichier à l’aide d’un gestionnaire d’UDF Java

Vous pouvez lire le contenu d’un fichier avec le code du gestionnaire d’UDF Java. Le fichier doit se trouver sur une zone de préparation Snowflake qui est disponible pour votre gestionnaire. Par exemple, vous pourriez vouloir lire un fichier pour traiter des données non structurées dans le gestionnaire. Pour plus d’informations, voir Traiter des données non structurées en utilisant des UDFs ou des UDTFs Java.

Pour lire le contenu des fichiers en zone de préparation, vos UDFs ou UDTFs Java peuvent appeler des méthodes de la classe SnowflakeFile ou de la classe InputStream. SnowflakeFile fournit des fonctionnalités non disponibles avec InputStream, tel que décrit dans le tableau suivant.

Classe

Entrée

Remarques

SnowflakeFile

URL scopée, URL de fichier, ou chemin d’accès au fichier pour les fichiers situés dans une zone de préparation interne ou externe

Accédez facilement à des attributs de fichier supplémentaires, tels que la taille du fichier.

InputStream

URL scopée, URL de fichier, ou chemin d’accès au fichier pour les fichiers situés dans une zone de préparation interne ou externe

Conditions préalables

Avant que votre code de gestionnaire Java puisse lire un fichier sur une zone de préparation, vous devez effectuer les opérations suivantes pour mettre le fichier à la disposition du code :

  1. Créez une zone de préparation qui est disponible pour votre gestionnaire.

    Vous pouvez utiliser une zone de préparation externe ou une zone de préparation interne. Si vous utilisez une zone de préparation interne, il doit s’agir d’une zone de préparation d’utilisateur ou nommée ; Snowflake ne prend actuellement pas en charge l’utilisation d’une zone de préparation de table pour des dépendances d’UDF. Pour en savoir plus sur la création d’une zone de préparation, voir CREATE STAGE. Pour en savoir plus sur le choix d’un type de zone de préparation interne, voir Choix d’une zone de préparation interne pour les fichiers locaux.

    Gardez à l’esprit que des privilèges adéquats sur la zone de préparation doivent être attribués aux rôles effectuant des actions SQL qui effectuent des lectures à partir de la zone de préparation. Pour plus d’informations, voir Accorder des privilèges pour les fonctions définies par l’utilisateur.

  2. Sur la zone de préparation, copiez le fichier qui sera lu par le code.

    Vous pouvez copier le fichier d’un lecteur local vers une zone de préparation en utilisant la commande PUT. Pour la référence de la commande, voir PUT. Pour des informations sur la mise en zone de préparation de fichiers avec PUT, voir Mise en zone de préparation des fichiers de données à partir d’un système de fichiers local.

Lire un fichier en utilisant la classe Snowflakefile

En utilisant les méthodes de la classe SnowflakeFile, vous pouvez lire les fichiers d’une zone de préparation avec votre code de gestionnaire Java. La classe SnowflakeFile est incluse dans le classpath accessible aux gestionnaires d’UDF Java sur Snowflake.

Pour développer le code de votre UDF localement, ajoutez le fichier JAR Snowpark contenant SnowflakeFile au chemin de classe de votre code. Pour plus d’informations sur snowpark.jar, voir Configuration de votre environnement de développement pour Snowpark Java. Notez que les applications client Snowpark ne peuvent pas utiliser cette classe ; par conséquent, la classe n’est pas documentée dans les rubriques sur Snowpark.

Lorsque vous utilisez SnowflakeFile, il n’est pas nécessaire de spécifier également le fichier en zone de préparation ou le fichier JAR contenant SnowflakeFile avec une clause IMPORTS lorsque vous créez l’UDF, comme dans SQL avec une instruction CREATE FUNCTION.

La classe SnowflakeFile possède les méthodes suivantes :

Méthode

Description

public static native SnowflakeFile newInstance(String url)

Renvoie un objet SnowflakeFile pour le fichier à l’emplacement spécifié par l’argument url.

public synchronized InputStream getInputStream()

Retourne un objet InputStream pour lire le contenu du fichier.

public synchronized Long getSize()

Renvoie la taille du fichier.

Le code de l’exemple suivant utilise SnowflakeFile pour lire un fichier à partir d’un emplacement de zone de préparation spécifié. En utilisant un InputStream à partir de la méthode getInputStream, il lit le contenu du fichier dans une variable String.

create or replace function sum_total_sales(file string)
returns INTEGER
language java
handler='SalesSum.sumTotalSales'
target_path='@jar_stage/sales_functions2.jar'
as
$$
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.snowflake.snowpark_java.types.SnowflakeFile;

public class SalesSum {

  public static int sumTotalSales(String filePath) throws IOException {
    int total = -1;

    // Use a SnowflakeFile instance to read sales data from a stage.
    SnowflakeFile file = SnowflakeFile.newInstance(filePath);
    InputStream stream = file.getInputStream();
    String contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);

    // Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.

    return total;
  }
}
$$;

Appeler l’UDF, en spécifiant un fichier sur une zone de préparation à traiter.

select sum_total_sales('@sales_data_stage/car_sales.json');

Lire un fichier en utilisant la classe InputStream

Vous pouvez lire le contenu du fichier directement dans un java.io.InputStream en faisant de l’argument de votre fonction de traitement une variable InputStream.

Le code de l’exemple suivant comporte une fonction de gestionnaire sumTotalSales qui prend un InputStream et renvoie un int. Au moment de l’exécution, Snowflake attribue automatiquement le contenu du fichier au chemin d’accès de la variable file à la variable de l’argument stream.

create or replace function sum_total_sales(file string)
returns INTEGER
language java
handler='SalesSum.sumTotalSales'
target_path='@jar_stage/sales_functions2.jar'
as
$$
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class SalesSum {

  public static int sumTotalSales(InputStream stream) throws IOException {
    int total = -1;
    String contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);

    // Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.

    return total;
  }
}
$$;

Appeler l’UDF, en spécifiant un fichier sur une zone de préparation à traiter.

select sum_total_sales('@sales_data_stage/car_sales.json');

Création et appel d’une simple UDF Java précompilée

Les instructions suivantes créent une simple UDF Java. Cet échantillon suit généralement la structure des fichiers et des répertoires décrite dans Organiser vos fichiers.

Créer et compiler le code du gestionnaire Java

  1. Créez un fichier .java qui contient votre code source.

    package mypackage;
    
    public class MyUDFHandler {
    
      public static int decrementValue(int i)
      {
          return i - 1;
      }
    
      public static void main(String[] argv)
      {
          System.out.println("This main() function won't be called.");
      }
    }
    
  2. Compilez le code source. Cet exemple stocke le ou les fichiers .class générés dans le répertoire nommé classDirectory.

    javac -d classDirectory mypackage/MyUDFHandler.java
    

Empaqueter le code compilé dans un fichier JAR

  1. En option, créez un fichier manifeste similaire à celui présenté ci-dessous :

    Manifest-Version: 1.0
    Main-Class: MyUDFHandler.class
    
  2. Créez le fichier JAR à partir du fichier .class. Cet exemple utilise -C classDirectory pour spécifier l’emplacement des fichiers .class :

    jar cmf my_udf_manifest.manifest ./my_udf_jar.jar -C classDirectory mypackage/MyUDFHandler.class
    

Charger le fichier JAR avec le gestionnaire compilé vers une zone de préparation

Utilisez la commande PUT pour copier le fichier JAR du système de fichiers local vers une zone de préparation. Cet exemple utilise la zone de préparation par défaut de l’utilisateur, nommée @~ :

put
    file:///Users/Me/JavaUDFExperiments/my_udf_jar.jar
    @~/my_decrement_udf_package_dir/
    auto_compress = false
    overwrite = true
    ;

Vous pouvez stocker la commande PUT dans un fichier de script, puis exécuter ce fichier via snowsql.

La commande snowsql doit être similaire à ce qui suit :

snowsql -a <account_identifier> -w <warehouse> -d <database> -s <schema> -u <user> -f put_command.sql

Cet exemple suppose que le mot de passe de l’utilisateur est spécifié dans la variable d’environnement SNOWSQL_PWD.

Créer l’UDF avec le code compilé comme gestionnaire

Créez l’UDF :

create function my_decrement_udf(i numeric(9, 0))
    returns numeric
    language java
    imports = ('@~/my_decrement_udf_package_dir/my_decrement_udf_jar.jar')
    handler = 'my_decrement_udf_package.my_decrement_udf_class.my_decrement_udf_method'
    ;

Appelez l’UDF :

SELECT my_decrement_udf(-15);
+-----------------------+
| MY_DECREMENT_UDF(-15) |
|-----------------------|
|                   -16 |
+-----------------------+

Considérations

  • Si une requête appelle une UDF pour accéder à des fichiers en zone de préparation, l’opération échoue avec une erreur utilisateur si l’instruction SQL interroge également une vue qui appelle une UDF ou une UDTF, que la fonction de la vue accède ou non à des fichiers en zone de préparation.

  • Les UDTFs peuvent traiter plusieurs fichiers en parallèle ; cependant, les UDFs traitent actuellement les fichiers en série. Comme solution de rechange, regroupez les lignes dans une sous-requête en utilisant la clause GROUP BY. Voir Exemples d’UDTF Java pour un exemple.

  • Actuellement, si les fichiers en zone de préparation référencés dans une requête sont modifiés ou supprimés pendant l’exécution de la requête, l’appel de la fonction renvoie une erreur.

Revenir au début