Exemples de gestionnaires d’UDF Scala

Cette rubrique comprend des exemples simples de code de gestionnaire d’UDF écrits en Scala.

Pour plus d’informations sur l’utilisation de Scala pour créer un gestionnaire d’UDF scalaires, reportez-vous à Écriture d’une UDF scalaire en Scala. Pour connaître les lignes directrices générales en matière de codage, voir Lignes directrices générales pour le codage de gestionnaires d’UDF Scala.

Création et appel d’une simple UDF Scala en ligne

Les instructions suivantes créent et appellent une UDF Scala en ligne. Ce code renvoie le VARCHAR qui lui est transmis.

Cette fonction est déclarée avec la clause facultative CALLED ON NULL INPUT pour indiquer que la fonction est appelée même si la valeur de l’entrée est NULL. (Cette fonction renverrait NULL avec ou sans cette clause, mais vous pourriez modifier le code pour traiter NULL d’une autre manière, par exemple, pour renvoyer une chaîne vide).

Créez l’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

Appelez l’UDF

SELECT echo_varchar('Hello');
Copy

Transmission d’une valeur NULL à une UDF Scala en ligne

Ceci utilise les echo_varchar() UDF définies ci-dessus. La valeur SQL NULL est implicitement convertie en Scala nulle, et cette valeur Scala Null est retournée et implicitement reconvertie au format SQL NULL :

Appelez l’UDF :

SELECT echo_varchar(NULL);
Copy

Renvoyer NULL explicitement à partir d’une UDF en ligne

Le code suivant montre comment retourner une valeur NULL de manière explicite. La valeur Scala Null est convertie en SQL NULL.

Créez l’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

Appelez l’UDF

SELECT return_a_null();
Copy

Transmission d’un OBJECT à une UDF Scala en ligne

L’exemple suivant utilise le type de données SQL OBJECT et le type de données Scala correspondant (Map[String, String]), et extrait une valeur à partir de OBJECT. Cet exemple montre également que vous pouvez transmettre plusieurs paramètres à une UDF Scala.

Créer et charger une table qui contient une colonne de type OBJECT :

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

Créez l’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

Appelez l’UDF

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

Transmission d’un ARRAY à une UDF Scala en ligne

L’exemple suivant utilise le type de données SQL ARRAY.

Créez l’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

Lecture d’un fichier à l’aide d’une UDF Scala

Vous pouvez lire le contenu d’un fichier avec le code du gestionnaire. Par exemple, vous pourriez vouloir lire un fichier pour traiter des données non structurées avec le gestionnaire.

Le fichier doit se trouver sur une zone de préparation Snowflake qui est disponible pour votre gestionnaire.

Pour lire le contenu de fichiers en zone de préparation, votre gestionnaire peut lire un fichier spécifié de façon dynamique en appelant les méthodes de la classe SnowflakeFile ou de la classe InputStream.

Vous pouvez procéder ainsi si vous devez accéder à un fichier spécifié par l’appelant. Pour plus d’informations, voir les sections suivantes de ce chapitre :

SnowflakeFile offre des fonctionnalités non disponibles avec InputStream, comme décrit dans le tableau suivant.

Classe

Entrée

Remarques

SnowflakeFile

Formats d’URL :

  • URL scopée qui réduit le risque d’attaques par injection de fichiers lorsque l’appelant de la fonction n’est pas également son propriétaire.

  • URL de fichier ou chemin de chaîne pour les fichiers auxquels le propriétaire de l’UDF a accès.

Le fichier doit être situé dans une zone de préparation interne ou externe nommée.

Accédez facilement à des attributs de fichier supplémentaires, tels que la taille du fichier.

InputStream

Formats d’URL :

  • URL scopée qui réduit le risque d’attaques par injection de fichiers lorsque l’appelant de la fonction n’est pas également son propriétaire.

Le fichier doit être situé dans une zone de préparation interne ou externe nommée.

Note

Le propriétaire de l’UDF doit avoir accès à tous les fichiers dont les emplacements ne sont pas des URLs scopées. Vous pouvez lire ces fichiers en zone de préparation en demandant au code du gestionnaire d’appeler la méthode SnowflakeFile.newInstance avec une valeur boolean pour un nouveau paramètre requireScopedUrl.

L’exemple suivant utilise SnowflakeFile.newInstance tout en spécifiant qu’une URL scopée n’est pas nécessaire.

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

Lecture d’un fichier spécifié de façon dynamique avec SnowflakeFile

En utilisant les méthodes de la classe SnowflakeFile , vous pouvez lire les fichiers d’une zone de préparation avec votre code de gestionnaire. La classe SnowflakeFile est incluse dans le classpath accessible aux gestionnaires d’UDF Scala sur Snowflake.

Note

Pour rendre votre code résistant aux attaques par injection de fichiers, utilisez toujours une URL scopée lorsque vous transmettez l’emplacement d’un fichier à une UDF, en particulier lorsque l’appelant de la fonction n’est pas également son propriétaire. Vous pouvez créer une URL scopée en SQL à l’aide de la fonction intégrée BUILD_SCOPED_FILE_URL. Pour plus d’informations sur le rôle de BUILD_SCOPED_FILE_URL, voir Introduction aux données non structurées.

Pour développer le code de votre UDF localement, ajoutez le fichier JAR Snowpark contenant SnowflakeFile au chemin de classe de votre code. Pour plus d’informations sur snowpark.jar, voir Configuration de votre environnement de développement pour Snowpark Scala. Notez que les applications clientes Snowpark ne peuvent pas utiliser cette classe.

Lorsque vous utilisez SnowflakeFile, il n’est pas nécessaire de spécifier également le fichier en zone de préparation ou le fichier JAR contenant SnowflakeFile avec une clause IMPORTS lorsque vous créez l’UDF, comme dans SQL avec une instruction CREATE FUNCTION.

Créez l’UDF

Le code de l’exemple suivant utilise SnowflakeFile pour lire un fichier à partir d’un emplacement de zone de préparation spécifié. En utilisant un InputStream à partir de la méthode getInputStream, il lit le contenu du fichier dans une variable 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

Appelez l’UDF

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

Lecture d’un fichier spécifié de façon dynamique avec InputStream

Vous pouvez lire le contenu du fichier directement dans un java.io.InputStream en faisant de l’argument de votre fonction de traitement une variable InputStream. Cela peut être utile lorsque l’appelant de la fonction veut passer un chemin de fichier comme argument.

Note

Pour que votre code résiste aux attaques par injection de fichiers, vous devez utiliser des URLs scopées lorsque vous transmettez l’emplacement d’un fichier à une UDF. Vous pouvez créer une URL scopée en SQL à l’aide de la fonction intégrée BUILD_SCOPED_FILE_URL. Pour plus d’informations sur le rôle de BUILD_SCOPED_FILE_URL, voir Introduction aux données non structurées.

Créez l’UDF

Le code de l’exemple suivant comporte une fonction de gestionnaire sumTotalSales qui prend un InputStream et renvoie un Int. Au moment de l’exécution, Snowflake attribue automatiquement le contenu du fichier au chemin d’accès de la variable file à la variable de l’argument 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

Appelez l’UDF

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