mirromutth/r2dbc-mysql

Cannot convert between numeric types when fetching a value

lukaseder opened this issue · 7 comments

This query currently fails:

System.out.println(
Flux.from(cf.create())
    .flatMap(c -> Mono.from(c.createStatement("select 1").execute()))
    .flatMap(it -> it.map((r, m) -> r.get(0, Byte.class)))
    .blockFirst()
);

The exception being:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot decode value of type class java.lang.Byte for 8 with collation 63
	at dev.miku.r2dbc.mysql.codec.DefaultCodecs.decodeNormal(DefaultCodecs.java:212)
	at dev.miku.r2dbc.mysql.codec.DefaultCodecs.decode(DefaultCodecs.java:105)
	at dev.miku.r2dbc.mysql.MySqlRow.get(MySqlRow.java:59)
	at org.jooq.testscripts.R2DBC.lambda$2(R2DBC.java:28)
	at dev.miku.r2dbc.mysql.MySqlResult.processRow(MySqlResult.java:176)
	at dev.miku.r2dbc.mysql.MySqlResult.handleResult(MySqlResult.java:149)
	at dev.miku.r2dbc.mysql.MySqlResult.lambda$map$1(MySqlResult.java:93)
	at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:102)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
	at dev.miku.r2dbc.mysql.util.DiscardOnCancelSubscriber.onNext(DiscardOnCancelSubscriber.java:70)
	at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drainRegular(FluxWindowPredicate.java:657)
	at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drain(FluxWindowPredicate.java:735)
	at reactor.core.publisher.FluxWindowPredicate$WindowFlux.onNext(FluxWindowPredicate.java:777)
	at reactor.core.publisher.FluxWindowPredicate$WindowPredicateMain.onNext(FluxWindowPredicate.java:255)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:184)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
	at dev.miku.r2dbc.mysql.util.DiscardOnCancelSubscriber.onNext(DiscardOnCancelSubscriber.java:70)
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
	at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:250)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:199)
	at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:118)
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
	at reactor.core.publisher.EmitterProcessor.drain(EmitterProcessor.java:491)
	at reactor.core.publisher.EmitterProcessor.tryEmitNext(EmitterProcessor.java:299)
	at reactor.core.publisher.InternalManySink.emitNext(InternalManySink.java:27)
	at reactor.core.publisher.EmitterProcessor.onNext(EmitterProcessor.java:265)
	at dev.miku.r2dbc.mysql.client.ReactorNettyClient$ResponseSink.next(ReactorNettyClient.java:340)
	at dev.miku.r2dbc.mysql.client.ReactorNettyClient.lambda$new$0(ReactorNettyClient.java:103)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:184)
	at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:265)
	at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:371)
	at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:358)
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:96)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at dev.miku.r2dbc.mysql.client.MessageDuplexCodec.handleDecoded(MessageDuplexCodec.java:187)
	at dev.miku.r2dbc.mysql.client.MessageDuplexCodec.channelRead(MessageDuplexCodec.java:95)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:432)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1533)
	at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1282)
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1329)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:834)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
		at reactor.core.publisher.Flux.blockFirst(Flux.java:2474)
		at org.jooq.testscripts.R2DBC.main(R2DBC.java:29)

See also: r2dbc/r2dbc-h2#190

had the same issue with fetching integers from stored procedure select output

public Mono<CodeResponseDto> generateCode(String phone) {
        return databaseClient.sql("call myschema.generate_code("+phone+")")
                //.bind("phone", phone)
                .map(QueryMappers.CODE_RESP_MAPPER).one();
                
    }

mapper

public static final BiFunction<Row, RowMetadata, CodeResponseDto> CODE_RESP_MAPPER = (row, rowMetaData) -> CodeResponseDto.builder()
            .res(row.get("res",Integer.class))
            .description(row.get("description", String.class))
            .v_code(row.get("v_code", String.class)).build();

exception

java.lang.IllegalArgumentException: Cannot decode value of type class java.lang.Integer for 8 with collation 63
	at dev.miku.r2dbc.mysql.codec.DefaultCodecs.decodeNormal(DefaultCodecs.java:212) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ Handler com.bet.api.controllers.AuthController#generateCode(String) [DispatcherHandler]
	|_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
	|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
	|_ checkpoint ⇢ HTTP GET "/auth/generate-code/254725063659" [ExceptionHandlingWebHandler]
Stack trace:
		at dev.miku.r2dbc.mysql.codec.DefaultCodecs.decodeNormal(DefaultCodecs.java:212) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at dev.miku.r2dbc.mysql.codec.DefaultCodecs.decode(DefaultCodecs.java:105) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at dev.miku.r2dbc.mysql.MySqlRow.get(MySqlRow.java:67) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at com.bet.api.model.dto.QueryMappers.lambda$static$0(QueryMappers.java:26) ~[classes/:na]
		at dev.miku.r2dbc.mysql.MySqlResult.processRow(MySqlResult.java:176) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at dev.miku.r2dbc.mysql.MySqlResult.handleResult(MySqlResult.java:149) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at dev.miku.r2dbc.mysql.MySqlResult.lambda$map$1(MySqlResult.java:93) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:102) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.4.6.jar:3.4.6]
		at dev.miku.r2dbc.mysql.util.DiscardOnCancelSubscriber.onNext(DiscardOnCancelSubscriber.java:70) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drainRegular(FluxWindowPredicate.java:667) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drain(FluxWindowPredicate.java:745) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxWindowPredicate$WindowFlux.onNext(FluxWindowPredicate.java:787) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxWindowPredicate$WindowPredicateMain.onNext(FluxWindowPredicate.java:265) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:184) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.4.6.jar:3.4.6]
		at dev.miku.r2dbc.mysql.util.DiscardOnCancelSubscriber.onNext(DiscardOnCancelSubscriber.java:70) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:250) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:199) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:118) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.EmitterProcessor.drain(EmitterProcessor.java:491) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.EmitterProcessor.tryEmitNext(EmitterProcessor.java:299) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.InternalManySink.emitNext(InternalManySink.java:27) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.core.publisher.EmitterProcessor.onNext(EmitterProcessor.java:265) ~[reactor-core-3.4.6.jar:3.4.6]
		at dev.miku.r2dbc.mysql.client.ReactorNettyClient$ResponseSink.next(ReactorNettyClient.java:340) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at dev.miku.r2dbc.mysql.client.ReactorNettyClient.lambda$new$0(ReactorNettyClient.java:103) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:184) ~[reactor-core-3.4.6.jar:3.4.6]
		at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:280) ~[reactor-netty-core-1.0.7.jar:1.0.7]
		at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:389) ~[reactor-netty-core-1.0.7.jar:1.0.7]
		at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:401) ~[reactor-netty-core-1.0.7.jar:1.0.7]
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94) ~[reactor-netty-core-1.0.7.jar:1.0.7]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at dev.miku.r2dbc.mysql.client.MessageDuplexCodec.handleDecoded(MessageDuplexCodec.java:187) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at dev.miku.r2dbc.mysql.client.MessageDuplexCodec.channelRead(MessageDuplexCodec.java:95) ~[r2dbc-mysql-0.8.2.RELEASE.jar:0.8.2.RELEASE]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) ~[netty-codec-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311) ~[netty-codec-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:432) ~[netty-codec-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795) ~[netty-transport-native-epoll-4.1.65.Final-linux-x86_64.jar:4.1.65.Final]
		at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480) ~[netty-transport-native-epoll-4.1.65.Final-linux-x86_64.jar:4.1.65.Final]
		at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378) ~[netty-transport-native-epoll-4.1.65.Final-linux-x86_64.jar:4.1.65.Final]
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.65.Final.jar:4.1.65.Final]
		at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

however I found a workaround and it worked hopefully guys you will look into this issue and provide a fix
the workaround:

Integer.valueOf(row.get("res", Object.class).toString())

Generally speaking, numeric conversions make only sense if the returned data type is already numeric. Parsing e.g. VARCHAR into a number is not desired.

just for clarity, are you saying select 1 does not return 1 as numeric?

No, not necessarily. It's just to clarify that we should not expect conversion across incompatible base types (character types to numeric, character types to dates, and such).

No, not necessarily. It's just to clarify that we should not expect conversion across incompatible base types (character types to numeric, character types to dates, and such).

Why not? ISO/IEC 9075-2:2016(E) 6.13 <cast specification> specifies a wide variety of conversions that should be supported from character types to/from others, including numeric, temporal, boolean.

I've always found that very convenient in JDBC, and I'm pretty sure users will expect this convenience from R2DBC as well.

A broad brush of cross-type conversion an aspect targetting all drivers. We avoided from the start requiring all drivers to do the exact same work. I'd rather suggest having a layer that would take care of this. R2DBC proxy is a good place to place such cross-cutting concerns in a single place.

Hi there,

Great thanks for your suggestion. Apologies for the late reply.

I'm also considering this, let me explain the conversion failure exception:

The query SELECT 1 will return a value as an INT type, so it will be failed if user want decode it as a Byte, and it will be succeeded if decode it as a Long or BigInteger. My initial thought was: make it look like Java automatic type conversion.

But, yes, it seems like make confuse to user: I call the method with parameter Byte.class exactly, so it should be an explicit conversion. Just like byte byteValue = (byte) intValue, and it will not consider data overflow.

In addtion, just like @mp911de said, the driver layer should not consider a broad brush of cross-type conversion.

I'm following up on this ticket and it will be resolved soon.