Overview
この記事の対象読者はPrestoでUDFを作ったことがある人です。
PrestoとはFacebookが開発したオープンソースの分散クエリ処理エンジンです。
詳細はこちらを参照。
Prestoはプラグインで拡張することが可能でUDFもプラグインとして導入することが出来ます。
これまでいくつかのUDFをJavaで作って来ましたが、最近何年かぶりにscalaに興味が出てきたのでUDFをscalaで書いてみました。
Sample Code
今回のコードはここにあります。
Update pom.xml
まず、pom.xmlを編集してscalaをコンパイル出来る環境を整えます。
今回は比較のためにJavaでも同じようなUDFを作成するのでJava/Scala混生のmavenプロジェクトのための設定を導入します。
scala-maven-plugin
のドキュメントにJava/Scala混在プロジェクトでのビルド方法が書かれているのでそれを参考にします。
- http://davidb.github.io/scala-maven-plugin/example_java.html
- http://davidb.github.io/scala-maven-plugin/example_incremental.html
次の3つのコードをpom.xmlに追加します。
<properties>
<scala.major.version>2.12</scala.major.version>
<scala.version>${scala.major.version}.0</scala.version>
<scala.maven.plugin.version>3.4.2</scala.maven.plugin.version>
</properties>
<dependencies>
<!--scala-->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>${scala.maven.plugin.version}</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
scala UDFの開発
次にscalaでハローワールドな関数を書きます。
object HelloWorldScalaUDF {
@Description("Hello World(UDF Practice)")
@ScalarFunction("hello_worlds")
@SqlType(StandardTypes.VARCHAR)
def helloworld(@SqlNullable @SqlType(StandardTypes.VARCHAR) name: Slice): Slice =
if (name == null || name.toStringUtf8.isEmpty) {
utf8Slice("Hello World from scala")
}
else {
utf8Slice(String.format("Hello %s from scala", name.toStringUtf8))
}
}
ほぼ同じコードをJava版で書くとこちらです。簡単なコードなので関数のコードだけを比べればそれほど大きな差はありません。
return
, ()
の数が減ったぐらいの差しかありません。
public class HelloWorldUDF
{
private HelloWorldUDF()
{
}
@Description("Hello World(UDF Practice)")
@ScalarFunction("hello_world")
@SqlType(StandardTypes.VARCHAR)
public static Slice helloworld(@SqlNullable @SqlType(StandardTypes.VARCHAR) Slice name)
{
if (name == null || name.toStringUtf8().isEmpty()) {
return utf8Slice("Hello World");
}
else {
return utf8Slice(String.format("Hello %s", name.toStringUtf8()));
}
}
}
最後に UDFPlugin#getFunctions
にscalaのクラスを登録して完了です。
public class UdfPlugin implements Plugin {
@Override
public Set<Class<?>> getFunctions()
{
return ImmutableSet.<Class<?>>builder()
.add(HelloWorldScalaUDF.class)
.build();
}
}
ビルド
あとはこれをmavenでビルドしてjarを作るだけです。
$ ./mvnw clean compile package
scala -> Javaの順番にコンパイルしてあげないとUdfPlugin
でコンパイルが失敗するのですがscala-maven-plugin
がその辺を面倒を見てくれています。
[INFO] --- scala-maven-plugin:3.4.2:add-source (scala-compile-first) @ presto-sample-udf ---
[INFO] Add Source directory: /Users/callistoiv/works/IdeaProjects/prestodb/ecosystem/presto-sample-udf/src/main/scala
[INFO] Add Test Source directory: /Users/callistoiv/works/IdeaProjects/prestodb/ecosystem/presto-sample-udf/src/test/scala
[INFO]
[INFO] --- scala-maven-plugin:3.4.2:compile (scala-compile-first) @ presto-sample-udf ---
[INFO] /Users/callistoiv/works/IdeaProjects/prestodb/ecosystem/presto-sample-udf/src/main/java:-1: info: compiling
[INFO] /Users/callistoiv/works/IdeaProjects/prestodb/ecosystem/presto-sample-udf/src/main/scala:-1: info: compiling
[INFO] Compiling 4 source files to /Users/callistoiv/works/IdeaProjects/prestodb/ecosystem/presto-sample-udf/target/classes at 1537073450711
[INFO] prepare-compile in 0 s
[INFO] compile in 3 s
まとめ
scalaでPresto UDFを作る環境構築の方法を紹介しました。
今回のサンプルコードだとscala化のメリットがあるとは言えませんが業務で作るようなif-else
文満載のコードをパターンマッチで置き換えることで
コードの可読性があがるのではないかという可能性を感じました。
また、ユニットテストのscala化も検討したが自分が書くpresto UDFのユニットテストがそんなに複雑なものではなくscala化のメリットが得られそうになかったので見送りました。