Beispiele für Java-UDF-Handler

Unter diesem Thema werden einfache Beispiele für in Java geschriebenen UDF-Handler-Code bereitgestellt.

Weitere Informationen zur Verwendung von Java beim Erstellen eines UDF-Handlers finden Sie unter Erstellen eines Java-UDF-Handlers.

Unter diesem Thema:

Erstellen und Aufrufen einer einfachen Inline-Java-UDF

Mit den folgenden Anweisungen wird eine Inline-Java-UDF erstellt und aufgerufen. Dieser Code gibt den VARCHAR-Wert zurück, der übergeben wurde.

Diese Funktion wird mit der optionalen CALLED ON NULL INPUT-Klausel deklariert, um anzugeben, dass die Funktion auch dann aufgerufen wird, wenn der Eingabewert NULL ist. (Diese Funktion würde mit und ohne diese Klausel NULL zurückgeben. Sie könnten den Code aber ändern, um NULL anders zu behandeln, z. B. um eine leere Zeichenfolge zurückzugeben).

Erstellen der 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

Aufrufen der UDF:

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

Übergeben von NULL an eine Inline-Java-UDF

Hier wird die oben definierte echo_varchar()-UDF verwendet. Der SQL NULL-Wert wird implizit in einen Java null-Wert umgewandelt, und dieser Java null-Wert wird zurückgegeben und implizit wieder in SQL-NULL umgewandelt:

Aufrufen der UDF:

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

Übergeben von Array-Werten

Java-Methoden können SQL-Arrays auf eine von zwei Arten erhalten:

  • Verwendung der Array-Funktion von Java.

  • Verwendung der Java-Funktion varargs (variable Anzahl von Argumenten).

In beiden Fällen muss Ihr SQL-Code ein ARRAY übergeben.

Bemerkung

Achten Sie darauf, Java-Typen mit gültigen Zuordnungen zu SQL-Typen zu verwenden. Weitere Informationen dazu finden Sie unter Zuordnung von Datentypen zwischen SQL und Java.

Werteübergabe via ARRAY

Deklarieren Sie den Java-Parameter als Array. In der folgenden Methode ist dritte Parameter beispielsweise ein String-Array:

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

Nachfolgend finden Sie ein vollständiges Beispiel:

Erstellen und Laden der Tabelle:

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

Erstellen der 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

Aufrufen der UDF:

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

Werteübergabe via varargs

Die Verwendung von varargs ist der Verwendung eines Arrays sehr ähnlich.

Verwenden Sie in Ihrem Java-Code den varargs-Deklarationsstil von Java:

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

Nachfolgend finden Sie ein vollständiges Beispiel. Der einzige wesentliche Unterschied zwischen diesem Beispiel und dem vorangegangenen Beispiel (für Arrays) ist die Deklaration der Parameter für die Methode.

Erstellen und Laden der Tabelle:

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

Erstellen der 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

Aufrufen der UDF:

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

Explizite Rückgabe von NULL aus einer Inline-UDF

Das folgende Codebeispiel zeigt, wie Sie einen NULL-Wert explizit zurückgeben. Der Java null-Wert wird in einen SQL NULL-Wert umgewandelt.

Erstellen der 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

Aufrufen der UDF:

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

Übergeben eines OBJECT-Werts an eine Inline-Java-UDF

Im folgenden Beispiel werden der Datentyp SQL OBJECT und der entsprechende Java-Datentyp (Map<String, String>) verwendet, um einen Wert aus OBJECT zu extrahieren. In diesem Beispiel wird auch gezeigt, dass Sie mehrere Parameter an eine Java-UDF übergeben können.

Erstellen und laden Sie eine Tabelle, die eine Spalte vom Typ OBJECT enthält:

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

Erstellen der 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

Aufrufen der 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

Übergeben eines GEOGRAPHY-Werts an eine Inline-Java-UDF

Im folgenden Beispiel wird der SQL-Datentyp GEOGRAPHY verwendet.

Erstellen der 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

Sie können die PACKAGES-Klausel verwenden, um ein Snowflake-Systempaket wie das Snowpark-Paket anzugeben. Wenn Sie dies tun, müssen Sie die Snowpark-JAR-Datei nicht mehr als Wert in einer IMPORTS-Klausel angeben. Weitere Informationen zu PACKAGES finden Sie unter Optionale CREATE FUNCTION-Parameter.

Erstellen Sie Daten, und rufen Sie mit diesen Daten die UDF auf:

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

Die Ausgabe sollte ungefähr wir folgt aussehen:

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

Übergeben eines VARIANT-Werts an eine Inline-Java-UDF

Wenn Sie einen Wert vom SQL-Typ VARIANT an eine Java-UDF übergeben, kann Snowflake den Wert in den Datentyp Variant konvertieren, der mit dem Snowpark-Paket bereitgestellt wird. Beachten Sie, dass Variant ab Snowpark-Paketversion 1.4.0 unterstützt wird.

Der Snowpark-Typ Variant bietet Methoden zur Konvertierung von Werten zwischen Variant und anderen Datentypen.

Für die Verwendung des Snowpark-Typs Variant muss beim Erstellen der UDF mit der PACKAGES-Klausel das Snowpark-Paket angegeben werden. Wenn Sie dies tun, müssen Sie die Snowpark-JAR-Datei nicht mehr als Wert in einer IMPORTS-Klausel angeben. Weitere Informationen zu PACKAGES finden Sie unter Optionale CREATE FUNCTION-Parameter.

Der Code im folgenden Beispiel empfängt JSON-Daten, die als VARIANT-Typ gespeichert sind, und verwendet dann den Variant-Typ der Snowpark-Bibliothek, um den Wert price aus dem JSON-Text abzurufen. Der empfangene JSON-Text hat eine ähnliche Struktur wie der in In Beispielen verwendete Beispieldaten angezeigte JSON-Text.

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

Lesen einer Datei mit einer Java-UDF

Sie können den Inhalt einer Datei mit Handler-Code lesen. Sie könnten zum Beispiel eine Datei lesen, um unstrukturierte Daten mit dem Handler zu verarbeiten. Weitere Informationen zum Verarbeiten von unstrukturierten Daten sowie entsprechenden Beispielcode finden Sie unter Verarbeiten von unstrukturierten Daten mit UDF- und Prozedur-Handlern.

Die Datei muss sich in einem Snowflake-Stagingbereich befinden, auf den Ihr Handler zugreifen kann.

Um den Inhalt von Stagingdateien zu lesen, verwenden Sie Ihren Handler wie folgt:

  • Der Handler liest eine Datei, deren Dateipfad statisch in der IMPORTS-Klausel angegeben ist. Zur Laufzeit liest der Handler die Datei aus dem Basisverzeichnis der UDF.

    Dies kann nützlich sein, wenn Sie auf die Datei während der Initialisierung zugreifen möchten.

  • Der Handler liest eine dynamisch spezifizierte Datei, indem er die Methoden der Klasse SnowflakeFile oder der Klasse InputStream aufruft.

    Dies kann der Fall sein, wenn Sie auf eine vom Aufrufer angegebene Datei zugreifen müssen. Weitere Informationen dazu finden Sie in den folgenden Abschnitten unter diesem Thema:

    SnowflakeFile bietet Features, die bei InputStream nicht verfügbar sind, wie in der folgenden Tabelle beschrieben.

    Klasse

    Eingabe

    Anmerkungen

    SnowflakeFile

    URL-Formate:

    • Bereichs-URL, um das Risiko von Angriffen per Dateieinschleusung zu verringern, wenn der Aufrufer der Funktion nicht deren Eigentümer ist.

    • Datei-URL oder Dateipfad-Zeichenfolge von Dateien, auf die der Eigentümer der UDF Zugriff hat.

    Die Datei muss sich in einem benannten internen Stagingbereich oder in einem externen Stagingbereich befinden.

    Einfacher Zugriff auf zusätzliche Dateiattribute, wie z. B. die Dateigröße.

    InputStream

    URL-Formate:

    • Bereichs-URL, um das Risiko von Angriffen per Dateieinschleusung zu verringern, wenn der Aufrufer der Funktion nicht deren Eigentümer ist.

    Die Datei muss sich in einem internen oder externen Stagingbereich befinden.

Voraussetzungen

Bevor der Java-Handler-Code eine in einem Stagingbereich befindliche Datei lesen kann, müssen Sie Folgendes tun, um die Datei für den Code zur Verfügung zu stellen:

  1. Erstellen Sie einen Stagingbereich, auf den Ihr Handler zugreifen kann.

    Sie können einen externen Stagingbereich oder eine interne Stagingbereich verwenden. Wenn Sie einen internen Stagingbereich verwenden, muss es sich um einen Benutzer- oder benannten Stagingbereich handeln. Snowflake unterstützt derzeit nicht die Verwendung von Tabellen-Stagingbereichen für UDF-Abhängigkeiten. Weitere Informationen zum Erstellen eines Stagingbereichs finden Sie unter CREATE STAGE. Weitere Informationen zur Auswahl des Typs eines internen Stagingbereichs finden Sie unter Auswahl eines internen Stagingbereichs für lokale Dateien.

    Denken Sie daran, dass Rollen, die SQL-Aktionen zum Lesen aus einem Stagingbereich ausführen, entsprechende Berechtigungen für den jeweiligen Stagingbereich zugewiesen sein müssen. Weitere Informationen dazu finden Sie unter Granting Privileges for User-Defined Functions.

  2. Kopieren Sie die Datei, die vom Code gelesen werden soll, in den Stagingbereich.

    Sie können die Datei von einem lokalen Laufwerk in einen Stagingbereich kopieren, indem Sie den Befehl PUT verwenden. Weitere Informationen zu diesem Befehl finden Sie unter PUT. Weitere Informationen zum Staging von Dateien mit PUT finden Sie unter Staging von Datendateien aus einem lokalen Dateisystem.

Lesen einer in IMPORTS statisch spezifizierten Datei

Ihr Handler kann eine Datei lesen, deren Stagingbereich in der IMPORTS-Klausel des Befehls CREATE FUNCTION angegeben wurde.

Wenn in der IMPORTS-Klausel eine Datei angegeben wird, kopiert Snowflake diese Datei vom Stagingbereich in das Basisverzeichnis der UDF (auch Home-Verzeichnis oder Importverzeichnis genannt), welches das Verzeichnis ist, aus dem die UDF die Datei schließlich ausliest.

Da importierte Dateien in ein einziges Verzeichnis kopiert werden und innerhalb dieses Verzeichnisses eindeutige Namen haben müssen, muss jede Datei in der IMPORTS-Klausel einen eindeutigen Namen haben, auch wenn sich die Dateien in verschiedenen Stagingbereichen oder verschiedenen Unterverzeichnissen innerhalb eines Stagingbereichs befinden.

Im folgenden Beispiel wird eine Java-UDF zum Lesen einer Datei erstellt und aufgerufen.

Der Java-Quellcode erstellt eine Java-Methode mit dem Namen readFile. Die UDF verwendet diese Methode.

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

Mit dem folgenden SQL-Code wird die UDF erstellt. Dieser Code geht davon aus, dass der Java-Quellcode kompiliert und in einer JAR-Datei mit dem Namen TestReadRelativeFile.jar bereitgestellt wurde, die von der UDF importiert wird. Die zweite und die dritte importierte Datei, my_config_file_1.txt und my_config_file_2.txt, sind Konfigurationsdateien, die von der UDF gelesen werden können.

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

Der folgende Code ruft die UDF auf:

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

Entscheiden, ob Zugriff auf Datei im komprimierten oder nicht komprimierten Format erfolgen soll

Dateien können in einem Stagingbereich in komprimiertem oder unkomprimiertem Format gespeichert werden. Der Benutzer kann die Datei komprimieren, bevor er sie in den Stagingbereich kopiert, oder er kann im Befehl PUT anweisen, die Datei zu komprimieren.

Wenn Snowflake eine im GZIP-Format komprimierte Datei von einem Stagingbereich in das Basisverzeichnis der UDF kopiert, kann Snowflake die Datei so kopieren, wie sie ist, oder Snowflake kann den Inhalt vor dem Schreiben der Datei dekomprimieren.

Wenn die Datei im Stagingbereich komprimiert ist und wenn Sie möchten, dass die Kopie im Basisverzeichnis der UDF ebenfalls komprimiert vorliegen soll, dann verwenden Sie bei der Angabe des Dateinamens in der IMPORTS-Klausel einfach den ursprünglichen Dateinamen (z. B. „MyData.txt.gz“). Beispiel:

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

Wenn die Datei im Stagingbereich GZIP-komprimiert ist, Sie aber möchten, dass die Kopie im UDF-Home-Verzeichnis nicht komprimiert ist, dann lassen Sie bei der Angabe des Dateinamens in der IMPORTS-Klausel die Erweiterung „.gz“ weg. Wenn der Stagingbereich beispielsweise „MyData.txt.gz“ enthält, Sie aber möchten, dass Ihre UDF die Datei im nicht komprimierten Format liest, dann geben Sie in der IMPORTS-Klausel „MyData.txt“ an. Wenn es noch keine nicht komprimierte Datei mit dem Namen „MyData.txt“ vorhanden ist, dann sucht Snowflake nach „MyData.txt.gz“ und schreibt automatisch eine dekomprimierte Kopie „MyData.txt“ in das Home-Verzeichnis der UDF. Ihre UDF kann dann die nicht komprimierte Datei „MyData.txt“ öffnen und lesen.

Beachten Sie, dass die intelligente Dekomprimierung nur für die Kopie im UDF-Home-Verzeichnis gilt. Die Originaldatei im Stagingbereich wird nicht verändert.

Folgen Sie den bewährten Verfahren für den Umgang mit komprimierten Dateien:

  • Befolgen Sie die korrekten Konventionen für die Dateibenennung. Wenn eine Datei im GZIP-komprimierten Format vorliegt, fügen Sie am Ende des Dateinamens die Erweiterung „.gz“ hinzu. Wenn eine Datei nicht im GZIP-komprimierten Format vorliegt, darf der Dateiname nicht mit der Erweiterung „.gz“ enden.

  • Vermeiden Sie es, Dateien zu erstellen, deren Namen sich nur durch die Endung „.gz“ unterscheiden. Erstellen Sie z. B. nicht gleichzeitig „MyData.txt“ und „MyData.txt.gz“ im selben Stagingbereich und Verzeichnis, und versuchen Sie nicht, sowohl „MyData.txt“ als auch „MyData.txt.gz“ im selben CREATE FUNCTION-Befehl zu importieren.

  • Komprimieren Sie Dateien nicht doppelt. Wenn Sie z. B. eine Datei manuell komprimieren und dann diese Datei ohne Verwendung von AUTO_COMPRESS = FALSE mit PUT komprimieren, wird die Datei ein zweites Mal komprimiert. Bei der intelligenten Dekomprimierung wird sie nur einmal dekomprimiert, sodass die Datendatei (oder JAR-Datei) immer noch komprimiert ist, wenn sie im Home-Verzeichnis der UDF gespeichert ist.

  • In Zukunft erweitert Snowflake möglicherweise die intelligente Dekomprimierung auf weitere Komprimierungsalgorithmen als nur GZIP. Um Kompatibilitätsprobleme in Zukunft zu vermeiden, wenden Sie folgende bewährte Verfahren auf Dateien an, die einen beliebigen Typ von Komprimierung verwenden.

Bemerkung

Auch JAR-Dateien können in einem Stagingbereich in komprimiertem oder unkomprimiertem Format gespeichert werden. Snowflake dekomprimiert automatisch alle komprimierten JAR-Dateien, bevor sie der Java-UDF zur Verfügung gestellt werden.

Lesen einer mit SnowflakeFile dynamisch spezifizierten Datei

Die Methoden der SnowflakeFile-Klasse können im Java-Handler-Code verwendet werden, um Dateien aus einem Stagingbereich zu lesen. Die SnowflakeFile-Klasse wird in den Klassenpfad aufgenommen, der in Snowflake für Java-UDF-Handler verfügbar ist.

Bemerkung

Um Ihren Code widerstandsfähig gegen Angriffe per Dateieinschleusung zu machen, verwenden Sie immer eine Bereichs-URL, wenn Sie den Speicherort einer Datei an eine UDF übergeben, insbesondere wenn der Aufrufer der Funktion nicht auch ihr Eigentümer ist. Mit der integrierten Funktion BUILD_SCOPED_FILE_URL können Sie eine Bereichs-URL in SQL erstellen. Weitere Informationen zur Funktion BUILD_SCOPED_FILE_URL finden Sie unter Einführung in unstrukturierte Daten.

Um Ihren UDF-Code lokal zu entwickeln, fügen Sie die Snowpark-JAR-Datei, die die SnowflakeFile-Klasse enthält, zum Klassenpfad Ihres Codes hinzu. Weitere Informationen zur snowpark.jar-Datei finden Sie unter Einrichten Ihrer Entwicklungsumgebung für Snowpark Java. Beachten Sie, dass Snowpark-Clientanwendungen diese Klasse nicht verwenden können.

Wenn Sie SnowflakeFile verwenden, ist es beim Erstellen der UDF nicht notwendig, in einer IMPORTS-Klausel die Stagingdatei bzw. die JAR-Datei, die SnowflakeFile enthält, anzugeben, wie dies in SQL mit einer CREATE FUNCTION-Anweisung erforderlich ist.

Im folgenden Codebeispiel wird SnowflakeFile verwendet, um eine Datei vom Speicherort des Stagingbereichs zu lesen. Mit InputStream aus der getInputStream-Methode wird der Inhalt der Datei in eine String-Variable gelesen.

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

Rufen Sie die UDF auf, und übergeben Sie den Speicherort der Datei in einer Bereichs-URL, um die Wahrscheinlichkeit von Angriffen mittels Dateieinschleusung zu verringern.

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

Bemerkung

Der Eigentümer der UDF muss Zugriff auf alle Dateien haben, deren Speicherorte keine Bereichs-URLs sind. Sie können diese Stagingdateien lesen, indem Ihr Handler-Code die Methode SnowflakeFile.newInstance mit einem boolean-Wert für einen neuen requireScopedUrl-Parameter aufruft.

Das folgende Beispiel verwendet SnowflakeFile.newInstance und gibt gleichzeitig an, dass keine Bereichs-URL erforderlich ist.

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

Lesen einer mit InputStream dynamisch spezifizierten Datei

Sie können den Inhalt einer Datei direkt in einen java.io.InputStream-Eingabestream einlesen, indem Sie das Argument Ihrer Handler-Funktion als InputStream-Variable behandeln. Dies kann nützlich sein, wenn der Aufrufer der Funktion einen Dateipfad als Argument übergeben möchte.

Bemerkung

Um Ihren Code widerstandsfähig gegen Angriffe per Dateieinschleusung zu machen, verwenden Sie immer eine Bereichs-URL, wenn Sie den Speicherort einer Datei an eine UDF übergeben, insbesondere wenn der Aufrufer der Funktion nicht auch ihr Eigentümer ist. Mit der integrierten Funktion BUILD_SCOPED_FILE_URL können Sie eine Bereichs-URL in SQL erstellen. Weitere Informationen zur Funktion BUILD_SCOPED_FILE_URL finden Sie unter Einführung in unstrukturierte Daten.

Im folgenden Codebeispiel nimmt ein Handler-Funktion sumTotalSales einen InputStream-Wert entgegen und gibt einen int-Wert zurück. Zur Laufzeit weist Snowflake den Inhalt der Datei mit dem in der Variable file angegebenen Pfad automatisch der Argumentvariablen stream zu.

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

Rufen Sie die UDF auf, und übergeben Sie den Speicherort der Datei in einer Bereichs-URL, um die Wahrscheinlichkeit von Angriffen mittels Dateieinschleusung zu verringern.

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

Erstellen und Aufrufen einer einfachen Staging-Java-UDF

Mit den folgenden Anweisungen wird eine einfache Java-UDF erstellt. Dieses Beispiel folgt im Allgemeinen der unter Organisieren Ihrer Dateien beschriebenen Datei- und Verzeichnisstruktur.

Java-Handler-Code erstellen und kompilieren

  1. Erstellen Sie im Stammverzeichnis Ihres Projekts (hier my_udf) ein Unterverzeichnis src, das die .java-Quelldateien enthält, sowie ein Unterverzeichnis classes, das die generierten .class-Dateien enthält.

    Die Verzeichnishierarchie sollte dann in etwa wie folgt aussehen:

    my_udf/
    |-- classes/
    |-- src/
    
    Copy
  2. Erstellen Sie im Verzeichnis src ein Unterverzeichnis mit dem Namen mypackage, um .java-Dateien zu speichern, deren Klassen sich im mypackage-Paket befinden.

  3. Erstellen Sie im Verzeichnis mypackage eine Datei MyUDFHandler.java, die Ihren Quellcode enthält.

    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. Verwenden Sie in Ihrem Projektstammverzeichnis (hier my_udf) den Befehl javac, um den Quellcode zu kompilieren.

    Mit dem Befehl javac wird im folgenden Beispiel MyUDFHandler.java kompiliert, um eine Datei MyUDFHandler.class im Verzeichnis classes zu generieren.

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

    Dieses Beispiel enthält die folgenden Argumente:

    • -d classes – Verzeichnis, in das die generierten Klassendateien geschrieben werden sollen.

    • src/mypackage/MyUDFHandler.java – Pfad zur .java-Datei in der Form source_directory/package_directory/Java_file_name.

Kompilierten Code in JAR-Datei packen

  1. Optional können Sie im Stammverzeichnis des Projekts eine Manifest-Datei mit dem Namen my_udf.manifest erstellen, die die folgenden Attribute enthält:

    Manifest-Version: 1.0
    Main-Class: mypackage.MyUDFHandler
    
    Copy
  2. Führen Sie im Stammverzeichnis Ihres Projekts den Befehl jar aus, um eine JAR-Datei zu erstellen, die die .class-Datei und die Manifest-Datei enthält.

    Mit dem jar-Befehl im folgenden Beispiel wird die generierte Datei MyUDFHandler.class im Paketordner mypackage in einer .jar-Datei namens my_udf.jar abgelegt. Das Flag -C ./classes gibt den Speicherort der .class-Dateien an.

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

    Dieses Beispiel enthält die folgenden Argumente:

    • cmf – Befehlsargumente: c zum Erstellen einer JAR-Datei, m zum Verwenden der angegebenen Manifest-Datei und f zum Benennung der JAR-Datei mit dem angegebenen Namen.

    • my_udf.manifest – Manifest-Datei.

    • my_udf.jar – Name der zu erstellenden JAR-Datei.

    • -C ./classes – Verzeichnis, das die generierten .class-Dateien enthält.

    • mypackage/MyUDFHandler.class – Paket und Name der .class-Datei, die in die JAR-Datei eingebunden werden soll.

JAR-Datei mit kompiliertem Handler in Stagingbereich hochladen

  1. Erstellen Sie in Snowflake einen Stagingbereich mit dem Namen jar_stage, in dem die JAR-Datei mit Ihrem UDF-Handler gespeichert werden soll.

    Weitere Informationen zum Erstellen eines Stagingbereichs finden Sie unter CREATE STAGE.

  2. Verwenden Sie den PUT-Befehl, um die JAR-Datei aus dem lokalen Dateisystem in einen Stagingbereich zu kopieren.

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

Sie können den PUT-Befehl in einer Skriptdatei speichern und diese Datei dann über SnowSQL ausführen.

Der snowsql-Befehl sieht ungefähr wie folgt aus:

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

In diesem Beispiel wird davon ausgegangen, dass das Kennwort des Benutzers in der Umgebungsvariablen SNOWSQL_PWD angegeben ist.

UDF mit kompiliertem Code als Handler erstellen

Erstellen der UDF:

create function decrement_value(i numeric(9, 0))
  returns numeric
  language java
  imports = ('@jar_stage/my_udf.jar')
  handler = 'mypackage.MyUDFHandler.decrementValue'
  ;
Copy

Aufrufen der UDF:

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