Skip to content

BadSqlGrammarException when attempting to use bound parameters in an on conflict clause #700

@s1mp3-a

Description

@s1mp3-a

Bug Report

Versions

  • Driver: 1.0.9.RELEASE
  • Database: 15.3.0
  • Java:
    openjdk 25.0.1 2025-10-21 LTS
    OpenJDK Runtime Environment Temurin-25.0.1+8 (build 25.0.1+8-LTS)
    OpenJDK 64-Bit Server VM Temurin-25.0.1+8 (build 25.0.1+8-LTS, mixed mode, sharing)
  • OS: Sonoma 14.2.1 (23C71)

Current Behavior

I have a project in which there is a partial unique index. My team prefers specifying the predicate of the partial index with explicit parameters and binding them in the code, so that the usages of bound parameters are visible in the IDE. We've used this approach a lot in jdbc.

When trying to execute a query with such a partial index postgres states that "there is no unique or exclusion constraint matching the ON CONFLICT specification" and i get a BadSqlGrammarException. (Most of the times but sometimes it executes with no problem)

Tried it with both postgres enums and regular string values with explicit type casts as parameters in the predicate and it fails consistently. It only seems to work when writing the on conflict clause by hand

Stack trace
org.springframework.r2dbc.BadSqlGrammarException: execute; bad SQL grammar [
            INSERT INTO test(
                uq_column, status 
            ) VALUES (
                :uqColumn, :status
            )
            ON CONFLICT (uq_column) WHERE status IN (:prepared, :inProgress)
            DO NOTHING
            ]

	at org.springframework.r2dbc.connection.ConnectionFactoryUtils.convertR2dbcException(ConnectionFactoryUtils.java:255)
	at org.springframework.r2dbc.core.DefaultDatabaseClient.lambda$inConnection$4(DefaultDatabaseClient.java:135)
	at reactor.core.publisher.Mono.lambda$onErrorMap$27(Mono.java:3769)
	at reactor.core.publisher.Mono.lambda$onErrorResume$29(Mono.java:3859)
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94)
	at reactor.core.publisher.MonoUsingWhen$MonoUsingWhenSubscriber.deferredError(MonoUsingWhen.java:277)
	at reactor.core.publisher.FluxUsingWhen$RollbackInner.onComplete(FluxUsingWhen.java:475)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2205)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2205)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:209)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:209)
	at reactor.pool.SimpleDequePool.maybeRecycleAndDrain(SimpleDequePool.java:540)
	at reactor.pool.SimpleDequePool$QueuePoolRecyclerInner.onComplete(SimpleDequePool.java:781)
	at reactor.core.publisher.Operators.complete(Operators.java:137)
	at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4496)
	at reactor.pool.SimpleDequePool$QueuePoolRecyclerMono.subscribe(SimpleDequePool.java:893)
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
	at reactor.core.publisher.Operators.complete(Operators.java:137)
	at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4496)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263)
	at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51)
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
	at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onComplete(FluxHandleFuseable.java:239)
	at reactor.core.publisher.MonoSupplier$MonoSupplierSubscription.request(MonoSupplier.java:148)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.request(FluxHandleFuseable.java:260)
	at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onSubscribe(MonoIgnoreElements.java:72)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onSubscribe(FluxHandleFuseable.java:164)
	at reactor.core.publisher.MonoSupplier.subscribe(MonoSupplier.java:48)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4496)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263)
	at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4496)
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
	at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onError(MonoIgnoreElements.java:84)
	at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:134)
	at reactor.core.publisher.FluxFilter$FilterSubscriber.onError(FluxFilter.java:157)
	at reactor.core.publisher.FluxFilter$FilterConditionalSubscriber.onError(FluxFilter.java:291)
	at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onError(FluxMap.java:265)
	at reactor.core.publisher.Operators.error(Operators.java:198)
	at reactor.core.publisher.MonoError.subscribe(MonoError.java:53)
	at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4496)
	at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onError(FluxUsingWhen.java:364)
	at reactor.core.publisher.MonoStreamCollector$StreamCollectorSubscriber.onError(MonoStreamCollector.java:149)
	at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:134)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:843)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:609)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:589)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.innerError(FluxFlatMap.java:864)
	at reactor.core.publisher.FluxFlatMap$FlatMapInner.onError(FluxFlatMap.java:991)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onError(FluxHandleFuseable.java:229)
	at reactor.core.publisher.MonoCollectList$MonoCollectListSubscriber.onError(MonoCollectList.java:108)
	at reactor.core.publisher.FluxHandle$HandleSubscriber.onError(FluxHandle.java:213)
	at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onError(MonoFlatMapMany.java:255)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:201)
	at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
	at io.r2dbc.postgresql.util.FluxDiscardOnCancel$FluxDiscardOnCancelSubscriber.onNext(FluxDiscardOnCancel.java:91)
	at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
	at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:129)
	at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:878)
	at reactor.core.publisher.FluxCreate$BufferAsyncSink.next(FluxCreate.java:803)
	at reactor.core.publisher.FluxCreate$SerializedFluxSink.next(FluxCreate.java:161)
	at io.r2dbc.postgresql.client.ReactorNettyClient$Conversation.emit(ReactorNettyClient.java:699)
	at io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.emit(ReactorNettyClient.java:951)
	at io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:825)
	at io.r2dbc.postgresql.client.ReactorNettyClient$BackendMessageSubscriber.onNext(ReactorNettyClient.java:731)
	at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:129)
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
	at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
	at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
	at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:294)
	at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:403)
	at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:425)
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:333)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:455)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)
	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:1474)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:103)
		at reactor.core.publisher.Mono.block(Mono.java:1712)
		at Test.on conflict unsafe enum biding blocking(Test.kt:87)
Caused by: io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException: [42P10] there is no unique or exclusion constraint matching the ON CONFLICT specification
	at io.r2dbc.postgresql.ExceptionFactory.createException(ExceptionFactory.java:96)
	at io.r2dbc.postgresql.ExceptionFactory.createException(ExceptionFactory.java:65)
	at io.r2dbc.postgresql.ExceptionFactory.handleErrorResponse(ExceptionFactory.java:132)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:179)
	... 45 more

Table schema

Input Code

Here is the schema and an example query that i used to reproduce the problem

CREATE TYPE status_enum AS ENUM('PREPARED', 'IN_PROGRESS', 'FINISHED', 'CANCELLED');

CREATE TABLE test(
            id BIGSERIAL NOT NULL PRIMARY KEY,
            uq_column BIGINT NOT NULL,
            status status_enum NOT NULL
);

CREATE UNIQUE INDEX test_uq_column_uq_partial_idx ON test(uq_column) WHERE status IN ('PREPARED', 'IN_PROGRESS');

This code does not work:

databaseClient.sql(
            """
            INSERT INTO test(
                uq_column, status 
            ) VALUES (
                :uqColumn, :status
            )
            ON CONFLICT (uq_column) WHERE status IN (:prepared, :inProgress)
            DO NOTHING
            """
        )
            .bind("uqColumn", 101L)
            .bind("status", StatusEnum.PREPARED)
            .bind("prepared", StatusEnum.PREPARED)
            .bind("inProgress", StatusEnum.IN_PROGRESS)
            .fetch()
            .rowsUpdated()
            .block()

This code works:

databaseClient.sql(
            """
            INSERT INTO test(
                uq_column, status 
            ) VALUES (
                :uqColumn, :status
            )
            ON CONFLICT (uq_column) WHERE status IN ('PREPARED', 'IN_PROGRESS')
            DO NOTHING
            """
        )
            .bind("uqColumn", 101L)
            .bind("status", StatusEnum.PREPARED)
            // .bind("prepared", StatusEnum.PREPARED)
            // .bind("inProgress", StatusEnum.IN_PROGRESS)
            .fetch()
            .rowsUpdated()
            .block()

Steps to reproduce

Input Code

Here is the repo with junit tests to reproduce the problem. This is the closest match in terms of dependencies to the project that i initially encountered the problem in. There are suspend tests because at first i thought that the problem is related to kotlinx-coroutines-reactor usage which it seems not to be

https://github.com/s1mp3-a/r2dbc-postgres-on-conflict-issue

Expected behavior/code

I expect the query to execute successfully instead of triggering an sql exception

Additional context

The problem seems to be inconsistent such that some times little to no "unsafe" tests would pass and other times around half of the repetitions would. I also tried bumping first the driver version to 1.1.1.RELEASE then the postgres version to 18.1 but still the problem persisted.

Looking at the postgres logs in failed tests i see that OID lookup for the enum type happens after the query error. However, that does not explain the case why binding strings with type casts fails

Logs
2026-01-26 20:16:58.218 MSK [63804] LOG:  statement: SHOW TRANSACTION ISOLATION LEVEL
2026-01-26 20:16:58.218 MSK [63802] ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification
2026-01-26 20:16:58.218 MSK [63802] CONTEXT:  portal "B_10" with parameters: $1 = '101', $2 = 'PREPARED', $3 = 'PREPARED', $4 = 'IN_PROGRESS'
2026-01-26 20:16:58.218 MSK [63802] STATEMENT:  
	            INSERT INTO test(
	                uq_column, status 
	            ) VALUES (
	                $1, $2
	            )
	            ON CONFLICT (uq_column) WHERE status IN ($3, $4)
	            DO NOTHING
	            
2026-01-26 20:16:58.218 MSK [63804] LOG:  statement: SELECT pg_type.oid, pg_type.*   FROM pg_catalog.pg_type   LEFT   JOIN (select ns.oid as nspoid, ns.nspname, r.r           from pg_namespace as ns           join ( select s.r, (current_schemas(false))[s.r] as nspname                    from generate_series(1, array_upper(current_schemas(false), 1)) as s(r) ) as r          using ( nspname )        ) as sp     ON sp.nspoid = typnamespace  WHERE typname IN ('status_enum')  ORDER BY sp.r, pg_type.oid DESC ;
2026-01-26 20:16:58.223 MSK [63804] LOG:  statement: SELECT oid, * FROM pg_catalog.pg_type WHERE typname IN ('hstore','geometry','vector')

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions