-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve R2DBC result handling (#1076)
Fixes #1019
- Loading branch information
Showing
30 changed files
with
699 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
data-model/src/main/java/io/micronaut/data/exceptions/NonUniqueResultException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright 2017-2020 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.data.exceptions; | ||
|
||
/** | ||
* The exception represents the error state when one result has been requested by data layer returned multiple results. | ||
* | ||
* @author Denis Stepanov | ||
* @since 2.4.7 | ||
*/ | ||
public class NonUniqueResultException extends DataAccessException { | ||
|
||
public NonUniqueResultException() { | ||
super("Query did not return a unique result"); | ||
} | ||
|
||
public NonUniqueResultException(String message) { | ||
super(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 62 additions & 41 deletions
103
...bc/src/main/java/io/micronaut/data/r2dbc/operations/DefaultR2dbcRepositoryOperations.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/PlainR2dbcSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/* | ||
* Copyright 2017-2020 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.data.r2dbc | ||
|
||
import io.micronaut.context.ApplicationContext | ||
import io.micronaut.data.tck.entities.Author | ||
import io.micronaut.data.tck.repositories.AuthorRepository | ||
import io.micronaut.transaction.reactive.ReactiveTransactionOperations | ||
import io.r2dbc.spi.Connection | ||
import io.r2dbc.spi.ConnectionFactory | ||
import reactor.core.publisher.Flux | ||
import reactor.core.publisher.Mono | ||
import spock.lang.AutoCleanup | ||
import spock.lang.Shared | ||
import spock.lang.Specification | ||
|
||
abstract class PlainR2dbcSpec extends Specification { | ||
|
||
@AutoCleanup | ||
@Shared | ||
ApplicationContext context = ApplicationContext.run(properties) | ||
|
||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory) | ||
|
||
ReactiveTransactionOperations<Connection> reactiveTransactionOperations = context.getBean(ReactiveTransactionOperations) | ||
|
||
protected abstract AuthorRepository getAuthorRepository() | ||
|
||
protected String getInsertQuery() { | ||
'INSERT INTO author (name) VALUES ($1)' | ||
} | ||
|
||
def "save one"() { | ||
when: | ||
def author = new Author(name: "Denis") | ||
def result = Flux.usingWhen(connectionFactory.create(), connection -> { | ||
return Flux.usingWhen(Mono.from(connection.beginTransaction()).then(Mono.just(connection)), | ||
(b) -> { | ||
return Flux.from(connection.createStatement(getInsertQuery()) | ||
.bind(0, author.getName()) | ||
.execute()).flatMap { r -> Mono.from(r.getRowsUpdated()) }; | ||
}, | ||
(b) -> connection.commitTransaction(), | ||
(b, throwable) -> connection.rollbackTransaction(), | ||
(b) -> connection.commitTransaction()) | ||
|
||
}, { it -> it.close() }) | ||
.collectList() | ||
.block() | ||
then: | ||
result.size() == 1 | ||
result[0] == 1 | ||
authorRepository.findByNameContains("Denis").size() == 1 | ||
} | ||
|
||
def "save one - convert flux to mono"() { | ||
when: | ||
def author = new Author(name: "Zed") | ||
def result = Mono.from(Flux.usingWhen(connectionFactory.create(), connection -> { | ||
return Flux.usingWhen(Mono.from(connection.beginTransaction()).then(Mono.just(connection)), | ||
(b) -> { | ||
return Flux.from(connection.createStatement(getInsertQuery()) | ||
.bind(0, author.getName()) | ||
.execute()).flatMap { r -> Mono.from(r.getRowsUpdated()) }; | ||
}, | ||
(b) -> connection.commitTransaction(), | ||
(b, throwable) -> connection.rollbackTransaction(), | ||
(b) -> connection.commitTransaction()) | ||
|
||
}, { it -> it.close() }) | ||
).block() | ||
then: | ||
result == 1 | ||
if (isFailsRandomlyWhenConvertingFluxToMono()) { | ||
true | ||
} else { | ||
authorRepository.findByNameContains("Zed").size() == (isFailsWhenConvertingFluxToMono() ? 0 : 1) | ||
} | ||
} | ||
|
||
def "save one - reactiveTransactionOperations"() { | ||
when: | ||
def author = new Author(name: "John") | ||
def result = Flux.from(reactiveTransactionOperations.withTransaction { status -> | ||
return Flux.from(status.connection.createStatement(getInsertQuery()) | ||
.bind(0, author.getName()) | ||
.execute()).flatMap { r -> Mono.from(r.getRowsUpdated()) }; | ||
}) | ||
.collectList() | ||
.block() | ||
then: | ||
result.size() == 1 | ||
result[0] == 1 | ||
authorRepository.findByNameContains("John").size() == 1 | ||
} | ||
|
||
def "save one - reactiveTransactionOperations - convert flux to mono"() { | ||
when: | ||
def author = new Author(name: "Josh") | ||
def result = Mono.from(reactiveTransactionOperations.withTransaction { status -> | ||
return Flux.from(status.connection.createStatement(getInsertQuery()) | ||
.bind(0, author.getName()) | ||
.execute()).flatMap { r -> Mono.from(r.getRowsUpdated()) }; | ||
}).block() | ||
then: | ||
result == 1 | ||
if (isFailsRandomlyWhenConvertingFluxToMono()) { | ||
true | ||
} else { | ||
authorRepository.findByNameContains("Josh").size() == (isFailsWhenConvertingFluxToMono() ? 0 : 1) | ||
} | ||
} | ||
|
||
boolean isFailsWhenConvertingFluxToMono() { | ||
// Override if the driver is not correctly handling `cancel` when Flux is converted to Mono | ||
return false | ||
} | ||
|
||
boolean isFailsRandomlyWhenConvertingFluxToMono() { | ||
return false | ||
} | ||
|
||
def "save one - without `getRowsUpdated` call nothing is saved"() { | ||
when: | ||
def author = new Author(name: "Fred") | ||
Flux.usingWhen(connectionFactory.create(), connection -> { | ||
return Flux.usingWhen(Mono.from(connection.beginTransaction()).then(Mono.just(connection)), | ||
(b) -> { | ||
return Flux.from(connection.createStatement(getInsertQuery()) | ||
.bind(0, author.getName()) | ||
.execute()).flatMap { r -> Mono.just(1) }; | ||
}, | ||
(b) -> connection.commitTransaction(), | ||
(b, throwable) -> connection.rollbackTransaction(), | ||
(b) -> connection.commitTransaction()) | ||
|
||
}, { it -> it.close() }) | ||
.collectList() | ||
.block() | ||
then: | ||
authorRepository.findByNameContains("Fred").size() == (isFailsToInsertWithoutGetRowsUpdatedCall() ? 0 : 1) | ||
} | ||
|
||
boolean isFailsToInsertWithoutGetRowsUpdatedCall() { | ||
// Override if the driver is not inserting a record without `getRowsUpdated` call | ||
return false | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/h2/H2PlainR2dbcSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* Copyright 2017-2020 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.data.r2dbc.h2 | ||
|
||
import groovy.transform.Memoized | ||
import io.micronaut.data.r2dbc.PlainR2dbcSpec | ||
|
||
class H2PlainR2dbcSpec extends PlainR2dbcSpec implements H2TestPropertyProvider { | ||
|
||
@Memoized | ||
@Override | ||
H2AuthorRepository getAuthorRepository() { | ||
return context.getBean(H2AuthorRepository) | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
data-r2dbc/src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbPlainR2dbcSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright 2017-2020 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.data.r2dbc.mariadb | ||
|
||
import groovy.transform.Memoized | ||
import io.micronaut.data.r2dbc.PlainR2dbcSpec | ||
import io.micronaut.data.r2dbc.mysql.MySqlAuthorRepository | ||
import io.micronaut.data.tck.repositories.AuthorRepository | ||
|
||
class MariaDbPlainR2dbcSpec extends PlainR2dbcSpec implements MariaDbTestPropertyProvider { | ||
|
||
@Memoized | ||
@Override | ||
AuthorRepository getAuthorRepository() { | ||
return context.getBean(MySqlAuthorRepository) | ||
} | ||
|
||
@Override | ||
String getInsertQuery() { | ||
return 'INSERT INTO author (name) VALUES (?)' | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
.../src/test/groovy/io/micronaut/data/r2dbc/mariadb/MariaDbReactiveRepositoryPoolSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright 2017-2020 original authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micronaut.data.r2dbc.mariadb | ||
|
||
class MariaDbReactiveRepositoryPoolSpec extends MariaDbReactiveRepositorySpec { | ||
|
||
@Override | ||
boolean usePool() { | ||
return true | ||
} | ||
} |
Oops, something went wrong.