【jOOQ】getSqlでプリペアドステートメントが$1, $2, ...形式のクエリを取得する【PostgreSQL】

文脈

jOOQでは、getSqlでパラメータが埋め込まれる前のクエリ文字列を得られます。
このクエリについて、デフォルトでは、select * from table where id = ?というように、プリペアドステートメント?で表現されています。

一方PostgreSQLでは、select * from table where id = $1というように、プリペアドステートメント$1, $2, ...形式で要求されます(少なくとも、r2dbc-postgresqlではこの形式以外受け付けられません)。
そこで、jOOQの設定調整によってこの形式でクエリが得られるようにします。

やり方

設定によってはgetSQL(ParamType.NAMED)で取得できます。

www.jooq.org

もし上記でダメなら、setRenderNamedParamPrefixsetParseNamedParamPrefixの両方に"$"を設定した上でgetSQL(ParamType.NAMED)すれば取得できます。

www.jooq.org

www.jooq.org

setParamTypeParamType.NAMEDを指定すれば、getSQL(/* 何も指定しない */)で取得した場合もこの形式になります。

www.jooq.org

補足

以下を参考にしました。

github.com

【MockK】mock/spyされたオブジェクトを大量に呼び出すと異常な負荷・OOMが生じた【Kotlin】

状況

パターン網羅のために大量の呼び出しを行うようなテストを追加した所、java.lang.OutOfMemoryErrorでテストが失敗する状況が生じました。
外形的な挙動としては、テスト関数が1つ終わってもメモリが解放されず、テストが進むにつれメモリ使用量も線形に伸びていました。
また、CPUにも大きめの負荷がかかっているようでした。

調査した所、これらのリソース消費増大はMockKmock/spyした内容を大量に呼び出した場合に起きていました。

問題の起きたプロジェクトはSpringBootを用いていますが、テスト用のBeanとして使われているmock(= 元から大量呼び出しが有った)でこの症状が発生していました。
また、1テストに閉じたmockであっても、数十 ~ 数百万回以上呼び出した場合には500MB以上のメモリ消費が生じていました。
MockKの開いているイシューにはこのような問題の報告が無かったため、もしかするとSpringBootTest/SpringMockKに関係が有るのかもしれません。

少なくともバージョン1.12.11.13.10(執筆時点の最新最新)で発生を確認しています。

対処

最新でも問題が生じていたため、一旦大量呼び出しが想定されるオブジェクトではMockKを使わない方針としました。

【Java】Java17以降で整数の完全な16進数文字列を得たいならHexFormatがおすすめ

Long.toStringは上位ビットが0だった場合に削れた文字列を返してしまいます。
完全な16進数表記を得るにはString.format("%016x", number)を利用する例がよく引っかかりますが、内部処理を見ると結構重そうなことをしています。

一方、Java 17からは、このような場合に利用可能なHexFormatというクラスが追加されました。

docs.oracle.com

toHexDigitsの内部実装を読んだ所、以下のような形で、ほぼビット演算のみで実装されていました。
ベンチマークは取っていませんが、これなら確実にString.formatよりも高速でしょう。

Java内から引用
    public String toHexDigits(long value) {
        byte[] rep = new byte[16];
        rep[0] = (byte)toHighHexDigit((byte)(value >>> 56));
        rep[1] = (byte)toLowHexDigit((byte)(value >>> 56));
        rep[2] = (byte)toHighHexDigit((byte)(value >>> 48));
        rep[3] = (byte)toLowHexDigit((byte)(value >>> 48));
        rep[4] = (byte)toHighHexDigit((byte)(value >>> 40));
        rep[5] = (byte)toLowHexDigit((byte)(value >>> 40));
        rep[6] = (byte)toHighHexDigit((byte)(value >>> 32));
        rep[7] = (byte)toLowHexDigit((byte)(value >>> 32));
        rep[8] = (byte)toHighHexDigit((byte)(value >>> 24));
        rep[9] = (byte)toLowHexDigit((byte)(value >>> 24));
        rep[10] = (byte)toHighHexDigit((byte)(value >>> 16));
        rep[11] = (byte)toLowHexDigit((byte)(value >>> 16));
        rep[12] = (byte)toHighHexDigit((byte)(value >>> 8));
        rep[13] = (byte)toLowHexDigit((byte)(value >>> 8));
        rep[14] = (byte)toHighHexDigit((byte)value);
        rep[15] = (byte)toLowHexDigit((byte)value);

        try {
            return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1);
        } catch (CharacterCodingException cce) {
            throw new AssertionError(cce);
        }
    }

また、このクラスには16進数文字列 -> intのようなデコード関数も実装されており、相互変換に利用できます。

docs.oracle.com

【jOOQ】UUIDを大量指定する際の省メモリ化【PostgreSQL】

状況

大量のUUIDを指定したin句を発行する際に、メモリ消費量が問題になりました。
見たところ、jOOQではUUID1件当たりcast('b81b8735-6ac8-4b0b-a969-658e70425616' as uuid)みたいな形のクエリになってしまうことが原因のようでした。

対処

応急処置として、以下2点を満たす省メモリ化されたin句を発行するためのカスタム実装を行いました。

  • cast文無し
  • UUIDのハイフン無し

サンプルコード

Kotlinかつ一部余計な処理が入ってますが、要するに${取得対象カラム} in (b81b87356ac84b0ba969658e70425616, ...)みたいな条件を生成しています。

private val hexFormat = HexFormat.of()

fun UUID.hyphenLessStr(): String = hexFormat.toHexDigits(this.mostSignificantBits) + hexFormat.toHexDigits(this.leastSignificantBits)

// jOOQの生成結果が基本nullableなため、Fieldの型パラメータはnullableにしている
fun <T> Field<UUID?>.reducedIn(args: Collection<T>, unbox: (T) -> UUID): Condition {
    val condStr = args.joinToString(prefix = "${this.qualifiedName} in (", separator = ",", postfix = ")") {
        // アプリ上メモリ消費量低減のため、普通のUUIDからハイフンを抜いた形式にしている
        // 参照: https://www.postgresql.org/docs/14/datatype-uuid.html
        "'${unbox(it).hyphenLessStr()}'"
    }

    return DSL.condition(condStr)
}

PostgreSQLのクエリにおけるUUID指定について

ハイフン無しでOKということは文章化されています。

www.postgresql.org

【Java】AWS SDK2で、S3からファイルへgetObjectする際、デフォルトでは失敗時にDL先ファイルが削除される

AsyncClientの方で調査していますが、通常の方も同じ内容が有るはずです。

特にコンフィグ無しでAsyncResponseTransformer.toFileを呼び出した場合、FileTransformerConfigurationdefaultCreateNewで設定されます。
aws-sdk-java-v2/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/async/FileAsyncResponseTransformer.java at bb6c1a489e2bfd03dc08ccf2bb94278342f810b9 · aws/aws-sdk-java-v2 · GitHub

この中ではFailureBehavior.DELETEが指定されています。
aws-sdk-java-v2/core/sdk-core/src/main/java/software/amazon/awssdk/core/FileTransformerConfiguration.java at bb6c1a489e2bfd03dc08ccf2bb94278342f810b9 · aws/aws-sdk-java-v2 · GitHub

(よく読んだらJavadocにもそう書いてありました)

sdk.amazonaws.com

【Kotlin】kotlin-reflectを弄るためのメモ

github.com

greadle.propertiesを弄る

ビルド・テストで全コア使われると他アプリの動作が重くなるので、kotlin.test.junit5.maxParallelForks全コア数 - 最低限確保したいコア数にしていいかも。
以前はJDK7以前を無視するオプションも有ったがいつの間にか消えていた。

テスト周り

変更

kotlin-reflect関連はcompiler/testData/codegen/box/reflectionに有る。
特にテスト追加のような変更後はgenerateTestsIDEに保存されている内だとGenerators -> GenerateTests)を実行することで、各環境向けテスト生成を行う(この結果もコミットが必要)。

実行

codegenTarget8Jvm8Testを実行すれば最低限の確認は出来る。

成果物の生成先

kotlin-reflectjarlibraries/reflect/build/libs/kotlin-reflect-${バージョン}-SNAPSHOT-sources.jarに出力される。 ベンチマーク等を取る時はこの辺りを弄る。

OpenAPI Generatorで、何でも入れてOKのプロパティの値に型付け(Map<String, String>とかに)する方法

以下のように、additionalPropertiestypeを指定すると、値の型を変えられます。
例ではstringを指定しているので、Kotlinで生成した場合Map<String, String>になります。

type: object
additionalProperties: 
  type: string

type: integerにすればMap<String, Int>になりますし、type: stringのままformat: uuidにすればMap<String, UUID>になります。