Scala UDF ハンドラーの例¶
このトピックには、Scalaで記述された UDF ハンドラーコードの簡単な例が含まれています。
Scalaを使用してスカラー UDF ハンドラーを作成する方法については、 Scalaによる スカラー UDF の記述 をご参照ください。コーディングの一般的なガイドラインについては、 Scala UDF ハンドラーコーディングの一般的なガイドライン をご参照ください。
単純なインラインScala UDF の作成と呼び出し¶
次のステートメントは、インラインScala UDF を作成して呼び出します。このコードは、渡された VARCHAR を返します。
この関数は、オプションの CALLED ON NULL INPUT
句を使用して宣言され、入力の値が NULL であっても関数が呼び出されることを示します。(この関数は、この句の有無にかかわらず NULL を返しますが、たとえば空の文字列を返すように、別の方法で NULL を処理するようにコードを変更できます。)
UDF を作成する¶
CREATE OR REPLACE FUNCTION echo_varchar(x VARCHAR)
RETURNS VARCHAR
LANGUAGE SCALA
CALLED ON NULL INPUT
RUNTIME_VERSION = 2.12
HANDLER='Echo.echoVarchar'
AS
$$
class Echo {
def echoVarchar(x : String): String = {
return x
}
}
$$;
UDF を呼び出す¶
SELECT echo_varchar('Hello');
インラインScala UDF への NULL の引き渡し¶
これは、上記で定義された echo_varchar()
UDF を使用します。SQL NULL
値は暗黙的にScala Null に変換され、そのScala Null
が返され、暗黙的に SQL NULL
に変換されます。
UDF を呼び出します。
SELECT echo_varchar(NULL);
インライン UDF からの明示的な NULL の戻り¶
次のコードは、 NULL 値を明示的に返す方法を示しています。Scala値 Null
は SQL NULL
に変換されます。
UDF を作成する¶
CREATE OR REPLACE FUNCTION return_a_null()
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER='TemporaryTestLibrary.returnNull'
AS
$$
class TemporaryTestLibrary {
def returnNull(): String = {
return null
}
}
$$;
UDF を呼び出す¶
SELECT return_a_null();
インラインScala UDF への OBJECT の引き渡し¶
次の例では、 SQL OBJECT データ型と対応するScalaデータ型(Map[String, String]
)を使用し、 OBJECT から値を抽出します。この例は、Scala 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 SCALA
RUNTIME_VERSION = 2.12
HANDLER='VariantLibrary.extract'
AS
$$
import scala.collection.immutable.Map
class VariantLibrary {
def extract(m: Map[String, String], key: String): String = {
return m(key)
}
}
$$;
UDF を呼び出す¶
SELECT extract_from_object(o, 'outer_key'),
extract_from_object(o, 'outer_key')['inner_key'] FROM OBJECTIVES;
インラインScala UDF への ARRAY の引き渡し¶
次の例では、 SQL ARRAY データ型を使用しています。
UDF を作成する¶
CREATE OR REPLACE FUNCTION generate_greeting(greeting_words ARRAY)
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER='StringHandler.handleStrings'
AS
$$
class StringHandler {
def handleStrings(strings: Array[String]): String = {
return concatenate(strings)
}
private def concatenate(strings: Array[String]): String = {
var concatenated : String = ""
for (newString <- strings) {
concatenated = concatenated + " " + newString
}
return concatenated
}
}
$$;
Scala UDF ハンドラーを使用したファイルの読み取り¶
ハンドラーコードを使用してファイルの内容を読み取ることができます。たとえば、ハンドラーを使用してファイルを読み取り、非構造化データを処理する場合があります。
ファイルは、ハンドラーが使用できるSnowflakeのステージである必要があります。
ステージングされたファイルのコンテンツを読み取るために、ハンドラーは、 SnowflakeFile
クラスまたは InputStream
クラスのメソッドを呼び出して、動的に指定されたファイルを読み取ることができます。
呼び出し元が指定したファイルにアクセスする必要があるときには、この操作を実行する場合があります。詳細については、このトピック内で次をご参照ください。
SnowflakeFile
は、次のテーブルで説明されているように、 InputStream
では利用できない機能を提供します。
クラス |
入力 |
メモ |
---|---|---|
|
URL フォーマット:
ファイルは、名前付き内部ステージまたは外部ステージに配置されている必要があります。 |
ファイルサイズなど、追加のファイル属性に簡単にアクセスできます。 |
|
URL フォーマット:
ファイルは、名前付き内部ステージまたは外部ステージに配置されている必要があります。 |
注釈
UDF 所有者は、場所がスコープ付き URLs ではないファイルへのアクセス権を持っている必要があります。新しい requireScopedUrl
パラメーターに boolean
値を指定して SnowflakeFile.newInstance
メソッドを呼び出すハンドラーコードにより、これらのステージングされたファイルを読み取ることができます。
次の例では、スコープ付き URL が不要であることを指定し、 SnowflakeFile.newInstance
を使用します。
var filename = "@my_stage/filename.txt"
var sfFile = SnowflakeFile.newInstance(filename, false)
SnowflakeFile
を使用した動的に指定されたファイルの読み取り¶
SnowflakeFile
クラスのメソッドを使用すると、ハンドラーコードを使用してステージからファイルを読み取ることができます。 SnowflakeFile
クラスは、SnowflakeのScala UDF ハンドラーが使用できるクラスパスに含まれています。
注釈
ファイルインジェクション攻撃に対するコードの回復性を高めるには、ファイルの場所を UDF に渡す場合、特に関数の呼び出し元がその所有者でもない場合に、スコープ URL を常に使用します。組み込み関数 BUILD_SCOPED_FILE_URL を使用して、スコープ URL を SQL に作成できます。BUILD_SCOPED_FILE_URL の機能の詳細については、 非構造化データの概要 をご参照ください。
UDF コードをローカルで開発するには、 SnowflakeFile
を含んでいるSnowpark JAR をコードのクラスパスに追加します。 snowpark.jar
については、 Snowpark Scalaの開発環境の設定 をご参照ください。Snowparkクライアントアプリケーションは、このクラスを使用できないことに注意してください。
SnowflakeFile
を使用する場合は、 SQL で CREATE FUNCTION ステートメントを使用する場合のように、 UDF の作成時に、ステージングされたファイル、または SnowflakeFile
を含んでいる JAR のいずれかを IMPORTS 句で指定する必要はありません。
UDF を作成する¶
次の例のコードは、 SnowflakeFile
を使用して、指定されたステージの場所からファイルを読み取ります。 getInputStream
メソッドの InputStream
を使用して、ファイルの内容を String
変数に読み込みます。
CREATE OR REPLACE FUNCTION sum_total_sales_snowflake_file(file string)
RETURNS INTEGER
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
PACKAGES=('com.snowflake:snowpark:latest')
HANDLER='SalesSum.sumTotalSales'
AS
$$
import java.io.InputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import com.snowflake.snowpark_java.types.SnowflakeFile
object SalesSum {
@throws(classOf[IOException])
def sumTotalSales(filePath: String): Int = {
var total = -1
// Use a SnowflakeFile instance to read sales data from a stage.
val file = SnowflakeFile.newInstance(filePath)
val stream = file.getInputStream()
val 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
}
}
$$;
UDF を呼び出す¶
SELECT sum_total_sales_input_stream(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
InputStream
を使用した動的に指定されたファイルの読み取り¶
ハンドラー関数の引数を InputStream
変数にすると、ファイルの内容を直接 java.io.InputStream
に読み込むことができます。これは、関数の呼び出し元が引数としてファイルパスを渡す場合に役立ちます。
注釈
ファイルインジェクション攻撃に対して耐久性のあるコードにするには、ファイルの場所を UDF に渡すときに、スコープ付き URLs が必要です。組み込み関数 BUILD_SCOPED_FILE_URL を使用して、スコープ URL を SQL に作成できます。BUILD_SCOPED_FILE_URL の機能の詳細については、 非構造化データの概要 をご参照ください。
UDF を作成する¶
次の例のコードには、 InputStream
を受け取り、 Int
を返すハンドラー関数 sumTotalSales
があります。実行時に、Snowflakeは file
変数のパスにあるファイルの内容を stream
引数変数に自動で割り当てます。
CREATE OR REPLACE FUNCTION sum_total_sales_input_stream(file STRING)
RETURNS NUMBER
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER = 'SalesSum.sumTotalSales'
PACKAGES = ('com.snowflake:snowpark:latest')
AS $$
import com.snowflake.snowpark.types.Variant
import java.io.InputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
object SalesSum {
@throws(classOf[IOException])
def sumTotalSales(stream: InputStream): Int = {
val total = -1
val 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
}
}
$$;
UDF を呼び出す¶
SELECT sum_total_sales_input_stream(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));