Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions storm-core/src/main/java/st/orm/core/template/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -278,27 +278,44 @@ default <T extends Data> List<Ref<T>> getRefList(@Nonnull Class<T> type, @Nonnul
*/
private <T> T singleResult(@Nonnull Stream<T> stream) {
try (stream) {
return stream
.reduce((a, b) -> {
throw new NonUniqueResultException("Expected single result, but found more than one.");
}).orElseThrow(() -> new NoResultException("Expected single result, but found none."));
var iterator = stream.iterator();
if (!iterator.hasNext()) {
throw new NoResultException("Expected single result, but found none.");
}
T result = iterator.next();
if (iterator.hasNext()) {
throw new NonUniqueResultException("Expected single result, but found more than one.");
}
if (result == null) {
throw new PersistenceException("Expected single result, but found null. Wrap the field in COALESCE() to provide a non-null default.");
}
return result;
}
}

/**
* Returns the single result of the stream, or an empty optional if there is no result.
*
* @param stream the stream to get the single result from.
* @return the single result of the stream.
* @param <T> the type of the result.
* @return the single result of the stream.
* @throws NonUniqueResultException if more than one result.
* @throws PersistenceException if the single row's value is null.
*/
private <T> Optional<T> optionalResult(@Nonnull Stream<T> stream) {
try (stream) {
return stream
.reduce((a, b) -> {
throw new NonUniqueResultException("Expected single result, but found more than one.");
});
var iterator = stream.iterator();
if (!iterator.hasNext()) {
return Optional.empty();
}
T result = iterator.next();
if (iterator.hasNext()) {
throw new NonUniqueResultException("Expected single result, but found more than one.");
}
if (result == null) {
throw new PersistenceException("Result is null. Wrap the field in COALESCE() to provide a non-null default.");
}
return Optional.of(result);
}
}
}
35 changes: 26 additions & 9 deletions storm-core/src/main/java/st/orm/core/template/QueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -946,29 +946,46 @@ public final List<R> getResultList() {
* @return the single result.
* @throws NoResultException if there is no result.
* @throws NonUniqueResultException if more than one result.
* @throws PersistenceException if the query fails.
* @throws PersistenceException if the single row's value is null, or the query fails.
*/
public final R getSingleResult() {
try (var stream = getResultStream()) {
return stream
.reduce((a, b) -> {
throw new NonUniqueResultException("Expected single result, but found more than one.");
}).orElseThrow(() -> new NoResultException("Expected single result, but found none."));
var iterator = stream.iterator();
if (!iterator.hasNext()) {
throw new NoResultException("Expected single result, but found none.");
}
R result = iterator.next();
if (iterator.hasNext()) {
throw new NonUniqueResultException("Expected single result, but found more than one.");
}
if (result == null) {
throw new PersistenceException("Expected single result, but found null. Wrap the field in COALESCE() to provide a non-null default.");
}
return result;
}
}

/**
* Executes the query and returns an optional result.
*
* @return the optional result.
* @return the optional result; {@link Optional#empty()} when no row matched.
* @throws NonUniqueResultException if more than one result.
* @throws PersistenceException if the query fails.
* @throws PersistenceException if the single row's value is null, or the query fails.
*/
public final Optional<R> getOptionalResult() {
try (var stream = getResultStream()) {
return stream.reduce((a, b) -> {
var iterator = stream.iterator();
if (!iterator.hasNext()) {
return Optional.empty();
}
R result = iterator.next();
if (iterator.hasNext()) {
throw new NonUniqueResultException("Expected single result, but found more than one.");
});
}
if (result == null) {
throw new PersistenceException("Result is null. Wrap the field in COALESCE() to provide a non-null default.");
}
return Optional.of(result);
}
}

Expand Down
46 changes: 33 additions & 13 deletions storm-kotlin/src/main/kotlin/st/orm/template/Query.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import kotlinx.coroutines.stream.consumeAsFlow
import st.orm.Data
import st.orm.NoResultException
import st.orm.NonUniqueResultException
import st.orm.PersistenceException
import st.orm.Ref
import java.util.function.Supplier
import java.util.stream.Stream
import kotlin.reflect.KClass

Expand Down Expand Up @@ -318,29 +318,49 @@ interface Query {
*/
private fun <T> singleResult(stream: Stream<T>): T {
stream.use {
return stream
.reduce { _, _ ->
throw NonUniqueResultException("Expected single result, but found more than one.")
}
.orElseThrow(Supplier { NoResultException("Expected single result, but found none.") })
val iterator = stream.iterator()
if (!iterator.hasNext()) {
throw NoResultException("Expected single result, but found none.")
}
val result = iterator.next()
if (iterator.hasNext()) {
throw NonUniqueResultException("Expected single result, but found more than one.")
}
if (result == null) {
throw PersistenceException("Expected single result, but found null. Wrap the field in COALESCE() to provide a non-null default.")
}
return result
}
}

/**
* Returns the single result of the stream, or an empty optional if there is no result.
* Returns the single result of the stream, or `null` if there is no result.
*
* Iterates the stream explicitly rather than using [Stream.reduce] — the standard reduce internally
* calls `Optional.of(element)`, which throws a message-less [NullPointerException] when the only
* element is `null`. The iterator form lets the method detect that case and report it via a typed
* [PersistenceException] with a clear message.
*
* @param stream the stream to get the single result from.
* @return the single result of the stream.
* @param <T> the type of the result.
* @return the single result of the stream, or `null` when no row matched.
* @throws NonUniqueResultException if more than one result.
* @throws PersistenceException if the single row's value is SQL NULL.
*/
private fun <T> optionalResult(stream: Stream<T>): T? {
stream.use {
return stream
.reduce { _, _ ->
throw NonUniqueResultException("Expected single result, but found more than one.")
}
.orElse(null)
val iterator = stream.iterator()
if (!iterator.hasNext()) {
return null
}
val result = iterator.next()
if (iterator.hasNext()) {
throw NonUniqueResultException("Expected single result, but found more than one.")
}
if (result == null) {
throw PersistenceException("Result is null. Wrap the field in COALESCE() to provide a non-null default.")
}
return result
}
}
}
36 changes: 25 additions & 11 deletions storm-kotlin/src/main/kotlin/st/orm/template/QueryBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import st.orm.template.TemplateString.Companion.raw
import st.orm.template.TemplateString.Companion.wrap
import st.orm.template.impl.create
import st.orm.template.impl.createRef
import java.util.function.Supplier
import java.util.stream.Stream
import kotlin.reflect.KClass

Expand Down Expand Up @@ -1015,32 +1014,47 @@ interface QueryBuilder<T : Data, R, ID> {
* @return the single result.
* @throws NoResultException if there is no result.
* @throws NonUniqueResultException if more than one result.
* @throws PersistenceException if the query fails.
* @throws PersistenceException if the single row's value is null, or the query fails.
*/
get() {
resultStream.use { stream ->
return stream
.reduce { _, _ ->
throw NonUniqueResultException("Expected single result, but found more than one.")
}
.orElseThrow(Supplier { NoResultException("Expected single result, but found none.") })
val iterator = stream.iterator()
if (!iterator.hasNext()) {
throw NoResultException("Expected single result, but found none.")
}
val result = iterator.next()
if (iterator.hasNext()) {
throw NonUniqueResultException("Expected single result, but found more than one.")
}
if (result == null) {
throw PersistenceException("Expected single result, but found null. Wrap the field in COALESCE() to provide a non-null default.")
}
return result
}
}

val optionalResult: R?
/**
* Executes the query and returns an optional result.
*
* @return the optional result.
* @return the optional result; `null` when no row matched.
* @throws NonUniqueResultException if more than one result.
* @throws PersistenceException if the query fails.
* @throws PersistenceException if the single row's value is null, or the query fails.
*/
get() {
resultStream.use { stream ->
return stream.reduce { _, _ ->
val iterator = stream.iterator()
if (!iterator.hasNext()) {
return null
}
val result = iterator.next()
if (iterator.hasNext()) {
throw NonUniqueResultException("Expected single result, but found more than one.")
}
.orElse(null)
if (result == null) {
throw PersistenceException("Result is null. Wrap the field in COALESCE() to provide a non-null default.")
}
return result
}
}

Expand Down
Loading