/var/log/jsoizo

メモ帳 技術とか趣味とか

jOOQのRecordから値を取得するときのnullableをarrowのoptionで綺麗に取り扱う

jOOQの Record.get(field) は以下のようなシグネチャでnullableである。

 <T> T get(Field<T> field) throws IllegalArgumentException;

nullableな処理が一つなら良いが、複数フィールドを取得したい場合などは以下のようなコードを書くことがある。
TODOS テーブルの各フィールドにはNOT NULL制約がついているものとする。

val query = create.select().from(TODOS)

val todos = query.fetch().map { record ->
    ToDo(
        id = record.get(TODOS.ID)!!,
        title = record.get(TODOS.TITLE)!!,
        body = record.get(TODOS.BODY)!!,
        created_at = record.get(TODOS.CREATED_AT)!!,
        modified_at. = record.get(TODOS.CREATED_AT)!!
    )
}

この例は、各フィールドに対してNOT NULL制約がついているので !! で強制的に値を読み取ったとしてもNullPointerExceptionが発生することはないだろう。だが、たとえば left outer join をした右側のテーブルに対してこれを実行した場合はNullPointerExceptionが発生してしまう。ゆえに極力 !! を使うことは避けて通りたい。
また、jOOQ code generatorなどで生成したTableRecord型のクラスについても、フィールドにNOT NULL制約がついていたとしても生成されるTableRecordのフィールドはすべてnullableとなってしまう。*1

これに対しarrow coreのoptionを利用すると若干の記述量の増加だけでこれを多少改善することができる。
option型自体の説明は↓から

jsoizo.hatenablog.com

val todos: List<ToDo> = query.fetch().mapNotNull { record ->
    runBlocking {
        option {
            ToDo(
                id = record.get(TODOS.ID).toOption().bind(),
                title = record.get(TODOS.TITLE).toOption().bind(),
                body = record.get(TODOS.BODY).toOption().bind(),
                created_at = record.get(TODOS.CREATED_AT).toOption().bind(),
                modified_at. = record.get(TODOS.CREATED_AT).toOption().bind()
            )
        }.orNull()
    }
}

arrowにて nullableをOptionに変換する <T> T?.toOption(): Option<T> と、Optionの合成を行う関数が定義されている。それを用いると、ToDo型のインスタンス作成時にフィールドに1つでもnullがある場合にはToDoオブジェクトは生成せずにNoneとすることができる。
その後 .orNull() によってOptionをnullableに戻してたうえで、さらにfetch()した結果に対してmapNotNullしているので、最終的にはnullのないListが返却されることになる。

毎度 toOption() するのがだるかったら以下のような拡張関数を定義しておくと良いかもしれない。

fun <T> Record.getOrNone(field: Field<T>): Option<T> = this.get(field).toOption()

TableRecordを使っている場合はどうしたらいいかわからん。
code generatorの拡張みたいなことをすればいいのかな?できるかもわからないけど。。。

ダラダラ書いてしまったが要するにJavaのOptionalやScalaのOptionライクな挙動にすればスッキリすることもあるね〜というだけの話。ただKotlinに慣れていないだけでいい感じにできるのかもしれない。。。

*1:jOOQ 3.18のcode generatorにはこのあたりを改善するオプションが追加される? Add options to generate non-null attributes on Records, Pojos, and interfaces in KotlinGenerator · Issue #10212 · jOOQ/jOOQ · GitHub