Java UDFs の設計¶
このトピックは、Java UDFs を設計するための助けになります。
このトピックの内容:
データ型の選択¶
コードを記述する前に、
関数が引数として受け入れるデータ型と、関数が返すデータ型を選択します。
タイムゾーン関連の問題を考慮に入れます。
NULL 値の処理方法を決定します。
パラメーターと戻り値型の SQL-Javaデータ型のマッピング¶
SnowflakeがJavaと SQL データ型の間で変換する方法については、 SQL とハンドラー言語間のデータ型マッピング をご参照ください。
TIMESTAMP_LTZ 値とタイムゾーン¶
Java UDF は多くの場合、呼び出される環境から分離されています。ただし、タイムゾーンは呼び出し元の環境から継承されます。呼び出し元のセッションがJava UDF を呼び出す前にデフォルトのタイムゾーンを設定した場合、Java UDF のデフォルトのタイムゾーンは同じになります。Java UDF は、ネイティブ TIMEZONE Snowflake SQL が使用するのと同じ IANA タイムゾーンデータベース データを使用します(つまり、タイムゾーンデータベースのリリース 2021a からのデータ)。
NULL 値¶
Snowflakeは、 SQL NULL
と VARIANT の JSON null
という2つの異なる NULL 値をサポートしています。(Snowflake VARIANT NULL については、 NULL 値 を参照。)
Javaは、1つの null
値をサポートします。これは、非プリミティブデータ型専用です。
Java UDF に対する SQL NULL
引数は、 null
をサポートするJavaデータ型の場合にのみ、Java null
値に変換されます。
返されたJava null
値は、 SQL NULL
に変換されます。
引数の配列および可変の数¶
Java UDFs は、次に挙げるJavaデータ型のいずれかの配列を受け取ることができます。
String
boolean
double
float
int
long
short
渡される SQL 値のデータ型には、対応するJavaデータ型との互換性が必要です。データ型の互換性の詳細については、 SQL-Javaデータ型マッピング をご参照ください。
指定されたJavaデータ型のそれぞれに、次の追加ルールが適用されます。
boolean: Snowflake ARRAY には BOOLEAN 要素のみが含まれている必要があり、 NULL 値は含まれていてはなりません。
int/short/long: Snowflake ARRAY には、スケールが0の 固定小数点 要素のみが含まれている必要があり、 NULL 値が含まれていてはなりません。
float/double: Snowflake ARRAY には次のいずれかが含まれている必要があります。
ARRAY には NULL の値を含めることはできません。
Javaメソッドは、次に挙げる2つの方法のいずれかでこれらの配列を受け取ることができます。
Javaの配列機能を使用。
Javaの varargs (引数の数が可変)機能を使用。
どちらの場合も、 SQL コードは ARRAY を渡す必要があります。
ARRAY 経由の引き渡し¶
Javaパラメーターを配列として宣言します。たとえば、次に挙げるメソッドの3番目のパラメーターは文字列配列です。
static int myMethod(int fixedArgument1, int fixedArgument2, String[] stringArray)
以下は包括的な例です。
テーブルを作成してロードします。
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');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); } } $$;UDF を呼び出します。
SELECT concat_varchar_2(a) FROM string_array_table ORDER BY id; +---------------------+ | CONCAT_VARCHAR_2(A) | |---------------------| | Hello | | Hello Jay | | Hello Jay Smith | +---------------------+
Varargs経由の引き渡し¶
Varargsの使用は、配列の使用と非常によく似ています。
Javaコードでは、Javaのvarargs宣言スタイルを使用します。
static int myMethod(int fixedArgument1, int fixedArgument2, String ... stringArray)
以下は包括的な例です。この例と前の例(配列の場合)の間における唯一の重要な違いは、メソッドへのパラメーター宣言です。
テーブルを作成してロードします。
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');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); } } $$;UDF を呼び出します。
SELECT concat_varchar(a) FROM string_array_table ORDER BY id; +-------------------+ | CONCAT_VARCHAR(A) | |-------------------| | Hello | | Hello Jay | | Hello Jay Smith | +-------------------+
Snowflakeによる制約の範囲内にとどまるJava UDFs の設計¶
Snowflakeで適切に動作するハンドラーコードの設計については、 Snowflakeが課す制約内でのハンドラーの設計 をご参照ください。
クラスの設計¶
SQL ステートメントがJava UDF を呼び出すと、Snowflakeは作成したJavaメソッドを呼び出します。Javaメソッドは、「ハンドラーメソッド」または略して「ハンドラー」と呼ばれます。
他のJavaメソッドと同様に、メソッドはクラスの一部として宣言する必要があります。ハンドラーメソッドは、クラスの静的メソッドまたはインスタンスメソッドにすることができます。ハンドラーがインスタンスメソッドであり、クラスが引数なしのコンストラクターを定義している場合、Snowflakeは初期化時にコンストラクターを呼び出して、クラスのインスタンスを作成します。ハンドラーが静的メソッドの場合、クラスにコンストラクターは必要ありません。
ハンドラーは、Java UDF に渡される行ごとに1回呼び出されます。(注: クラスの新しいインスタンスは行ごとに作成されません。Snowflakeは、同じインスタンスのハンドラーメソッドを複数回呼び出すことも、同じ静的メソッドを複数回呼び出すこともできます。)
コードの実行を最適化するために、Snowflakeは、ハンドラーメソッドの実行が速い一方で、初期化が遅い可能性があると想定しています。Snowflakeは、初期化を実行するためのタイムアウト(UDF をロードする時間と、コンストラクターが定義されている場合は、ハンドラーメソッドを含むクラスのコンストラクターを呼び出すための時間を含む)を、ハンドラーを実行するためのタイムアウト(入力1行でハンドラーを呼び出す時間)よりも長く設定します。
クラスの設計に関する追加情報は Java UDF ハンドラーの作成 にあります。
スカラー UDFs における初期化の最適化とグローバル状態の制御¶
ほとんどの関数およびプロシージャハンドラーは、次のガイドラインに従う必要があります。
行全般で変化しない共有状態を初期化する必要がある場合は、ハンドラー関数の外部(モジュールまたはコンストラクターなど)で初期化します。
スレッドセーフになるようにハンドラー関数またはメソッドを記述します。
行間で動的状態を保存および共有することは避けてください。
UDF がこれらのガイドラインに従えない場合、またはこれらのガイドラインの理由をより深く理解したい場合は、次のいくつかのサブセクションをお読みください。
Java UDF 並列化について¶
パフォーマンスを向上させるために、Snowflakeは JVMs の全体および内部で並列化します。
JVMs 全体:
Snowflakeは、 ウェアハウス のワーカー間で並列化されます。各ワーカーは1つ(または複数)の JVMs を実行します。これは、グローバル共有状態がないことを意味します。最大で、状態は単一の JVM 内でのみ共有できます。
JVMs 内部:
各 JVM は、同じインスタンスのハンドラーメソッドを並行して呼び出すことができる複数のスレッドを実行できます。これは、各ハンドラーメソッドがスレッドセーフである必要があることを意味します。
UDF が IMMUTABLE で、 SQL ステートメントが同じ行に対して同じ引数を使用して UDF を複数回呼び出す場合、 UDF は、その行の呼び出しごとに同じ値を返します。たとえば、 UDF が IMMUTABLE の場合、以下は各行に対して同じ値を2回返します。
select my_java_udf(42), my_java_udf(42) from table1;
同じ引数が渡された場合でも複数の呼び出しで独立した値を返すようにし、関数 VOLATILE を宣言しない場合は、複数の個別の UDFs を同じハンドラーメソッドにバインドします。例:
次のコードを使用して、
@java_udf_stage/rand.jar
という名前の JAR ファイルを作成します。class MyClass { private double x; // Constructor public MyClass() { x = Math.random(); } // Handler public double myHandler() { return x; } }
以下に示すように、Java UDFs を作成します。これらの UDFs の名前は異なりますが、同じ JAR ファイルと、その JAR ファイル内の同じハンドラーを使用します。
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';
次のコードは、両方の UDFs を呼び出します。UDFs は、同じ JAR ファイルとハンドラーをポイントします。これらの呼び出しは、同じクラスのインスタンスを2つ作成します。各インスタンスは独立した値を返すため、以下の例では、同じ値を2回返すのではなく、2つの独立した値を返します。
select my_java_udf_1(), my_java_udf_2() from table1;
JVM 状態情報の保存¶
動的共有状態に依存しない理由の1つは、行が必ずしも予測可能な順序で処理されるとは限らないことです。Snowflakeは、 SQL ステートメントが実行されるたびに、バッチの数、バッチが処理される順序、およびバッチ内の行の順序を変更できます。1つの行が後続の行の戻り値に影響を与えるようにスカラー UDF が設計されている場合、 UDF は、 UDF が実行されるたびに異なる結果を返す可能性があります。
エラーの処理¶
UDF として使用されるJavaメソッドは、通常のJava例外処理手法を使用して、メソッド内のエラーをキャッチできます。
メソッド内で例外が発生し、メソッドによってキャッチされない場合、Snowflakeは例外のスタックトレースを含むエラーを発生させます。 未処理の例外のログ を有効にすると、Snowflakeは未処理の例外に関するデータをイベントテーブルに記録します。
クエリを終了して SQL エラーを生成するために、例外をキャッチせずにスローできます。例:
if (x < 0) {
throw new IllegalArgumentException("x must be non-negative.");
}
デバッグ時に、 SQL エラーメッセージテキストに値を含めることができます。そうするには、Javaメソッドの本文全体をtry-catchブロックに配置し、キャッチしたエラーのメッセージに引数値を追加し、拡張メッセージで例外をスローします。機密データが公開されないようにするには、 JAR ファイルを実稼働環境に展開する前に引数値を削除します。
ベストプラクティスに準拠¶
プラットフォームに依存しないコードを記述します。
特定の CPU アーキテクチャ(例: x86)を想定したコードは避けます。
特定のオペレーティングシステムを想定したコードは避けます。
初期化コードを実行する必要があり、呼び出すメソッドに含めたくない場合は、初期化コードを静的初期化ブロックに入れることができます。
インラインハンドラーを使用する場合は、可能な限り CREATE FUNCTION または CREATE PROCEDURE TARGET_PATH パラメーターに値を指定してください。これにより、呼び出しごとに再コンパイルするのではなく、以前に生成されたハンドラーコード出力を再利用するようSnowflakeに促すことができます。詳細については、 インラインハンドラーの使用 をご参照ください。
こちらもご参照ください。
優れたセキュリティプラクティスに準拠¶
ハンドラーがセキュアに機能するようにするには、 UDFs とプロシージャのセキュリティプラクティス で説明されているベストプラクティスをご参照ください。