S-JIS[2026-03-08]
TsurugiのSQLのユーザー定義表値関数(UDTF)をJavaで作る例。
Tsurugiのユーザー定義表値関数(UDTF)は、関数の処理内容をgRPCサーバーで実装する。
そのgRPCサーバーをJavaで作ってみる。
依存ライブラリーには、gRPCとprotobufを入れる。
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'
}
TsurugiのUDFは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
TsurugiのUDTFを処理するgRPCサービスとgRPCサーバーを実装する。
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);
}
}
}
}
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コマンドで確認できる。
> 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で指定した場所にファイルが生成される)
~/.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
cd tsurugi-udf-example ./gradlew runServer
$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)