Entwerfen von Java-UDFs¶
Dieses Thema bietet Unterstützung beim Entwerfen von Java-UDFs.
Unter diesem Thema:
Auswählen Ihrer Datentypen¶
Bevor Sie Ihren Code schreiben:
Wählen Sie die Datentypen aus, die Ihre Funktion als Argumente akzeptieren soll, und den Datentyp, den Ihre Funktion zurückgeben soll.
Berücksichtigen Sie die Zeitzonenproblematik.
Entscheiden Sie, wie NULL-Werte behandelt werden sollen.
Zuordnung von SQL-Java-Datentypen für Parameter und Rückgabetypen¶
Informationen dazu, wie Snowflake zwischen Java- und SQL-Datentypen konvertiert, finden Sie unter Zuordnung von Datentypen zwischen SQL und Handler-Sprachen.
TIMESTAMP_LTZ-Werte und Zeitzonen¶
Ein Java-UDF ist weitgehend isoliert von der Umgebung, in der sie aufgerufen wird. Die Zeitzone wird jedoch von der aufrufenden Umgebung geerbt. Wenn die Sitzung des Aufrufers vor dem Aufruf der Java-UDF eine Standardzeitzone eingestellt hat, dann hat die Java-UDF die gleiche Standardzeitzone. Die Java-UDF verwendet dieselben Daten der IANA-Zeitzonendatenbank, die auch von der nativen TIMEZONE-Funktion in Snowflake SQL verwendet wird (d. h. Daten aus Release 2021a der Zeitzonendatenbank).
NULL-Werte¶
Snowflake unterstützt zwei verschiedene NULL-Werte: SQL NULL
und VARIANT JSON null
. (Weitere Informationen zu Snowflake VARIANT NULL finden Sie unter NULL-Werte.)
Java unterstützt einen null
-Wert, der nur für nicht primitive Datentypen gilt.
Ein SQL-NULL
-Argument für eine Java-UDF wird in den Java null
-Wert übersetzt, aber nur für Java-Datentypen, die null
unterstützen.
Ein zurückgegebener Java null
-Wert wird in SQL NULL
zurückübersetzt.
Arrays und variable Anzahl von Argumenten¶
Java-UDFs können Arrays von jedem der folgenden Java-Datentypen erhalten:
String
boolean
double
float
int
long
short
Der Datentyp von übergebenen SQL-Werten muss mit dem entsprechenden Java-Datentyp kompatibel sein. Weitere Informationen zur Datentypkompatibilität finden Sie unter Zuordnung von Datentypen zwischen SQL und Java.
Die folgenden zusätzlichen Regeln gelten für jeden der angegebenen Java-Datentypen:
boolean: Das Snowflake-ARRAY darf nur BOOLEAN-Elemente und keine NULL-Werte enthalten.
int/short/long: Das Snowflake-ARRAY darf nur Festkomma-Elemente mit einer Dezimalstellenzahl von 0 enthalten und auch keine NULL-Werte.
float/double: Das Snowflake-ARRAY muss enthalten:
Das ARRAY darf keine NULL-Werte enthalten.
Java-Methoden können diese 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.
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)
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');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); } } $$;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 | +---------------------+
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)
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');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); } } $$;Aufrufen der UDF:
SELECT concat_varchar(a) FROM string_array_table ORDER BY id; +-------------------+ | CONCAT_VARCHAR(A) | |-------------------| | Hello | | Hello Jay | | Hello Jay Smith | +-------------------+
Entwerfen von Java-UDFs unter Berücksichtigung der Snowflake-bedingten Einschränkungen¶
Informationen zum Entwerfen von Handler-Code, der unter Snowflake effizient ausgeführt wird, finden Sie unter Entwerfen von Handlern unter Berücksichtigung der Snowflake-bedingten Einschränkungen.
Entwerfen der Klasse¶
Wenn eine SQL-Anweisung Ihre Java-UDF aufruft, ruft Snowflake eine Java-Methode auf, die Sie geschrieben haben. Ihre Java-Methode wird als „Handler-Methode“ oder kurz „Handler“ bezeichnet.
Wie bei jeder Java-Methode muss Ihre Methode als Teil einer Klasse deklariert werden. Ihre Handler-Methode kann eine statische Methode oder eine Instanzenmethode der Klasse sein. Wenn Ihr Handler eine Instanzenmethode ist und Ihre Klasse einen Konstruktor ohne Argumente definiert, ruft Snowflake den Konstruktor zur Initialisierungszeit auf, um eine Instanz Ihrer Klasse zu erstellen. Wenn Ihr Handler eine statische Methode ist, muss Ihre Klasse keinen Konstruktor haben.
Der Handler wird für jede an die Java-UDF übergebene Zeile einmal aufgerufen. (Hinweis: Es wird nicht für jede Zeile eine neue Instanz der Klasse erstellt. Snowflake kann die Handler-Methode derselben Instanz mehrmals aufrufen oder dieselbe statische Methode mehrmals aufrufen).
Um die Ausführung Ihres Codes zu optimieren, geht Snowflake davon aus, dass die Initialisierung langsam sein kann, während die Ausführung der Handler-Methode schnell ist. Snowflake verwendet ein längeres Timeout für die Ausführung der Initialisierung (einschließlich Zeit für Laden der UDF und Aufruf des Konstruktors der Klasse, die die Handler-Methode enthält, falls ein Konstruktor definiert ist) als für die Ausführung des Handlers (Zeit für Aufruf Ihres Handlers mit einer Zeile der Eingabe).
Weitere Informationen zum Entwerfen der Klasse finden Sie unter Erstellen eines Java-UDF-Handlers.
Optimieren von Initialisierung und Steuerung des globalen Zustands in skalaren UDFs¶
Die meisten Funktions- und Prozedurhandler sollten die folgenden Richtlinien berücksichtigen:
Wenn Sie einen gemeinsamen Zustand (Shared State) initialisieren müssen, der sich über Zeilen hinweg nicht ändert, initialisieren Sie ihn nicht in der Handler-Funktion sondern im Modul oder Konstruktor.
Schreiben Sie Ihre Handler-Funktion so, dass sie threadsicher ist.
Vermeiden Sie das Speichern und Freigeben eines dynamischen Zustands über Zeilen hinweg.
Wenn Ihre UDF diesen Richtlinien nicht folgen kann oder wenn Sie ein tieferes Verständnis der Gründe für diese Richtlinien wünschen, lesen Sie die nächsten Unterabschnitte.
Erläuterungen zur Java-UDF-Parallelisierung¶
Um die Verarbeitungsleistung zu verbessern, nutzt Snowflake die Parallelisierung sowohl zwischen mehrere JVMs als auch innerhalb von JVMs.
Mehrere JVMs:
Snowflake parallelisiert mehrere Ausführungen in einem Warehouse. Jede Ausführung nutzt eine (oder mehrere) JVMs. Dies bedeutet, dass es keinen globalen gemeinsamen Zustand gibt. Der Zustand kann höchstens innerhalb einer einzelnen JVM gemeinsam genutzt werden.
Innerhalb von JVMs:
Jede JVM kann mehrere Threads ausführen, die parallel die Handler-Methode der gleichen Instanz aufrufen können. Das bedeutet, dass jede Handler-Methode thread-sicher sein muss.
Wenn eine UDF mit IMMUTABLE eingerichtet ist und eine SQL-Anweisung diese UDF mehr als einmal mit denselben Argumenten für dieselbe Zeile aufruft, dann gibt die UDF bei jedem Aufruf für diese Zeile den gleichen Wert zurück. Im Folgenden wird z. B. zweimal derselbe Wert für jede Zeile zurückgegeben, wenn die UDF mit IMMUTABLE eingerichtet ist:
select my_java_udf(42), my_java_udf(42) from table1;
Wenn Sie möchten, dass mehrere Aufrufe unabhängige Werte zurückgeben, auch wenn die gleichen Argumente übergeben werden, und wenn Sie nicht die Funktion VOLATILE deklarieren möchten, dann binden Sie mehrere separate UDFs an die gleiche Handler-Methode. Beispiel:
Erstellen Sie eine JAR-Datei mit dem Namen
@java_udf_stage/rand.jar
mit Code:class MyClass { private double x; // Constructor public MyClass() { x = Math.random(); } // Handler public double myHandler() { return x; } }
Erstellen Sie die Java-UDFs wie unten gezeigt. Diese UDFs haben unterschiedliche Namen, verwenden aber die gleiche JAR-Datei und innerhalb dieser JAR-Datei den gleichen Handler.
create function my_java_udf_1() returns double language java imports = ('@java_udf_stage/rand.jar') handler = 'MyClass.myHandler'; create function my_java_udf_2() returns double language java imports = ('@java_udf_stage/rand.jar') handler = 'MyClass.myHandler';
Der folgende Code ruft beide UDFs auf. Die UDFs verweisen auf die gleiche JAR-Datei und den gleichen Handler. Diese Aufrufe erstellen zwei Instanzen der gleichen Klasse. Jede Instanz gibt einen unabhängigen Wert zurück, sodass das folgende Beispiel zwei unabhängige Werte zurückgibt, anstatt zweimal denselben Wert zu liefern:
select my_java_udf_1(), my_java_udf_2() from table1;
Speichern von JVM-Statusinformationen¶
Ein Grund, sich nicht auf einen dynamischen gemeinsamen Zustand zu verlassen, besteht darin, dass Zeilen nicht unbedingt in einer vorhersehbaren Reihenfolge verarbeitet werden. Bei jeder Ausführung einer SQL-Anweisung kann Snowflake die Anzahl der Batches, die Reihenfolge, in der die Batches verarbeitet werden, und die Reihenfolge der Zeilen innerhalb eines Batches variieren. Wenn eine skalare UDF so gestaltet ist, dass eine Zeile den Rückgabewert einer nachfolgenden Zeile beeinflusst, dann kann die UDF bei jeder Ausführung unterschiedliche Ergebnisse zurückgeben.
Fehlerbehandlung¶
Eine als UDF verwendete Java-Methode kann die üblichen Java-Techniken zur Ausnahmebehandlung verwenden, um Fehler innerhalb der Methode abzufangen.
Wenn eine Ausnahme innerhalb der Methode auftritt und nicht von der Methode abgefangen wird, gibt Snowflake einen Fehler aus, der den Stacktrace für die Ausnahme enthält. Wenn die Protokollierung von unbehandelten Ausnahmen aktiviert ist, protokolliert Snowflake Daten zu unbehandelten Ausnahmen in einer Ereignistabelle.
Sie können explizit einen Fehler auszulösen, ohne ihn abzufangen, um die Abfrage zu beenden und einen SQL-Fehler zu erzeugen. Beispiel:
if (x < 0) {
throw new IllegalArgumentException("x must be non-negative.");
}
Beim Debugging können Sie in den Text der SQL-Fehlermeldung Werte integrieren. Setzen Sie dazu den gesamten Textkörper der Java-Methode in einen „try-catch“-Block, und fügen Sie Argumentwerte zur abgefangenen Fehlermeldung hinzu. Lösen Sie dann einen Fehler mit der erweiterten Meldung aus. Um die Offenlegung sensibler Daten zu verhindern, entfernen Sie die Argumente, bevor Sie die JAR-Dateien in einer Produktionsumgebung bereitstellen.
Befolgen von Best Practices¶
Schreiben Sie plattformunabhängigen Code.
Vermeiden Sie Code, der eine bestimmte CPU-Architektur voraussetzt (z. B. x86).
Vermeiden Sie Code, der ein bestimmtes Betriebssystem voraussetzt.
Wenn Sie Initialisierungscode ausführen müssen und diesen nicht in die aufgerufene Methode aufnehmen möchten, können Sie den Initialisierungscode in einen statischen Initialisierungsblock einfügen.
Wann immer möglich, geben Sie bei der Verwendung eines Inline-Handlers einen Wert für den TARGET_PATH-Parameter in CREATE FUNCTION oder CREATE PROCEDURE an. Dadurch wird Snowflake veranlasst, zuvor generierten Handler-Code wiederzuverwenden, anstatt ihn für jeden Aufruf neu zu kompilieren. Weitere Informationen dazu finden Sie unter Verwendung eines Inline-Handlers.
Siehe auch:
Einsetzen von bewährten Sicherheitsmethoden¶
Damit Ihr Handler auf sichere Weise funktioniert, beachten Sie die unter Sicherheitsverfahren für UDFs und Prozeduren beschriebenen Best Practices.