Java UDFs の作成

このトピックでは、Java UDF (ユーザー定義関数)を作成してインストールする方法を示します。

このトピックの内容:

Javaコードの記述

Javaクラスとメソッドの記述

以下の仕様に従うクラスを作成します。

  • クラスを公開として定義します。

  • クラス内で公開メソッドを定義します。

    メソッドが静的メソッドでない場合、メソッドを含むクラスには引数ゼロのコンストラクターが含まれているか、コンストラクターが定義されていない必要があります。(Snowflakeが非静的メソッドのクラスをインスタンス化する場合、Snowflakeは引数を渡しません。)

    コンストラクターがエラーをスローすると、エラーは例外メッセージとともにユーザーエラーとしてスローされます。

  • メソッドが引数を受け入れる場合、各引数は SQL-Java型マッピングテーブルJava Data Type 列で指定されたデータ型の1つである必要があります。

    Java変数のデータ型を選択するときは、Snowflakeから送信(およびSnowflakeに返信)される可能性のあるデータの可能な最大値と最小値を考慮に入れてください。

    メソッド引数は、名前ではなく位置によってバインドされます。UDF に渡される最初の引数は、Javaメソッドが最初に受け取る引数です。たとえば、以下のコード例では、 SQL UDF 引数 a はJavaメソッド引数 x に対応し、 by に対応します。

    create function my_udf(x numeric(9, 0), y float) ...
    
    public static int my_udf(int a, float b) ...
    
  • 適切な返品タイプを指定します。Java UDF はスカラー関数である必要があるため、呼び出されるたびに1つの値を返す必要があります。戻り型は、 SQL-Java型マッピングテーブルJava Data Type 列で指定されたデータタイプの1つである必要があります。戻り型は、 CREATE FUNCTION ステートメントの RETURNS 句で指定された SQL データ型と互換性がある必要があります。

  • クラスには複数のメソッドを含めることができます。Snowflakeによって呼び出されるメソッドは、同じクラスまたは他のクラスの他のメソッドを呼び出すことができます。

  • クラスには、直接呼び出すことができるメソッドを複数含めることもできます。たとえば、クラスにメソッド call_me_1()call_me_2() を含めることができ、それらの各メソッドは他のメソッドを呼び出すことができます。

    クラスに直接呼び出し可能なメソッドが複数含まれている場合は、Java UDF を プリコンパイル済み UDF として作成します。1つの JAR ファイルを作成し、2つ(またはそれ以上)の CREATE FUNCTION ステートメントを実行します。各ステートメントは、 HANDLER 句で異なる関数を指定します。

  • メソッド(およびメソッドによって呼び出されるメソッド)は、 Java UDFs に対するSnowflakeによる制約 に準拠する必要があります。

Snowflakeでの関数の作成

このセクションの情報は、コードが インライン または プリコンパイル済み のどちらで指定されているかに関係なく、すべてのJava UDFs に適用されます。

次を指定するには、 CREATE FUNCTION ステートメントを実行する必要があります。

  • 使用する SQL 関数名。

  • Java UDF が呼び出されたときに呼び出すJavaメソッドの名前。

UDF の名前は、Javaで記述されたハンドラーメソッドの名前と一致する必要はありません。CREATE FUNCTION ステートメントは、 UDF 名をJavaメソッドに関連付けます。次の図はこれを示しています。

Using the CREATE FUNCTION Statement to Associate the Handler Method With the UDF Name

UDF の名前を選択する場合は、

  • オブジェクト識別子 のルールに従います。

  • 一意の名前を選択するか、 UDF名のオーバーロード のルールに従います。

    重要

    引数の数とデータ型の両方に基づいて関数を区別する SQL UDFs のオーバーロードとは異なり、Java UDFs は、引数の数 のみ に基づいてメソッドを区別します。2つのJavaメソッドの名前と引数の数が同じで、データ型が異なる場合、これらのメソッドの1つをハンドラーとして使用して UDF を呼び出すと、次のようなエラーが生成されます。

    ハンドラー <クラス名>.<ハンドラー名> を使用する関数 <ユーザー定義関数名> に <引数の数> 引数の定義が複数あるため、呼び出すハンドラー「ハンドラー名」の実装を決定できません。

    ウェアハウスが使用可能な場合は、 UDF の作成時にエラーが検出されます。それ以外の場合は、 UDF が呼び出されたときにエラーが発生します。

    一部の SQL データ型は複数のJavaデータ型にマップでき、したがって複数のJava UDF 署名にマップできる可能性があるため、データ型に基づいて解決することは実用的ではありません。

メソッド引数は、名前ではなく位置によってバインドされます。UDF に渡される最初の引数は、Javaメソッドが最初に受け取る引数です。たとえば、以下のコード例では、 SQL UDF 引数 a はJavaメソッド引数 x に対応し、 by に対応します。

create function my_udf(x numeric(9, 0), y float) ...
public static int my_udf(int a, float b) ...

引数のデータ型については、 パラメーターと戻り値型の SQL-Javaデータ型のマッピング をご参照ください。

インライン UDFs とプリコンパイル済み UDFs

Java UDF のコードは、次のいずれかの方法で指定できます。

  • プリコンパイル済み: CREATE FUNCTION ステートメントは、既存の JAR ファイルの場所を指定します。ユーザーは、Javaソースコードをコンパイルし、 JAR ファイルを ステージ に配置する必要があります。

  • インライン: CREATE FUNCTION ステートメントは、Javaソースコードを指定します。Snowflakeはソースコードをコンパイルし、コンパイルされたコードを JAR ファイルに保存します。ユーザーには、 JAR ファイルの場所を指定するオプションがあります。

    • ユーザーが JAR ファイルの場所を指定すると、Snowflakeはコードを1回コンパイルし、将来の使用のために JAR ファイルを保持します。

    • ユーザーが JAR ファイルの場所を指定しない場合、Snowflakeは UDF を呼び出す各 SQL ステートメントのコードを再コンパイルし、Snowflakeは SQL ステートメントの終了後に JAR ファイルを自動的にクリーンアップします。

いくつかの実際的な違いは、作成するタイプに影響を与える可能性があります。

  • インラインJava UDFs には次の利点があります。

    • 通常、実装ははるかに簡単です。コードをコンパイルし、 JAR ファイルをSnowflakeステージにコピーする必要はありません。(ただし、ほとんどのプログラマーは、実稼働環境に移行する前にコードをコンパイルしてテストするため、 UDF がインラインであっても、ほとんどのJava UDF コードは開発者によってコンパイルされます。)

  • プリコンパイル済みJava UDFs には、次の利点があります。

    • JAR ファイルはあっても、ソースコードがない場合に使用できます。

    • ソースコードが大きすぎて CREATE FUNCTION ステートメントに貼り付けられない場合に使用できます。(インラインJava UDFs には、ソースコードサイズの上限があります。)

    • プリコンパイル済みJava UDF には、複数の呼び出し可能な関数を含めることができます。複数の CREATE FUNCTION ステートメントは同じ JAR ファイルを参照できますが、 JAR ファイル内で異なるハンドラー関数を指定します。

      インラインJava UDFs には通常、呼び出し可能な関数が1つだけ含まれています。(その呼び出し可能関数は他の関数を呼び出すことができ、それらの他の関数は同じクラスで定義することも、ライブラリ JAR ファイルで定義された他のクラスで定義することもできます。)

    • JAR ファイルをテストまたはデバッグするためのツールまたは環境がある場合は、 JAR ファイルを使用して UDF でほとんどの開発作業をする方が便利な場合があります。これは、コードが大きいか複雑な場合に特に当てはまります。

インラインJava UDF の作成

インライン UDF の場合、 CREATE FUNCTION ステートメントの一部としてJavaソースコードを指定します。例:

create function add(x integer, y integer)
returns integer
language java
handler='TestAddFunc.add'
target_path='@~/TestAddFunc.jar'
as
$$
    class TestAddFunc {
        public static int add(int x, int y) {
          return x + y;
        }
    }
$$;

Javaソースコードは AS 句で指定されています。ソースコードは、一重引用符またはドル記号($$)のペアで囲むことができます。ソースコードに一重引用符が埋め込まれている場合は、通常、二重ドル記号を使用する方が簡単です。

Javaソースコードには複数のクラスとクラス内の複数のメソッドを含めることができるため、 HANDLER 句は呼び出すクラスとメソッドを指定します。

インラインJava UDF は(プリコンパイル済みJava UDF と同じく)、 IMPORTS 句に含まれる JAR ファイルのコードを呼び出すことができます。

CREATE FUNCTION ステートメントの構文の詳細については、 CREATE FUNCTION をご参照ください。

その他の例については、 インラインJava UDF の例 をご参照ください。

プリコンパイル済みJava UDF の作成

ファイルの整理

Java UDF は JAR ファイルに保存されます。Javaコードをコンパイルして JAR ファイルを自分で作成する場合は、以下に示すようにファイルを整理できます。この例では、Javaのパッケージメカニズムの使用が予定されていることを前提としています。

  • developmentDirectory

    • packageDirectory

      • class_file1.java

      • class_file2.java

    • classDirectory

      • class_file1.class

      • class_file2.class

    • manifest_file.manifest(オプション)

    • jar_file.jar

    • put_command.sql

developmentDirectory

このディレクトリには、Java UDF の作成に必要なプロジェクト固有のファイルが含まれています。

packageDirectory

このディレクトリには、コンパイルしてパッケージに含める.javaファイルが含まれています。

class_file#.java

これらのファイルには、 UDF のJavaソースコードが含まれています。

class_file#.class

これらは、.javaファイルをコンパイルすることによって作成された.classファイルです。

manifest_file.manifest

.classファイル(およびオプションで依存関係 JAR ファイル)を JAR ファイルに結合するときに使用される、オプションのマニフェストファイル。

jar_file.jar

UDF コードを含んだ JAR ファイル。

put_command.sql

このファイルには、JAR ファイルをSnowflake ステージ にコピーするための SQL PUT コマンドが含まれています。

Javaコードのコンパイルと JAR ファイルの作成

コンパイルされたJavaコードを含んだ JAR ファイルを作成するには、

  • javacを使用して、.javaファイルを.classファイルにコンパイルします。

    バージョン11.xより新しいコンパイラを使用する場合は、「-release」オプションを使用して、ターゲットバージョンがバージョン11であることを指定できます。

  • .classファイルを JAR ファイルに配置します。複数のクラスファイル(およびその他の JAR ファイル)を JAR ファイルにパッケージ化できます。

    例:

    jar cf ./my_decrement_udf_jar.jar my_decrement_udf_package/my_decrement_udf_class.class
    

    マニフェストファイルは、パッケージを使用する場合には必須であり、パッケージを使用しない場合はオプションです。次の例では、マニフェストファイルを使用しています。

    jar cmf my_decrement_udf_manifest.manifest ./my_decrement_udf_jar.jar my_decrement_udf_package/my_decrement_udf_class.class
    

    すべての依存関係が含まれているjarファイルをビルドするには、maven-assembly-pluginでMavenの mvn package コマンドを使用できます。maven-assembly-pluginの詳細については、次をご参照ください。

    Snowflakeは、標準のJavaライブラリ(例: java.util)を自動的に提供します。コードでこれらのライブラリを呼び出す場合は、 JAR ファイルに含める必要はありません。

    ライブラリで呼び出すメソッドは、Javaメソッドと同じ Snowflakeによる制約 に従う必要があります。

JAR ファイルのステージへのコピー

Snowflakeは、外部または名前付きの内部 ステージ から JAR ファイルを読み取るため、 JAR ファイルをステージにコピーする必要があります。Snowflakeユーザーのデフォルトのステージ、または別の既存のステージを使用することも、新しいステージを作成することもできます。

JAR ファイルをホストするステージは、 UDF の 所有者 によって読み取り可能である必要があります。

通常は、名前付きの内部ステージを使用し、 PUT コマンドを使用してファイルをステージにアップロードします。(PUT コマンドは、Snowflake GUI を介して実行できないことに注意してください。SnowSQL を使用すると PUT を実行できます。)

JAR ファイルを削除または名前変更すると、 UDF を呼び出すことができなくなります。

Snowflakeは、次のベストプラクティスに従うことをお勧めします。

  • JAR ファイルを更新する必要がある場合は、次を実行します。

    • UDF の呼び出しができない間に更新します。

    • 古い.jarファイルがまだステージにある場合、 PUT コマンドには OVERWRITE=TRUE 句を含める必要があります。

.jarファイルをステージにコピーするための PUT コマンドの例については、 セクションをご参照ください。

関数に対する権限の付与

関数の所有者以外のロールが関数を呼び出すには、所有者がそのロールに適切な権限を付与する必要があります。

Java UDF の GRANT ステートメントは、 JavaScript UDFs などといった、他の UDFs の GRANT ステートメントと基本的に同じです。

例:

GRANT USAGE ON FUNCTION my_java_udf(number, number) TO my_role;

インラインJava UDFs

単純なインラインJava UDF の作成と呼び出し

次のステートメントは、インラインJava UDF を作成して呼び出します。このコードは、渡された VARCHAR を返します。

この関数は、オプションの CALLED ON NULL INPUT 句を使用して宣言され、入力の値が NULL であっても関数が呼び出されることを示します。(この関数は、この句の有無にかかわらず NULL を返しますが、たとえば空の文字列を返すように、別の方法で NULL を処理するようにコードを変更できます。)

UDF を作成します。

create or replace function echo_varchar(x varchar)
returns varchar
language java
called on null input
handler='TestFunc.echo_varchar'
target_path='@~/testfunc.jar'
as
'class TestFunc {
  public static String echo_varchar(String x) {
    return x;
  }
}';

UDF を呼び出します。

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

NULL のインラインJava UDF への引き渡し

これは、上記で定義された echo_varchar() UDF を使用します。SQL NULL 値は暗黙的にJava null に変換され、そのJava null が返され、暗黙的に SQL NULL に変換されます。

UDF を呼び出します。

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

NULL の明示的な返し

次のコードは、 NULL 値を明示的に返す方法を示しています。Java値 null は SQL NULL に変換されます。

UDF を作成します。

create or replace function return_a_null()
returns varchar
null
language java
handler='TemporaryTestLibrary.return_a_null'
target_path='@~/TemporaryTestLibrary.jar'
as
$$
class TemporaryTestLibrary {
  public static String return_a_null() {
    return null;
  }
}
$$;

UDF を呼び出します。

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

OBJECT のインラインJava UDF への引き渡し

次の例では、 SQL OBJECT データ型と対応するJavaデータ型(Map<文字列,文字列>)を使用し、 OBJECT から値を抽出します。この例は、Java UDF に複数のパラメーターを渡すことができることも示しています。

タイプ OBJECT の列を含むテーブルを作成してロードします。

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

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

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

ARRAY のインラインJava UDF への引き渡し

次の例では、 SQL ARRAY データ型を使用しています。

UDF を作成します。

create or replace function multiple_functions_in_jar(array1 array)
returns varchar
language java
handler='TemporaryTestLibrary.multiple_functions_in_jar'
target_path='@~/TemporaryTestLibrary.jar'
as
$$
class TemporaryTestLibrary {
  public static String multiple_functions_in_jar(String[] array_of_strings) {
    return concatenate(array_of_strings);
  }
  public static String concatenate(String[] array_of_strings) {
    int number_of_strings = array_of_strings.length;
    String concatenated = "";
    for (int i = 0; i < number_of_strings; i++)  {
        concatenated = concatenated + " " + array_of_strings[i];
        }
    return concatenated;
  }
}
$$;

UDF を呼び出します。

SELECT multiple_functions_in_jar(ARRAY_CONSTRUCT('Hello', 'world'));
+--------------------------------------------------------------+
| MULTIPLE_FUNCTIONS_IN_JAR(ARRAY_CONSTRUCT('HELLO', 'WORLD')) |
|--------------------------------------------------------------|
|  Hello world                                                 |
+--------------------------------------------------------------+

プリコンパイル済みJava UDFs

単純なプリコンパイル済みJava UDF の作成と呼び出し

次のステートメントは、単純なJava UDF を作成します。このサンプルは通常、 ファイルの整理 で説明されているファイルとディレクトリの構造に従います。

ソースコードを含むJavaファイルを作成します。

package my_decrement_udf_package;


public class my_decrement_udf_class
{

public static int my_decrement_udf_method(int i)
{
    return i - 1;
}


public static void main(String[] argv)
{
    System.out.println("This main() function won't be called.");
}


}

必要に応じて、以下に示すようなマニフェストファイルを作成します。

Manifest-Version: 1.0
Main-Class: my_decrement_udf_class.class

ソースコードをコンパイルします。この例では、生成された.classファイルを classDirectory という名前のディレクトリに保存します。

javac -d classDirectory my_decrement_udf_package/my_decrement_udf_class.java

.classファイルから JAR ファイルを作成します。この例では、「-CclassDirectory」を使用して.classファイルの場所を指定します。

jar cmf my_decrement_udf_manifest.manifest ./my_decrement_udf_jar.jar -C classDirectory my_decrement_udf_package/my_decrement_udf_class.class

PUT コマンドを使用して、 JAR ファイルをローカルファイルシステムからステージにコピーします。この例では、 @~ という名前のユーザーのデフォルトステージを使用します。

put
    file:///Users/Me/JavaUDFExperiments/my_decrement_udf_jar.jar
    @~/my_decrement_udf_package_dir/
    auto_compress = false
    overwrite = true
    ;

PUT コマンドをスクリプトファイルに保存してから、snowsqlを介してそのファイルを実行できます。snowsqlコマンドは次に似たものになります。

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

この例では、ユーザーのパスワードが SNOWSQL_PWD 環境変数で指定されていることを前提としています。

UDF を作成します。

create function my_decrement_udf(i numeric(9, 0))
    returns numeric
    language java
    imports = ('@~/my_decrement_udf_package_dir/my_decrement_udf_jar.jar')
    handler = 'my_decrement_udf_package.my_decrement_udf_class.my_decrement_udf_method'
    ;

UDF を呼び出します。

SELECT my_decrement_udf(-15);
+-----------------------+
| MY_DECREMENT_UDF(-15) |
|-----------------------|
|                   -16 |
+-----------------------+