Skip to content

Commit

Permalink
Add nextOrThrow methods to KiwiJdbc to advance a ResultSet (#1077)
Browse files Browse the repository at this point in the history
* Add three overloaded nextOrThrow methods to KiwiJdbc
* The different variants allow for a default message, a custom
  message, and a message template with arguments

Closes #1074
  • Loading branch information
sleberknight authored Nov 18, 2023
1 parent 7b13e28 commit 17b3ffd
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/main/java/org/kiwiproject/jdbc/KiwiJdbc.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package org.kiwiproject.jdbc;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;
import static org.kiwiproject.base.KiwiStrings.format;

import lombok.experimental.UtilityClass;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.kiwiproject.base.KiwiPrimitives;
import org.kiwiproject.base.KiwiPrimitives.BooleanConversionOption;
import org.kiwiproject.base.KiwiStrings;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
Expand All @@ -29,6 +32,51 @@
@UtilityClass
public class KiwiJdbc {

/**
* Attempt to call {@link ResultSet#next()} on the given ResultSet, and throw an {@link IllegalStateException}
* if the result set was not advanced.
*
* @param rs the ResultSet
* @throws SQLException if a database access error occurs or this method is called on a closed result set
* (copied from {@link ResultSet#next()})
* @throws IllegalStateException if the result set was not advanced
*/
public static void nextOrThrow(ResultSet rs) throws SQLException {
nextOrThrow(rs, "ResultSet.next() returned false");
}

/**
* Attempt to call {@link ResultSet#next()} on the given ResultSet, and throw an {@link IllegalStateException}
* if the result set was not advanced.
*
* @param rs the ResultSet
* @param message the error message in case the result set cannot be advanced
* @throws SQLException if a database access error occurs or this method is called on a closed result set
* (copied from {@link ResultSet#next()})
* @throws IllegalStateException if the result set was not advanced
*/
public static void nextOrThrow(ResultSet rs, String message) throws SQLException {
checkState(rs.next(), message);
}

/**
* Attempt to call {@link ResultSet#next()} on the given ResultSet, and throw an {@link IllegalStateException}
* if the result set was not advanced.
*
* @param rs the ResultSet
* @param messageTemplate the error message template in case the result set cannot be advanced, according to how
* {@link KiwiStrings#format(String, Object...)} handles placeholders
* @param args the arguments to be substituted into the message template
* @throws SQLException if a database access error occurs or this method is called on a closed result set
* (copied from {@link ResultSet#next()})
* @throws IllegalStateException if the result set was not advanced
*/
public static void nextOrThrow(ResultSet rs, String messageTemplate, Object... args) throws SQLException {
if (!rs.next()) {
throw new IllegalStateException(format(messageTemplate, args));
}
}

/**
* Convert the timestamp column given by {@code columnName} in the {@link ResultSet} to milliseconds
* from the epoch.
Expand Down
82 changes: 82 additions & 0 deletions src/test/java/org/kiwiproject/jdbc/KiwiJdbcTest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package org.kiwiproject.jdbc;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -37,6 +41,84 @@
@DisplayName("KiwiJdbc")
class KiwiJdbcTest {

@Nested
class NextOrThrow {

private ResultSet resultSet;

@BeforeEach
void setUp() {
resultSet = mock(ResultSet.class);
}

@Test
void shouldAdvanceResultSet() throws SQLException {
when(resultSet.next()).thenReturn(true);

assertThatCode(() -> KiwiJdbc.nextOrThrow(resultSet))
.doesNotThrowAnyException();

verify(resultSet, only()).next();
}

@Test
void shouldAdvanceResultSet_WithCustomMessageArgument() throws SQLException {
when(resultSet.next()).thenReturn(true);

assertThatCode(() -> KiwiJdbc.nextOrThrow(resultSet, "failed to advance ResultSet"))
.doesNotThrowAnyException();

verify(resultSet, only()).next();
}

@Test
void shouldAdvanceResultSet_WithCustomMessageTemplateAndArguments() throws SQLException {
when(resultSet.next()).thenReturn(true);

assertThatCode(() -> KiwiJdbc.nextOrThrow(resultSet, "{} with id {} was not found", "Order", "12345ABC"))
.doesNotThrowAnyException();

verify(resultSet, only()).next();
}

@Test
void shouldThrowIllegalState_WhenNextReturnsFalse() throws SQLException {
when(resultSet.next()).thenReturn(false);

assertThatIllegalStateException()
.isThrownBy(() -> KiwiJdbc.nextOrThrow(resultSet))
.withMessage("ResultSet.next() returned false");

verify(resultSet, only()).next();
}

@Test
void shouldThrowIllegalState_WithCustomMessage_WhenNextReturnsFalse() throws SQLException {
when(resultSet.next()).thenReturn(false);

assertThatIllegalStateException()
.isThrownBy(() -> KiwiJdbc.nextOrThrow(resultSet, "record was not found"))
.withMessage("record was not found");

verify(resultSet, only()).next();
}

@ParameterizedTest
@ValueSource(strings = {
"{} with id {} was not found",
"%s with id %s was not found"
})
void shouldThrowIllegalState_WithCustomMessageTemplate_WhenNextReturnsFalse(String template) throws SQLException {
when(resultSet.next()).thenReturn(false);

assertThatIllegalStateException()
.isThrownBy(() -> KiwiJdbc.nextOrThrow(resultSet, template, "Item", 42))
.withMessage("Item with id 42 was not found");

verify(resultSet, only()).next();
}
}

@Nested
class EpochMillisFromTimestamp {

Expand Down

0 comments on commit 17b3ffd

Please sign in to comment.