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;
}
}';
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 |
+--------------------+
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)
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');
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);
}
}
$$;
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 |
+---------------------+
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)
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');
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);
}
}
$$;
Appelez lâUDF :
SELECT concat_varchar(a)
FROM string_array_table
ORDER BY id;
+-------------------+
| CONCAT_VARCHAR(A) |
|-------------------|
| Hello |
| Hello Jay |
| Hello Jay Smith |
+-------------------+
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â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â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 classeInputStream
.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 :
Lecture dâun fichier spĂ©cifiĂ© de façon dynamique avec SnowflakeFile
Lecture dâun fichier spĂ©cifiĂ© de façon dynamique avec InputStream
SnowflakeFile
offre des fonctionnalités non disponibles avecInputStream
, 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 :
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.
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();
}
}
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';
Ce code appelle lâUDF :
SELECT file_reader('my_config_file_1.txt') ...;
...
SELECT file_reader('my_config_file_2.txt') ...;
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', ...)
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;
}
}
$$;
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'));
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);
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;
}
}
$$;
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'));
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¶
Dans le répertoire racine de votre projet (ici,
my_udf
), créez un sous-répertoiresrc
pour contenir les fichiers .java source et un sous-répertoireclasses
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/
Dans le répertoire
src
, créez un répertoire appelémypackage
pour contenir les fichiers .java dont les classes sont dans le packagemypackage
.Dans le répertoire
mypackage
, créez un fichierMyUDFHandler.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."); } }
Depuis le répertoire racine de votre projet (ici,
my_udf
), utilisez la commandejavac
pour compiler le code source.La commande
javac
de lâexemple suivant compileMyUDFHandler.java
pour générer un fichierMyUDFHandler.class
dans le répertoireclasses
.javac -d classes src/mypackage/MyUDFHandler.java
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¶
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
à 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 fichierMyUDFHandler.class
généré dans un dossier de packagemypackage
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
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é, etf
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¶
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.
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 ;
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 decrement_value(i NUMERIC(9, 0))
RETURNS NUMERIC
LANGUAGE JAVA
IMPORTS = ('@jar_stage/my_udf.jar')
HANDLER = 'mypackage.MyUDFHandler.decrementValue'
;
Appelez lâUDF :
SELECT decrement_value(-15);
+----------------------+
| DECREMENT_VALUE(-15) |
|----------------------|
| -16 |
+----------------------+