S-JIS[2026-03-08]

Tsurugi UDTF Javaでの実装例

TsurugiSQLユーザー定義表値関数(UDTF)をJavaで作る例。


概要

Tsurugiのユーザー定義表値関数(UDTF)は、関数の処理内容をgRPCサーバーで実装する。
そのgRPCサーバーをJavaで作ってみる。


build.gradle

依存ライブラリーには、gRPCとprotobufを入れる。

tsurugi-udf-example/build.gradle:

plugins {
    id 'java'
    id 'application'
    id 'com.google.protobuf' version '0.9.4'
}

group = 'com.example.tsurugi.udf'
version = '0.1-SNAPSHOT'

repositories {
    mavenCentral()
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

dependencies {
    implementation 'io.grpc:grpc-netty-shaded:1.59.0'
    implementation 'io.grpc:grpc-protobuf:1.59.0'
    implementation 'io.grpc:grpc-stub:1.59.0'
    implementation 'javax.annotation:javax.annotation-api:1.3.2'
    implementation 'com.google.protobuf:protobuf-java:3.25.0'
}

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.25.0'
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.59.0'
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
        }
    }
}

sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

application {
    mainClass = 'com.example.tsurugi.udf.UdtExampleServer'
}

task runServer(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath
    mainClass = 'com.example.tsurugi.udf.UdtExampleServer'
}

protoファイル

TsurugiのUDFはprotoファイルで定義する。

tsurugi-udf-example/src/main/proto/example.proto:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.tsurugi.udf.proto";

message MyTableFunctionRequest {
  int32 value = 1;
}

message MyTableFunctionResponse {
  int32 value1 = 1;
  int32 value2 = 2;
}

service ExampleService {
  rpc my_table_function(MyTableFunctionRequest) returns (stream MyTableFunctionResponse);
}

TsurugiのUDTF用のrpc関数は、引数と戻り値をmessageで定義する。
(引数が1個でも必ずmessageを定義する)

UDTFでは、returnsをstreamにする。


Gradleでビルドすると、protoファイルからJavaのソースが生成される。

cd tsurugi-udf-example
./gradlew build

gRPCサーバーの実装

TsurugiのUDTFを処理するgRPCサービスとgRPCサーバーを実装する。

tsurugi-udf-example/src/main/java/com/example/tsurugi/udf/UdfExampleServer.java:

package com.example.tsurugi.udf;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import com.example.tsurugi.udf.proto.ExampleServiceGrpc;
import com.example.tsurugi.udf.proto.MyFunctionRequest;
import com.example.tsurugi.udf.proto.MyFunctionResponse;
public class UdfExampleServer {
    private static final int PORT = 50051;

    public static void main(String[] args) throws IOException, InterruptedException {
        var server = new UdfExampleServer();
        server.start();
        server.blockUntilShutdown();
    }
    private Server server;

    private void start() throws IOException {
        server = ServerBuilder.forPort(PORT).addService(new ExampleServiceImpl()).build().start();
        System.out.println("Server started, listening on " + PORT);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.err.println("*** shutting down gRPC server since JVM is shutting down");
            try {
                UdfExampleServer.this.stop();
            } catch (InterruptedException e) {
                e.printStackTrace(System.err);
            }
            System.err.println("*** server shut down");
        }));
    }

    private void stop() throws InterruptedException {
        if (server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }
    static class ExampleServiceImpl extends ExampleServiceGrpc.ExampleServiceImplBase {

        @Override
        public void myTableFunction(MyTableFunctionRequest request, StreamObserver<MyTableFunctionResponse> responseObserver) {
            try {
                int value = request.getValue();

                for (int i = 0; i < 3; i++) {
                    var response = MyTableFunctionResponse.newBuilder().setValue1(i).setValue2(value + i).build();
                    responseObserver.onNext(response);
                }
                responseObserver.onCompleted();
            } catch (Exception e) {
                responseObserver.onError(e);
            }
        }
    }
}

TsurugiへのUDTFの登録

src/main/protoのprotoファイルを元に、udf-plugin-builderを使ってTsurugiにUDTFを登録する。


TsurugiのDockerイメージのTsurugiを使い、gRPCサーバーがホストにある場合は、登録時にホストのIPアドレスを指定する。
WindowsでDokcer Desktopを使っているならホスト名をhost.docker.internalにすればいいが、WSL2にインストールされたDockerを使っている場合は、ホストのIPアドレスを直接指定する。

WSL2から見たホストのIPアドレスは、PowerShellのipconfigコマンドで確認できる。

WindowsのPowerShellから

> ipconfig
Windows IP 構成

イーサネット アダプター vEthernet (WSL (Hyper-V firewall)):

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::8abc:xxxx:xxxx:xxxx%46
   IPv4 アドレス . . . . . . . . . . . .: 17y.yyy.yyy.yyy
   サブネット マスク . . . . . . . . . .: 255.255.240.0
   デフォルト ゲートウェイ . . . . . . .:
〜

udf-plugin-builderを実行してTsurugiに登録する。(--output-dirで指定した場所にファイルが生成される)

Tsurugiサーバー上で

~/.local/bin/udf-plugin-builder --proto example.proto --grpc-endpoint 17y.yyy.yyy.yyy:50051 --output-dir $TSURUGI_HOME/var/plugins/

接続先ホストとポート番号は、生成されたiniファイルを見れば確認できる。(このファイルを直接編集してもいい)

tsurugi@485bb627de4c:/usr/lib/tsurugi-1.9.0$ cat $TSURUGI_HOME/var/plugins/libexample.ini
[udf]
enabled=true
endpoint=17y.yyy.yyy.yyy:50051
secure=false
transport=stream

$TSURUGI_HOME/var/plugins/に配置したら、Tsurugiを再起動する。

$TSURUGI_HOME/bin/tgctl shutdown
$TSURUGI_HOME/bin/tgctl start

gRPCサーバーの実行

cd tsurugi-udf-example
./gradlew runServer

UDTFの実行例

$TSURUGI_HOME/bin/tgsql -c ipc:tsurugi
create table test (foo int primary key);
insert into test values(1), (2), (100);
tgsql> select foo, f.value1, f.value2
     | from test
     | apply my_table_function(foo) as f
     | ;
[foo: INT, value1: INT, value2: INT]
[1, 0, 1]
[1, 1, 2]
[1, 2, 3]
[2, 0, 2]
[2, 1, 3]
[2, 2, 4]
[100, 0, 100]
[100, 1, 101]
[100, 2, 102]
(9 rows)

UDTFへ戻る / SQLへ戻る / Tsurugiへ戻る / 技術メモへ戻る
メールの送信先:ひしだま