【jOOQ】PostgreSQLでTIMESTAMP WITH TIME ZONEからOffsetDateTimeへの読み出しがCannot decode value of type java.lang.String with OID 1184で失敗する問題への対処【R2DBC】

# TL;DR
- `Settings.isBindOffsetDateTimeType`が`false`になっていた場合、`jOOQ`は`R2DBC`から文字列で情報を読み出し、`OffsetDateTime`へパースしようとする
- 一方、`r2dbc-postgresql`は`TIMESTAMP WITH TIME ZONE`を文字列へデコードする手段を提供していないため、読み出しが失敗する
- 読み出し処理時に`Settings.isBindOffsetDateTimeType`が`true`になるよう設定することでこの問題は解決する

# 状況
`Spring Boot` + `R2DBC` + `jOOQ`のプロジェクトにて、`H2`から`Postgres`への移行を行なっていた所、`OffsetDateTime`を読み出す処理が何も変更していないのに失敗するようになりました。
問題となった部分のスタックトレースは以下のようになっていました。

```:スタックトレース（Causedより上は省略）
Caused by: java.lang.IllegalArgumentException: Cannot decode value of type java.lang.String with OID 1184
	at io.r2dbc.postgresql.codec.DefaultCodecs.decode(DefaultCodecs.java:158) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
	at io.r2dbc.postgresql.PostgresqlRow.decode(PostgresqlRow.java:90) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
	at io.r2dbc.postgresql.PostgresqlRow.get(PostgresqlRow.java:67) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
	at org.jooq.impl.R2DBC$R2DBCResultSet$DefaultRow.get(R2DBC.java:1110) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.R2DBC$R2DBCResultSet.nullable(R2DBC.java:989) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.R2DBC$R2DBCResultSet.nullable(R2DBC.java:985) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.R2DBC$R2DBCResultSet.getString(R2DBC.java:1050) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.DefaultBinding$DefaultOffsetDateTimeBinding.get0(DefaultBinding.java:3085) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.DefaultBinding$DefaultOffsetDateTimeBinding.get0(DefaultBinding.java:3000) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.DefaultBinding$AbstractBinding.get(DefaultBinding.java:942) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.R2DBC$ResultSubscriber.lambda$onNext$0(R2DBC.java:327) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.RecordDelegate.operate(RecordDelegate.java:143) ~[jooq-3.15.1.jar:na]
	at org.jooq.impl.R2DBC$ResultSubscriber.lambda$onNext$1(R2DBC.java:313) ~[jooq-3.15.1.jar:na]
	at io.r2dbc.postgresql.PostgresqlResult.lambda$map$1(PostgresqlResult.java:111) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
```

ここで、`OID 1184`は`io.r2dbc.postgresql.type.PostgresqlObjectId.TIMESTAMPTZ`(= `TIMESTAMP WITH TIME ZONE`)を表します。
従って、このエラーメッセージは「`TIMESTAMP WITH TIME ZONE`を`java.lang.String`として読み出せなかった」ということを意味します。

# 原因
コードを追いかけた所、`org.jooq.impl.DefaultBinding.DefaultOffsetDateTimeBinding.get0`の関数が`getString`で情報を読み出していることが直接の原因でした。
実際のコードは以下の通りです。

```java:jOOQのソースより抜粋
@Override
final OffsetDateTime get0(BindingGetResultSetContext<U> ctx) throws SQLException {
  if (!FALSE.equals(ctx.settings().isBindOffsetDateTimeType()))
    return ctx.resultSet().getObject(ctx.index(), OffsetDateTime.class);
  else
    return OffsetDateTimeParser.offsetDateTime(ctx.resultSet().getString(ctx.index()));
}
```

# 対処
先ほどお見せしたコードで、`getObject`が呼ばれるようにする、つまり`Settings.isBindOffsetDateTimeType`に`true`をセットすることで対処できました。
具体的な設定方法は幾つか考えられますが、自分は`Bean`初期化時に以下のようにすることで対処しました。

```kotlin
import io.r2dbc.spi.ConnectionFactory
import org.jooq.DSLContext
import org.jooq.SQLDialect
import org.jooq.conf.Settings
import org.jooq.impl.DSL
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component

@Component
class Config(val cfi: ConnectionFactory) {
    @Bean
    fun jooqDSLContext(): DSLContext = DSL.using(
        cfi,
        SQLDialect.POSTGRES,
        Settings().apply { isBindOffsetDateTimeType = true }
    ).dsl()
}
```

この設定方法に関しては自信が無いため、より良い方法などあればご教示頂けると幸いです。