SQL Server 2019 から sparkjava を実行してみた

SQL Server 2019 CTP2 で Java を動かしてみた - お だ のスペース で紹介した external_script で Java を呼べる機能を使って、Spark Framework: An expressive web framework for Kotlin and Java を実行してみました。
meetup app osaka@3 で Spark on SQL Server? という話ししました #meetupapp - お だ のスペース のスライドでも書いてますが、動いてますが外部*1から HTTP アクセス出来ませんでした。
なので、同じメソッド内から OkHttp で GETアクセスして動作確認しています。

Java 久しぶりすぎて、Maven も Gradle も書かれへんかった。。
適当に拾ってきたものをいじって、

  • com.sparkjava.spark-core
  • com.squareup.okhttp3.okhttp

を依存関係に突っ込んでるだけです。
幾つか必要な static フィールドがありますが、 How to call Java from SQL - SQL Server Machine Learning Services - SQL Server | Microsoft Docs を見てください。
実装の中で使わなくても今回のケースでは問題無いです。*2

package pkg;

import static spark.Spark.*;
import java.io.IOException;
import java.time.LocalDateTime;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class Hoge {
    public static int[] inputDataCol1 = new int[1];
    public static String[] inputDataCol2 = new String[1];

    //Required: Input null map. Size just needs to be set to "1"
    public static boolean[][] inputNullMap = new boolean[1][1];

    //Required: Output data columns returned back to SQL Server
    public static int[] outputDataCol1;
    public static String[] outputDataCol2;

    //Required: Output null map. Is populated with true or false values 
    //to indicate nulls
    public static boolean[][] outputNullMap;

    //Optional: This is only required if parameters are passed with @params
    // from SQL Server in sp_execute_external_script
    // n is giving us the size of ngram substrings
    public static int param1;

    //Optional: The number of rows we will be returning
    public static int numberOfRows;

    //Required: Number of output columns returned
    public static short numberOfOutputCols;

    public static void runSqlServer() {
        get("/hello", (request, response) -> "<h1>Hello Spark!! on SQL Server?</h1>");
        try { 
            String res = run("http://localhost:4567/hello");
            System.out.println(res);
        } catch (IOException e) {
            System.out.println(e);
        }
    }
    static OkHttpClient client = new OkHttpClient();

    static String run(String url) throws IOException {
        Request request = new Request.Builder()
            .url(url)
            .build();

        try (Response response = client.newCall(request).execute()) {
            return response.body().string();
        }
    }
}

これで HTTP アクセスが失敗する場合は、spark の設定 と HTTP アクセスの間に Thread.sleep なりなんなりで処理を遅らせてください。
Thread.sleep 入れて、実行中にポートの待ち受けを確認するとこんな感じです。
f:id:odashinsuke:20181217163251j:plain スライドにも書いてますが、Java.exe ではなく、ExtHost.exe として動いています。
ExtHost.exe として、4567 ポートで待ち受けています。
メソッド内の処理が全て終わってしまう*3と、spark のサーバーは落ちてしまいます。
外部からアクセス出来ないし、ずっと動かすには sleep や無限ループ等でメソッドが終わらないようにする必要があります。
※ExtHost にタイムアウトがあるかは未検証なので、無限ループの場合は注意
という訳で、余り実用向けではありませんが遊ぶ分には問題ありません。

sparkjava の実行とは関係無いですが、OkHttp なりなんなりで、外部にHTTPアクセスする事も出来ます。
なので外部のAPIとかを叩いてデータを引っ張ってきて遊ぶことも出来ると思います。

今回ご紹介したものは、「実運用を目指していない」お遊びのものなので、本番でこの構成を使おうとか間違っても辞めてね。。

*1:ローカルからの別プロセスも

*2:普通に使うと Java へのパラメーターや戻り値といった使い方になります

*3:external_script の呼出が終わってしまう