oracle / oracle-r2dbc

R2DBC Driver for Oracle Database
https://oracle.com
Other
194 stars 40 forks source link

`Statement.returnGeneratedValues()` leads to `NullPointerException` upon `INSERT` that does not generate keys #63

Closed mp911de closed 2 years ago

mp911de commented 2 years ago

With version 0.4.0, calling returnGeneratedValues() upon an insert leads to a NullPointerException. Omitting returnGeneratedValues() does not raise the exception.

DDL:

CREATE TABLE legoset (
    id          INTEGER PRIMARY KEY,
    version     INTEGER NULL,
    name        VARCHAR2(255) NOT NULL,
    manual      INTEGER NULL,
    cert        RAW(255) NULL
)

Statement:

INSERT INTO legoset (ID, NAME, MANUAL) VALUES (:P0_id, :P1_name, :P2_manual)

Code to reproduce:

ConnectionFactory connectionFactory = ConnectionFactories.get(options);

Connection connection = Mono.from(connectionFactory.create()).block();

Flux.from(connection.createStatement("DROP TABLE legoset").execute())
        .flatMap(Result::getRowsUpdated).onErrorResume(it -> Mono.empty()).blockLast();

String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
    + "    id          INTEGER PRIMARY KEY,\n" //
    + "    version     INTEGER NULL,\n" //
    + "    name        VARCHAR2(255) NOT NULL,\n" //
    + "    manual      INTEGER NULL,\n" //
    + "    cert        RAW(255) NULL\n" //
    + ")";

Flux.from(connection.createStatement(CREATE_TABLE_LEGOSET).execute())
        .flatMap(Result::getRowsUpdated).blockLast();

Flux.from(connection.createStatement("INSERT INTO legoset (ID, NAME, MANUAL) VALUES (:P0_id, :P1_name, :P2_manual)")
                .bind("P0_id", 42055)
                .bind("P1_name", "SCHAUFELRADBAGGER")
                .bind("P2_manual", 12)
                .returnGeneratedValues()
                .execute()).flatMap(Result::getRowsUpdated)
        .blockLast();

Stack trace:

java.lang.NullPointerException
    at oracle.jdbc.driver.OracleStatement.getMoreResults(OracleStatement.java:5851)
    at oracle.jdbc.driver.OracleStatementWrapper.getMoreResults(OracleStatementWrapper.java:298)
    at oracle.r2dbc.impl.OracleStatementImpl$JdbcReturningGenerated.lambda$executeJdbc$0(OracleStatementImpl.java:1582)
    at oracle.r2dbc.impl.AsyncLock.lambda$get$2(AsyncLock.java:161)
    at oracle.r2dbc.impl.AsyncLock.unlock(AsyncLock.java:122)
    at oracle.r2dbc.impl.AsyncLock$UsingConnectionSubscriber.terminate(AsyncLock.java:510)
    at oracle.r2dbc.impl.AsyncLock$UsingConnectionSubscriber.onComplete(AsyncLock.java:496)
    at reactor.core.publisher.StrictSubscriber.onComplete(StrictSubscriber.java:123)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058)
    at org.reactivestreams.FlowAdapters$FlowToReactiveSubscriber.onComplete(FlowAdapters.java:228)
    at oracle.jdbc.internal.CompletionStageUtil$IteratorSubscription.emitComplete(CompletionStageUtil.java:681)
    at oracle.jdbc.internal.CompletionStageUtil$IteratorSubscription.emitItems(CompletionStageUtil.java:628)
    at oracle.jdbc.driver.PhysicalConnection.lambda$createUserCodeExecutor$10(PhysicalConnection.java:11713)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at oracle.jdbc.driver.PhysicalConnection.lambda$createUserCodeExecutor$11(PhysicalConnection.java:11711)
    at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1426)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
        at reactor.core.publisher.Flux.blockLast(Flux.java:2645)
        at org.springframework.data.r2dbc.core.Repro.reproducer(Repro.java:86)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
        at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
        at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
Michael-A-McMahon commented 2 years ago

This looks like a bug that was present in Oracle JDBC 21.1, and was fixed for 21.3. Do you know which version is on your classpath? Our pom.xml should have it as 21.3.0.0

mp911de commented 2 years ago

Thanks for the pointer, I think I'm using an outdated Oracle JDBC version. I'll retest tomorrow and likely I'm going to close the issue then.

mp911de commented 2 years ago

Upgrading to 21.4.0.0.1 let the issue go away. Thanks for your guidance. Closing the ticket.