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’un gestionnaire d’UDF 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;
    }
  }';
Copy

Appelez l’UDF :

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

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               |
+--------------------+
Copy

Transmission de valeurs de tableaux¶

Les mĂ©thodes Java peuvent recevoir ces tableaux SQL de deux maniĂšres diffĂ©rentes :

  • En utilisant la fonctionnalitĂ© de tableau Java.

  • En utilisant la fonctionnalitĂ© varargs (nombre variable d’arguments) de Java.

Dans les deux cas, votre code SQL doit transmettre un ARRAY.

Note

Veillez Ă  utiliser des types Java avec des mappages valides avec les types SQL. Pour plus d’informations, reportez-vous Ă  Mappages de type de donnĂ©es SQL-Java.

Transmission par un ARRAY¶

DĂ©clarez le paramĂštre Java comme un tableau. Par exemple, le troisiĂšme paramĂštre de la mĂ©thode suivante est un tableau ChaĂźne :

static int myMethod(int fixedArgument1, int fixedArgument2, String[] stringArray)
Copy

Vous trouverez ci-dessous un exemple complet :

CrĂ©er et charger la table :

CREATE TABLE string_array_table(id INTEGER, a ARRAY);
INSERT INTO string_array_table (id, a) SELECT
        1, ARRAY_CONSTRUCT('Hello');
INSERT INTO string_array_table (id, a) SELECT
        2, ARRAY_CONSTRUCT('Hello', 'Jay');
INSERT INTO string_array_table (id, a) SELECT
        3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');
Copy

CrĂ©ez l’UDF :

CREATE OR REPLACE FUNCTION concat_varchar_2(a ARRAY)
  RETURNS VARCHAR
  LANGUAGE JAVA
  HANDLER = 'TestFunc_2.concatVarchar2'
  TARGET_PATH = '@~/TestFunc_2.jar'
  AS
  $$
  class TestFunc_2 {
      public static String concatVarchar2(String[] strings) {
          return String.join(" ", strings);
      }
  }
  $$;
Copy

Appelez l’UDF :

SELECT concat_varchar_2(a)
  FROM string_array_table
  ORDER BY id;
+---------------------+
| CONCAT_VARCHAR_2(A) |
|---------------------|
| Hello               |
| Hello Jay           |
| Hello Jay Smith     |
+---------------------+
Copy

Transmission par des Varargs¶

L’utilisation de varargs est trùs similaire à celle d’un tableau.

Dans votre code Java, utilisez le style de dĂ©claration varargs de Java :

static int myMethod(int fixedArgument1, int fixedArgument2, String ... stringArray)
Copy

Vous trouverez ci-dessous un exemple complet. La seule diffĂ©rence significative entre cet exemple et l’exemple prĂ©cĂ©dent (pour les tableaux) est la dĂ©claration des paramĂštres de la mĂ©thode.

CrĂ©er et charger la table :

CREATE TABLE string_array_table(id INTEGER, a ARRAY);
INSERT INTO string_array_table (id, a) SELECT
        1, ARRAY_CONSTRUCT('Hello');
INSERT INTO string_array_table (id, a) SELECT
        2, ARRAY_CONSTRUCT('Hello', 'Jay');
INSERT INTO string_array_table (id, a) SELECT
        3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');
Copy

CrĂ©ez l’UDF :

CREATE OR REPLACE FUNCTION concat_varchar(a ARRAY)
  RETURNS VARCHAR
  LANGUAGE JAVA
  HANDLER = 'TestFunc.concatVarchar'
  TARGET_PATH = '@~/TestFunc.jar'
  AS
  $$
  class TestFunc {
      public static String concatVarchar(String ... stringArray) {
          return String.join(" ", stringArray);
      }
  }
  $$;
Copy

Appelez l’UDF :

SELECT concat_varchar(a)
    FROM string_array_table
    ORDER BY id;
+-------------------+
| CONCAT_VARCHAR(A) |
|-------------------|
| Hello             |
| Hello Jay         |
| Hello Jay Smith   |
+-------------------+
Copy

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;
    }
  }
  $$;
Copy

Appelez l’UDF :

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

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"} }');
Copy

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);
    }
  }
  $$;
Copy

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"        |                                                  |
| }                                   |                                                  |
+-------------------------------------+--------------------------------------------------+
Copy

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);
    }
  }
  $$;
Copy

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;
Copy

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;
    }
  }
  $$;
Copy

Lecture d’un fichier Ă  l’aide d’une UDF Java¶

Vous pouvez lire le contenu d’un fichier avec le code du gestionnaire. Par exemple, vous pourriez vouloir lire un fichier pour traiter des donnĂ©es non structurĂ©es avec le gestionnaire. Pour de plus amples informations sur le traitement des donnĂ©es non structurĂ©es, ainsi que des exemples de code, reportez-vous Ă  Traitement de donnĂ©es non structurĂ©es avec des gestionnaires d’UDF et de procĂ©dures.

Le fichier doit se trouver sur une zone de préparation Snowflake qui est disponible pour votre gestionnaire.

Pour lire le contenu des fichiers en zone de prĂ©paration, votre gestionnaire peut :

  • Lire un fichier dont le chemin est spĂ©cifiĂ© de façon statique dans la clause IMPORTS. Au moment de l’exĂ©cution, votre code lit le fichier dans le rĂ©pertoire personnel de l’UDF.

    Cela peut ĂȘtre utile lorsque vous souhaitez accĂ©der au fichier pendant l’initialisation.

  • Lisez un fichier spĂ©cifiĂ© de façon dynamique en appelant les mĂ©thodes de la classe SnowflakeFile ou de la classe InputStream.

    Vous pouvez procĂ©der ainsi si vous devez accĂ©der Ă  un fichier spĂ©cifiĂ© par l’appelant. Pour plus d’informations, voir les sections suivantes de ce chapitre :

    SnowflakeFile offre des fonctionnalités non disponibles avec InputStream, comme décrit dans le tableau suivant.

    Classe

    Entrée

    Remarques

    SnowflakeFile

    Formats d’URL :

    • URL scopĂ©e qui rĂ©duit le risque d’attaques par injection de fichiers lorsque l’appelant de la fonction n’est pas Ă©galement son propriĂ©taire.

    • URL de fichier ou chemin de chaĂźne pour les fichiers auxquels le propriĂ©taire de l’UDF a accĂšs.

    Le fichier doit ĂȘtre situĂ© dans une zone de prĂ©paration interne ou externe nommĂ©e.

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

    InputStream

    Formats d’URL :

    • URL scopĂ©e qui rĂ©duit le risque d’attaques par injection de fichiers lorsque l’appelant de la fonction n’est pas Ă©galement son propriĂ©taire.

    Le fichier doit ĂȘtre situĂ© 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 SĂ©lection 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 de fichiers de donnĂ©es Ă  partir d’un systĂšme de fichiers local.

Lecture d’un fichier spĂ©cifiĂ© statiquement dans IMPORTS¶

Votre gestionnaire peut lire un fichier dont le chemin de zone de préparation a été spécifié dans la clause IMPORTS de la commande CREATE FUNCTION.

Lorsque vous spĂ©cifiez un fichier dans la clause IMPORTS, Snowflake copie ce fichier de la zone de prĂ©paration vers le rĂ©pertoire personnel (Ă©galement appelĂ© rĂ©pertoire d’importation) de l’UDF, qui est le rĂ©pertoire Ă  partir duquel l’UDF lit rĂ©ellement le fichier.

Étant donnĂ© que les fichiers importĂ©s sont copiĂ©s dans un seul rĂ©pertoire et qu’ils doivent avoir des noms uniques au sein de ce rĂ©pertoire, chaque fichier de la clause IMPORTS doit avoir un nom distinct, mĂȘme si les fichiers commencent dans diffĂ©rentes zones de prĂ©paration ou diffĂ©rents sous-rĂ©pertoires au sein d’une zone de prĂ©paration.

L’exemple suivant crĂ©e et appelle une UDF Java qui lit un fichier :

Le code source Java ci-dessous crée une méthode Java nommée readFile. Cette UDF utilise cette méthode.

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

class TestReadRelativeFile {
  public static String readFile(String fileName) throws IOException {
    StringBuilder contentBuilder = new StringBuilder();
    String importDirectory = System.getProperty("com.snowflake.import_directory");
    String fPath = importDirectory + fileName;
    Stream<String> stream = Files.lines(Paths.get(fPath), StandardCharsets.UTF_8);
    stream.forEach(s -> contentBuilder.append(s).append("\n"));
    return contentBuilder.toString();
  }
}
Copy

Le code SQL suivant crĂ©e l” UDF. Ce code suppose que le code source Java a Ă©tĂ© compilĂ© et placĂ© dans un fichier JAR nommĂ© TestReadRelativeFile.jar, que l’UDF importe. Les deuxiĂšme et troisiĂšme fichiers importĂ©s, my_config_file_1.txt et my_config_file_2.txt, sont des fichiers de configuration que l’UDF peut lire.

CREATE FUNCTION file_reader(file_name VARCHAR)
  RETURNS VARCHAR
  LANGUAGE JAVA
  IMPORTS = ('@my_stage/my_package/TestReadRelativeFile.jar',
             '@my_stage/my_path/my_config_file_1.txt',
             '@my_stage/my_path/my_config_file_2.txt')
  HANDLER = 'my_package.TestReadRelativeFile.readFile';
Copy

Ce code appelle l’UDF :

SELECT file_reader('my_config_file_1.txt') ...;
...
SELECT file_reader('my_config_file_2.txt') ...;
Copy

Choisir d’accĂ©der Ă  un fichier dans un format compressĂ© ou non compressé¶

Les fichiers d’une zone de prĂ©paration peuvent ĂȘtre stockĂ©s en format compressĂ© ou non compressĂ©. Les utilisateurs peuvent compresser le fichier avant de le copier dans la zone de prĂ©paration, ou demander Ă  la commande PUT de compresser le fichier.

Lorsque Snowflake copie un fichier compressĂ© au format GZIP d’une zone de prĂ©paration vers le rĂ©pertoire d’accueil de l’UDF, Snowflake peut Ă©crire la copie telle quelle, ou Snowflake peut dĂ©compresser le contenu avant d’écrire le fichier.

Si le fichier de la zone de prĂ©paration est compressĂ© et si vous souhaitez que la copie dans le rĂ©pertoire d’accueil de l’UDF soit Ă©galement compressĂ©e, lorsque vous spĂ©cifiez le nom du fichier dans la clause IMPORTS, utilisez simplement le nom du fichier original (par ex. « MyData.txt.gz Â») dans la clause IMPORTS. Par exemple :

... IMPORTS = ('@MyStage/MyData.txt.gz', ...)
Copy

Si le fichier de la zone de prĂ©paration est compressĂ© au format GZIP et si vous souhaitez que la copie dans le rĂ©pertoire d’accueil de l’UDF soit Ă©galement compressĂ©e, lorsque vous spĂ©cifiez le nom du fichier dans la clause IMPORTS, retirez l’extension « .gz Â». Par exemple, si votre zone de prĂ©paration contient « MyData.txt.gz Â», mais que vous voulez que votre UDF lise le fichier au format non compressĂ©, spĂ©cifiez « MyData.txt Â» dans la clause IMPORTS. S’il n’existe pas dĂ©jĂ  un fichier non compressĂ© nommĂ© « MyData.txt Â», alors Snowflake recherche « MyData.txt.gz Â» et Ă©crit automatiquement une copie dĂ©compressĂ©e dans « MyData.txt Â» dans le rĂ©pertoire personnel de l’UDF. Votre UDF peut alors ouvrir et lire le fichier non compressĂ© « MyData.txt Â».

Notez que la dĂ©compression intelligente ne s’applique qu’à la copie dans le rĂ©pertoire d’accueil de l’UDF ; le fichier original dans la zone de prĂ©paration n’est pas modifiĂ©.

Suivez ces meilleures pratiques pour la manipulation des fichiers compressĂ©s :

  • Respectez les conventions de dĂ©nomination des fichiers. Si un fichier est au format compressĂ© GZIP, incluez l’extension « .gz Â» Ă  la fin du nom du fichier. Si un fichier n’est pas au format compressĂ© GZIP, il ne faut pas terminer le nom du fichier par l’extension « .gz Â».

  • Évitez de crĂ©er des fichiers dont les noms ne diffĂšrent que par l’extension « .gz Â». Par exemple, ne crĂ©ez pas Ă  la fois « MyData.txt Â» et « MyData.txt.gz Â» dans la mĂȘme zone de prĂ©paration et le mĂȘme rĂ©pertoire, et n’essayez pas d’importer Ă  la fois « MyData.txt Â» et « MyData.txt.gz Â» dans la mĂȘme commande CREATE FUNCTION.

  • Ne compressez pas les fichiers deux fois. Par exemple, si vous compressez un fichier manuellement, puis vous placez (PUT) ce fichier sans utiliser AUTO_COMPRESS=FALSE, le fichier sera compressĂ© une seconde fois. La dĂ©compression intelligente ne le dĂ©compressera qu’une seule fois, de sorte que le fichier de donnĂ©es (ou JAR) sera toujours compressĂ© lorsqu’il sera stockĂ© dans le rĂ©pertoire d’accueil de l’UDF.

  • À l’avenir, Snowflake pourrait Ă©tendre la dĂ©compression intelligente Ă  des algorithmes de compression autres que GZIP. Pour Ă©viter les problĂšmes de compatibilitĂ© Ă  l’avenir, appliquez ces meilleures pratiques aux fichiers qui utilisent tout type de compression.

Note

Les fichiers JAR peuvent Ă©galement ĂȘtre stockĂ©s en format compressĂ© ou non compressĂ© dans une zone de prĂ©paration. Snowflake dĂ©compresse automatiquement tous les fichiers JAR compressĂ©s avant de les mettre Ă  la disposition de l’UDF Java.

Lecture d’un fichier spĂ©cifiĂ© de façon dynamique avec 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.

Note

Pour rendre votre code rĂ©sistant aux attaques par injection de fichiers, utilisez toujours une URL scopĂ©e lorsque vous transmettez l’emplacement d’un fichier Ă  une UDF, en particulier lorsque l’appelant de la fonction n’est pas Ă©galement son propriĂ©taire. Vous pouvez crĂ©er une URL scopĂ©e en SQL Ă  l’aide de la fonction intĂ©grĂ©e BUILD_SCOPED_FILE_URL. Pour plus d’informations sur le rĂŽle de BUILD_SCOPED_FILE_URL, voir Introduction aux donnĂ©es non structurĂ©es.

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 clientes Snowpark ne peuvent pas utiliser cette classe.

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.

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;
    }
  }
  $$;
Copy

Appelez l’UDF, en transmettant l’emplacement du fichier dans une URL scopĂ©e pour rĂ©duire la probabilitĂ© d’attaques par injection de fichier.

SELECT sum_total_sales(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

Note

Le propriĂ©taire de l’UDF doit avoir accĂšs Ă  tous les fichiers dont les emplacements ne sont pas des URLs scopĂ©es. Vous pouvez lire ces fichiers en zone de prĂ©paration en demandant au code du gestionnaire d’appeler la mĂ©thode SnowflakeFile.newInstance avec une valeur boolean pour un nouveau paramĂštre requireScopedUrl.

L’exemple suivant utilise SnowflakeFile.newInstance tout en spĂ©cifiant qu’une URL scopĂ©e n’est pas nĂ©cessaire.

String filename = "@my_stage/filename.txt";
String sfFile = SnowflakeFile.newInstance(filename, false);
Copy

Lecture d’un fichier spĂ©cifiĂ© de façon dynamique avec 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. Cela peut ĂȘtre utile lorsque l’appelant de la fonction veut passer un chemin de fichier comme argument.

Note

Pour rendre votre code rĂ©sistant aux attaques par injection de fichiers, utilisez toujours une URL scopĂ©e lorsque vous transmettez l’emplacement d’un fichier Ă  une UDF, en particulier lorsque l’appelant de la fonction n’est pas Ă©galement son propriĂ©taire. Vous pouvez crĂ©er une URL scopĂ©e en SQL Ă  l’aide de la fonction intĂ©grĂ©e BUILD_SCOPED_FILE_URL. Pour plus d’informations sur le rĂŽle de BUILD_SCOPED_FILE_URL, voir Introduction aux donnĂ©es non structurĂ©es.

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;
    }
  }
  $$;
Copy

Appelez l’UDF, en transmettant l’emplacement du fichier dans une URL scopĂ©e pour rĂ©duire la probabilitĂ© d’attaques par injection de fichier.

SELECT sum_total_sales(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

CrĂ©ation et appel d’une UDF Java simple et mise en zone de prĂ©paration¶

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. Dans le répertoire racine de votre projet (ici, my_udf), créez un sous-répertoire src pour contenir les fichiers .java source et un sous-répertoire classes pour contenir les fichiers .class générés.

    Vous devriez avoir une hiĂ©rarchie de rĂ©pertoires semblable Ă  la suivante :

    my_udf/
    |-- classes/
    |-- src/
    
    Copy
  2. Dans le répertoire src , créez un répertoire appelé mypackage pour contenir les fichiers .java dont les classes sont dans le package mypackage.

  3. Dans le répertoire mypackage , créez un fichier MyUDFHandler.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.");
      }
    }
    
    Copy
  4. Depuis le répertoire racine de votre projet (ici, my_udf), utilisez la commande javac pour compiler le code source.

    La commande javac de l’exemple suivant compile MyUDFHandler.java pour gĂ©nĂ©rer un fichier MyUDFHandler.class dans le rĂ©pertoire classes.

    javac -d classes src/mypackage/MyUDFHandler.java
    
    Copy

    Cet exemple comprend les arguments suivants :

    • -d classes – RĂ©pertoire dans lequel les fichiers de classe gĂ©nĂ©rĂ©s doivent ĂȘtre Ă©crits.

    • src/mypackage/MyUDFHandler.java – Chemin du fichier .java sous la forme : source_directory/package_directory/Java_file_name.

Empaqueter le code compilé dans un fichier JAR¶

  1. En option, dans le rĂ©pertoire racine du projet, crĂ©ez un fichier manifeste nommĂ© my_udf.manifest qui contient les attributs suivants :

    Manifest-Version: 1.0
    Main-Class: mypackage.MyUDFHandler
    
    Copy
  2. À partir du rĂ©pertoire racine de votre projet, exĂ©cutez la commande jar pour crĂ©er un fichier JAR contenant le fichier .class et le manifeste.

    La commande jar de l’exemple suivant place le fichier MyUDFHandler.class gĂ©nĂ©rĂ© dans un dossier de package mypackage dans un fichier .jar appelĂ© my_udf.jar. L’indicateur -C ./classes spĂ©cifie l’emplacement des fichiers .class.

    jar cmf my_udf.manifest my_udf.jar -C ./classes mypackage/MyUDFHandler.class
    
    Copy

    Cet exemple comprend les arguments suivants :

    • cmf – Arguments de la commande : c pour crĂ©er un fichier JAR, m pour utiliser le fichier .manifest spĂ©cifiĂ©, et f pour donner au fichier JAR le nom spĂ©cifiĂ©.

    • my_udf.manifest – Fichier manifeste.

    • my_udf.jar – Nom du fichier JAR Ă  crĂ©er.

    • -C ./classes – RĂ©pertoire contenant les fichiers .class gĂ©nĂ©rĂ©s.

    • mypackage/MyUDFHandler.class – Package et nom du fichier .class Ă  inclure dans le JAR.

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

  1. Dans Snowflake, créez une zone de préparation appelée jar_stage pour stocker le fichier JAR contenant votre gestionnaire UDF.

    Pour plus d’informations sur la crĂ©ation d’une zone de prĂ©paration, voir CREATE STAGE.

  2. Utilisez la commande PUT pour copier le fichier JAR du systÚme de fichiers local vers une zone de préparation.

    put
        file:///Users/Me/my_udf/my_udf.jar
        @jar_stage
        auto_compress = false
        overwrite = true
        ;
    
    Copy

    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
    
    Copy

    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 decrement_value(i NUMERIC(9, 0))
  RETURNS NUMERIC
  LANGUAGE JAVA
  IMPORTS = ('@jar_stage/my_udf.jar')
  HANDLER = 'mypackage.MyUDFHandler.decrementValue'
  ;
Copy

Appelez l’UDF :

SELECT decrement_value(-15);
Copy
+----------------------+
| DECREMENT_VALUE(-15) |
|----------------------|
|                  -16 |
+----------------------+