Beispiele für Scala-UDF-Handler

Unter diesem Thema werden einfache Beispiele für in Scala geschriebenen UDF-Handler-Code bereitgestellt.

Weitere Informationen zur Verwendung von Scala für das Erstellen eines UDF-Handlers finden Sie unter Schreiben einer skalaren UDF in Scala. Allgemeine Kodierungsrichtlinien finden Sie unter Allgemeine Richtlinien zum Codieren von Scala-UDF-Handlern.

Erstellen und Aufrufen einer einfachen Inline-Scala-UDF

Mit den folgenden Anweisungen wird eine Inline-Scala-UDF erstellt und aufgerufen. Dieser Code gibt den VARCHAR-Wert zurück, der übergeben wurde.

Diese Funktion wird mit der optionalen CALLED ON NULL INPUT-Klausel deklariert, um anzugeben, dass die Funktion auch dann aufgerufen wird, wenn der Eingabewert NULL ist. (Diese Funktion würde mit und ohne diese Klausel NULL zurückgeben. Sie könnten den Code aber ändern, um NULL anders zu behandeln, z. B. um eine leere Zeichenfolge zurückzugeben).

Erstellen der 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

Aufrufen der UDF

SELECT echo_varchar('Hello');
Copy

Übergeben von NULL an eine Inline-Scala-UDF

Hier wird die oben definierte echo_varchar()-UDF verwendet. Der SQL-NULL-Wert wird implizit in einen Scala-Null-Wert umgewandelt, und dieser Scala Null-Wert wird zurückgegeben und implizit wieder in SQL-NULL umgewandelt:

Aufrufen der UDF:

SELECT echo_varchar(NULL);
Copy

Explizite Rückgabe von NULL aus einer Inline-UDF

Das folgende Codebeispiel zeigt, wie Sie einen NULL-Wert explizit zurückgeben. Der Scala-Wert Null wird in den SQL-NULL-Wert umgewandelt.

Erstellen der 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

Aufrufen der UDF

SELECT return_a_null();
Copy

Übergeben eines OBJECT-Werts an eine Inline-Scala-UDF

Im folgenden Beispiel werden der Datentyp SQL OBJECT und der entsprechende Scala-Datentyp (Map[String, String]) verwendet, um einen Wert aus OBJECT zu extrahieren. In diesem Beispiel wird auch gezeigt, dass Sie mehrere Parameter an eine Scala-UDF übergeben können.

Erstellen und laden Sie eine Tabelle, die eine Spalte vom Typ OBJECT enthält:

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

Erstellen der 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

Aufrufen der UDF

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

Übergeben eines ARRAY-Werts an eine Inline-Scala-UDF

Im folgenden Beispiel wird der SQL-Datentyp ARRAY verwendet.

Erstellen der 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

Lesen einer Datei mit einer Scala-UDF

Sie können den Inhalt einer Datei mit Handler-Code lesen. Sie könnten zum Beispiel eine Datei lesen, um unstrukturierte Daten mit dem Handler zu verarbeiten.

Die Datei muss sich in einem Snowflake-Stagingbereich befinden, auf den Ihr Handler zugreifen kann.

Um den Inhalt von Stagingdateien zu lesen, kann Ihr Handler eine dynamisch spezifizierte Datei lesen, indem er Methoden entweder der Klasse SnowflakeFile oder der Klasse InputStream aufruft.

Dies kann der Fall sein, wenn Sie auf eine vom Aufrufer angegebene Datei zugreifen müssen. Weitere Informationen dazu finden Sie in den folgenden Abschnitten unter diesem Thema:

SnowflakeFile bietet Features, die bei InputStream nicht verfügbar sind, wie in der folgenden Tabelle beschrieben.

Klasse

Eingabe

Anmerkungen

SnowflakeFile

URL-Formate:

  • Bereichs-URL, um das Risiko von Angriffen per Dateieinschleusung zu verringern, wenn der Aufrufer der Funktion nicht deren Eigentümer ist.

  • Datei-URL oder Dateipfad-Zeichenfolge von Dateien, auf die der Eigentümer der UDF Zugriff hat.

Die Datei muss sich in einem benannten internen Stagingbereich oder in einem externen Stagingbereich befinden.

Einfacher Zugriff auf zusätzliche Dateiattribute, wie z. B. die Dateigröße.

InputStream

URL-Formate:

  • Bereichs-URL, um das Risiko von Angriffen per Dateieinschleusung zu verringern, wenn der Aufrufer der Funktion nicht deren Eigentümer ist.

Die Datei muss sich in einem benannten internen Stagingbereich oder in einem externen Stagingbereich befinden.

Bemerkung

Der Eigentümer der UDF muss Zugriff auf alle Dateien haben, deren Speicherorte keine Bereichs-URLs sind. Sie können diese Stagingdateien lesen, indem Ihr Handler-Code die Methode SnowflakeFile.newInstance mit einem boolean-Wert für einen neuen requireScopedUrl-Parameter aufruft.

Das folgende Beispiel verwendet SnowflakeFile.newInstance und gibt gleichzeitig an, dass keine Bereichs-URL erforderlich ist.

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

Lesen einer mit SnowflakeFile dynamisch spezifizierten Datei

Die Methoden der SnowflakeFile-Klasse können im Handler-Code verwendet werden, um Dateien aus einem Stagingbereich zu lesen. Die SnowflakeFile-Klasse wird in den Klassenpfad aufgenommen, der in Snowflake für Scala-UDF-Handler verfügbar ist.

Bemerkung

Um Ihren Code widerstandsfähig gegen Angriffe per Dateieinschleusung zu machen, verwenden Sie immer eine Bereichs-URL, wenn Sie den Speicherort einer Datei an eine UDF übergeben, insbesondere wenn der Aufrufer der Funktion nicht auch ihr Eigentümer ist. Mit der integrierten Funktion BUILD_SCOPED_FILE_URL können Sie eine Bereichs-URL in SQL erstellen. Weitere Informationen zur Funktion BUILD_SCOPED_FILE_URL finden Sie unter Einführung in das Laden unstrukturierter Daten.

Um Ihren UDF-Code lokal zu entwickeln, fügen Sie die Snowpark-JAR-Datei, die die SnowflakeFile-Klasse enthält, zum Klassenpfad Ihres Codes hinzu. Weitere Informationen zur snowpark.jar-Datei finden Sie unter Einrichten Ihrer Entwicklungsumgebung für Snowpark Scala. Beachten Sie, dass Snowpark-Clientanwendungen diese Klasse nicht verwenden können.

Wenn Sie SnowflakeFile verwenden, ist es beim Erstellen der UDF nicht notwendig, in einer IMPORTS-Klausel die Stagingdatei bzw. die JAR-Datei, die SnowflakeFile enthält, anzugeben, wie dies in SQL mit einer CREATE FUNCTION-Anweisung erforderlich ist.

Erstellen der UDF

Im folgenden Codebeispiel wird SnowflakeFile verwendet, um eine Datei vom Speicherort des Stagingbereichs zu lesen. Mit InputStream aus der getInputStream-Methode wird der Inhalt der Datei in eine String-Variable gelesen.

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

Aufrufen der UDF

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

Lesen einer mit InputStream dynamisch spezifizierten Datei

Sie können den Inhalt einer Datei direkt in einen java.io.InputStream-Eingabestream einlesen, indem Sie das Argument Ihrer Handler-Funktion als InputStream-Variable behandeln. Dies kann nützlich sein, wenn der Aufrufer der Funktion einen Dateipfad als Argument übergeben möchte.

Bemerkung

Um Ihren Code widerstandsfähig gegen Angriffe per Dateieinschleusung zu machen, verwenden Sie immer eine Bereichs-URLs, wenn Sie den Speicherort einer Datei an eine UDF übergeben. Mit der integrierten Funktion BUILD_SCOPED_FILE_URL können Sie eine Bereichs-URL in SQL erstellen. Weitere Informationen zur Funktion BUILD_SCOPED_FILE_URL finden Sie unter Einführung in das Laden unstrukturierter Daten.

Erstellen der UDF

Im folgenden Codebeispiel nimmt ein Handler-Funktion sumTotalSales einen InputStream-Wert entgegen und gibt einen Int-Wert zurück. Zur Laufzeit weist Snowflake den Inhalt der Datei mit dem in der Variable file angegebenen Pfad automatisch der Argumentvariablen stream zu.

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

Aufrufen der UDF

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