Calling `bind` on a `SET` statement raises a `syntax error`.
k163377 opened this issue · comments
Bug Report
If bind
to a SET
statement, the placeholder will not be properly processed and will result in a syntax error
.
Versions
- Driver: org.postgresql:postgresql -> 42.3.6
- I have confirmed that the same results are obtained with
org.postgresql:r2dbc-postgresql
andio.r2dbc:r2dbc-spi
0.9.1 and 1.0.0.
- I have confirmed that the same results are obtained with
- Database:
postgresql 13
- Java: 11
- OS: mac OS
Current Behavior
Describe the details in Steps to reproduce
.
Table schema
I think it can be reproduced without a table.
Steps to reproduce
I have created a test that looks like the following test.
f1
and f2
are the code that reproduces the problem.
It appears to contain a dependency on the SpringFramework
, but this dependency can be removed as long as there is a connectable URL
.
Input Code
import io.r2dbc.postgresql.PostgresqlConnectionFactory
import io.r2dbc.spi.ConnectionFactories
import kotlinx.coroutines.reactive.awaitSingle
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class BindToSetQueryTest @Autowired constructor(r2dbcProperties: R2dbcProperties) {
val cf = ConnectionFactories.get(r2dbcProperties.url) as PostgresqlConnectionFactory
// Direct embedding
@Test
fun t1() = runTest {
val con = cf.create().awaitSingle()
con.createStatement("set app.current_tenant = 'foo'").execute().awaitSingle()
val appCurrentTenant = con.createStatement("select current_setting('app.current_tenant'::text)")
.execute()
.flatMap { it.map { t, _ -> t.get(0) } }
.awaitSingle()
assertEquals("foo", appCurrentTenant)
}
// Select bound value
@Test
fun t2() = runTest {
val con = cf.create().awaitSingle()
val boundValue = con.createStatement("select $1").bind(0, "foo")
.execute()
.flatMap { it.map { t, _ -> t.get(0) } }
.awaitSingle()
assertEquals("foo", boundValue)
}
@Test
fun f1() = runTest {
val con = cf.create().awaitSingle()
con.createStatement("set app.current_tenant = $1").bind(0, "foo").execute().awaitSingle()
val appCurrentTenant = con.createStatement("select current_setting('app.current_tenant'::text)")
.execute()
.flatMap { it.map { t, _ -> t.get(0) } }
.awaitSingle()
assertEquals("foo", appCurrentTenant)
}
@Test
fun f2() = runTest {
val con = cf.create().awaitSingle()
con.createStatement("set app.current_tenant = $1").bind(0, "foo")
.execute()
.flatMap { it.map { t, _ -> t.metadata } } // The process by which the query is to be executed.
.awaitSingle()
}
}
f1
will give an error unrecognized configuration parameter "app.current_tenant"
.
Stack trace for f1
unrecognized configuration parameter "app.current_tenant"
io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException: [42704] unrecognized configuration parameter "app.current_tenant"
at app//io.r2dbc.postgresql.ExceptionFactory.createException(ExceptionFactory.java:96)
at app//io.r2dbc.postgresql.ExceptionFactory.createException(ExceptionFactory.java:65)
at app//io.r2dbc.postgresql.ExceptionFactory.handleErrorResponse(ExceptionFactory.java:132)
at app//io.r2dbc.postgresql.PostgresqlResult.lambda$map$2(PostgresqlResult.java:112)
at app//reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:176)
at app//reactor.core.publisher.FluxWindowPredicate$WindowFlux.drainRegular(FluxWindowPredicate.java:668)
at app//reactor.core.publisher.FluxWindowPredicate$WindowFlux.drain(FluxWindowPredicate.java:746)
at app//reactor.core.publisher.FluxWindowPredicate$WindowFlux.onNext(FluxWindowPredicate.java:788)
at app//reactor.core.publisher.FluxWindowPredicate$WindowPredicateMain.onNext(FluxWindowPredicate.java:239)
at app//io.r2dbc.postgresql.util.FluxDiscardOnCancel$FluxDiscardOnCancelSubscriber.onNext(FluxDiscardOnCancel.java:91)
at app//reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at app//reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:814)
at app//reactor.core.publisher.FluxCreate$BufferAsyncSink.next(FluxCreate.java:739)
at app//reactor.core.publisher.FluxCreate$SerializedFluxSink.next(FluxCreate.java:161)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$Conversation.emit(ReactorNettyClient.java:687)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.emit(ReactorNettyClient.java:939)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:813)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:719)
at app//reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:126)
at app//reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
at app//reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
at app//reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
at app//reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:279)
at app//reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:388)
at app//reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:404)
at app//reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at app//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at app//io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:327)
at app//io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:299)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at app//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at app//io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at app//io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at app//io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at app//io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:722)
at app//io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
at app//io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@11.0.16.1/java.lang.Thread.run(Thread.java:829)
f2
will give an error syntax error at or near "$1"
.
Stack trace for f2
syntax error at or near "$1"
io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException: [42601] syntax error at or near "$1"
at app//io.r2dbc.postgresql.ExceptionFactory.createException(ExceptionFactory.java:96)
at app//io.r2dbc.postgresql.ExceptionFactory.createException(ExceptionFactory.java:65)
at app//io.r2dbc.postgresql.ExceptionFactory.handleErrorResponse(ExceptionFactory.java:132)
at app//reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:176)
at app//reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
at app//reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at app//reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
at app//reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
at app//io.r2dbc.postgresql.util.FluxDiscardOnCancel$FluxDiscardOnCancelSubscriber.onNext(FluxDiscardOnCancel.java:91)
at app//reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
at app//reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:126)
at app//reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:814)
at app//reactor.core.publisher.FluxCreate$BufferAsyncSink.next(FluxCreate.java:739)
at app//reactor.core.publisher.FluxCreate$SerializedFluxSink.next(FluxCreate.java:161)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$Conversation.emit(ReactorNettyClient.java:687)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.emit(ReactorNettyClient.java:939)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:813)
at app//io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:719)
at app//reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:126)
at app//reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
at app//reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
at app//reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
at app//reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:279)
at app//reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:388)
at app//reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:404)
at app//reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at app//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at app//io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:327)
at app//io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:314)
at app//io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:435)
at app//io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:279)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at app//io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at app//io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at app//io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at app//io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at app//io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at app//io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:722)
at app//io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
at app//io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@11.0.16.1/java.lang.Thread.run(Thread.java:829)
Expected behavior/code
As far as the results for t1
and t2
are concerned, it is correct that f1
and f2
are not errors.
Also, for f1
, I think it is correct that it should be a syntax error at or near "$1"
(although this may be a separate issue).
Possible Solution
The r2dbc-postgresql
does not expose the escaping process, and I don't think there is a workaround.
I encountered this problem in the postAllocate
process of r2dbc-pool
and finally settled on using jOOQ
to generate a query with embedded parameters.
Additional context
The r2dbc-postgresql does not expose the escaping process,
R2DBC drivers generally do not rewrite not escape any provided SQL. Any error message concerning syntax or grammar that are reported by your Postgres server are caused by SQL input that isn't working.
I am able to reproduce the exact same result using JDBC:
org.postgresql.util.PSQLException: ERROR: syntax error at or near "$1"
Position: 26
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2676)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2366)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:356)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:496)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:413)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:190)
Code to reproduce:
JdbcOperations ops = …
ops.execute("set app.current_tenant = 'foo'");
ops.update("set app.current_tenant = ?", "foo"); // reproducer fails here