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

UDF を呼び出す

SELECT echo_varchar('Hello');
Copy

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

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

UDF を呼び出します。

SELECT echo_varchar(NULL);
Copy

インライン 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
  }
}
$$;
Copy

UDF を呼び出す

SELECT return_a_null();
Copy

インライン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"} }');
Copy

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

UDF を呼び出す

SELECT extract_from_object(o, 'outer_key'),
  extract_from_object(o, 'outer_key')['inner_key'] FROM OBJECTIVES;
Copy

インライン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
  }
}
$$;
Copy

Scala UDF ハンドラーを使用したファイルの読み取り

ハンドラーコードを使用してファイルの内容を読み取ることができます。たとえば、ハンドラーを使用してファイルを読み取り、非構造化データを処理する場合があります。

ファイルは、ハンドラーが使用できるSnowflakeのステージである必要があります。

ステージングされたファイルのコンテンツを読み取るために、ハンドラーは、 SnowflakeFile クラスまたは InputStream クラスのメソッドを呼び出して、動的に指定されたファイルを読み取ることができます。

呼び出し元が指定したファイルにアクセスする必要があるときには、この操作を実行する場合があります。詳細については、このトピック内で次をご参照ください。

SnowflakeFile は、次のテーブルで説明されているように、 InputStream では利用できない機能を提供します。

クラス

入力

メモ

SnowflakeFile

URL フォーマット:

  • 関数の呼び出し元がその所有者でもない場合に、ファイルインジェクション攻撃のリスクを軽減するためのスコープ URL を設定しました。

  • ファイル URL または UDF 所有者がアクセス権を持っているファイルの文字列パス。

ファイルは、名前付き内部ステージまたは外部ステージに配置されている必要があります。

ファイルサイズなど、追加のファイル属性に簡単にアクセスできます。

InputStream

URL フォーマット:

  • 関数の呼び出し元がその所有者でもない場合に、ファイルインジェクション攻撃のリスクを軽減するためのスコープ URL を設定しました。

ファイルは、名前付き内部ステージまたは外部ステージに配置されている必要があります。

注釈

UDF 所有者は、場所がスコープ付き URLs ではないファイルへのアクセス権を持っている必要があります。新しい requireScopedUrl パラメーターに boolean 値を指定して SnowflakeFile.newInstance メソッドを呼び出すハンドラーコードにより、これらのステージングされたファイルを読み取ることができます。

次の例では、スコープ付き URL が不要であることを指定し、 SnowflakeFile.newInstance を使用します。

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

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

UDF を呼び出す

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

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

UDF を呼び出す

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