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
4 changes: 2 additions & 2 deletions src/main/java/no/digipost/jdbc/Mappers.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public final class Mappers {
/**
* Combination of {@link #getTimestamp} and a conversion to an {@link Instant} using {@link Timestamp#toInstant()}.
*/
public static final BasicColumnMapper<Instant> getInstant = getTimestamp.andThen(Timestamp::toInstant);
public static final BasicColumnMapper<Instant> getInstant = getTimestamp.nullFallthrough().andThen(Timestamp::toInstant);
/**
* Combination of {@link #getNullableTimestamp} and a conversion to an {@link Instant}
* using {@link Timestamp#toInstant()}.
Expand Down Expand Up @@ -231,7 +231,7 @@ public final class Mappers {
* @see #getIntArray
* @see #getLongArray
*/
public static final BasicColumnMapper<Object> getArray = (name, rs) -> getSqlArray.andThen(SqlArray::of).map(name, rs).consume(Array::getArray);
public static final BasicColumnMapper<Object> getArray = (name, rs) -> getSqlArray.andThen(SqlArray::of).map(name, rs).consume(a -> a != null ? a.getArray() : null);

/**
* Gets the value of a given SQL {@code ARRAY} column as an {@code String[]} array.
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/no/digipost/jdbc/SqlArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ <R> R consume(ThrowingFunction<Array, R, SQLException> sqlArrayConsumer) throws
try {
return sqlArrayConsumer.apply(array);
} finally {
array.free();
if (array != null) {
array.free();
}
}
}

Expand Down
46 changes: 33 additions & 13 deletions src/test/java/no/digipost/jdbc/MappersTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@
package no.digipost.jdbc;

import com.mockrunner.mock.jdbc.MockResultSet;
import no.digipost.collection.NonEmptyList;
import org.junit.jupiter.api.Test;

import java.sql.SQLException;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Stream;

import static uk.co.probablyfine.matchers.StreamMatchers.allMatch;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static no.digipost.DiggCollectors.toNonEmptyList;
import static no.digipost.DiggExceptions.applyUnchecked;
import static no.digipost.jdbc.Mappers.getBoolean;
import static no.digipost.jdbc.Mappers.getByte;
Expand All @@ -40,28 +44,44 @@
import static no.digipost.jdbc.Mappers.getShort;
import static no.digipost.jdbc.ResultSetMock.mockSingleColumnResult;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertAll;
import static uk.co.probablyfine.matchers.Java8Matchers.whereNot;

public class MappersTest {
class MappersTest {

@Test
public void recognizesSQL_NULLsInPrimitiveMappers() throws SQLException {
void recognizesSQL_NULLsInPrimitiveMappers() throws SQLException {
try (MockResultSet rs = mockSingleColumnResult("value", new Object[] { null })) {
Stream<Optional<?>> results =
Stream.of(getNullableInt, getNullableBoolean, getNullableByte, getNullableDouble, getNullableFloat, getNullableLong, getNullableShort)
.map((NullableColumnMapper<?> nullableMapper) -> applyUnchecked(mapper -> mapper.map("value", rs), nullableMapper));
assertThat(results, allMatch(is(Optional.empty())));
assertAll(Stream.of(getNullableInt, getNullableBoolean, getNullableByte, getNullableDouble, getNullableFloat, getNullableLong, getNullableShort)
.map(nullableMapper -> applyUnchecked(mapper -> mapper.map("value", rs), nullableMapper))
.map(mappedValue -> () -> assertThat(mappedValue, whereNot(Optional::isPresent))));
}
}

@Test
public void correctlyHandlesResultSetIckySQL_NULLHandling() throws SQLException {
void correctlyHandlesResultSetIckySQL_NULLHandling() throws SQLException {
try (MockResultSet rs = mockSingleColumnResult("value", new Object[] { null })) {
Stream<Object> results =
Stream.of(getInt, getBoolean, getByte, getDouble, getFloat, getLong, getShort)
.map((BasicColumnMapper<?> basicMapper) -> applyUnchecked(mapper -> mapper.map("value", rs), basicMapper));
assertThat(results, allMatch(nullValue()));
assertAll(Stream.of(getInt, getBoolean, getByte, getDouble, getFloat, getLong, getShort)
.map(primitiveMapper -> applyUnchecked(mapper -> mapper.map("value", rs), primitiveMapper))
.map(mappedValue -> () -> assertThat(mappedValue, nullValue())));
}
}

@Test
void allBasicColumnMappersAreNullSafe() throws SQLException {
NonEmptyList<? extends BasicColumnMapper<?>> publicBasicMappers = Stream.of(Mappers.class.getFields())
.filter(field -> isStatic(field.getModifiers()) && isPublic(field.getModifiers()) && BasicColumnMapper.class.isAssignableFrom(field.getType()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fancy!

.map(publicBasicMapperField -> (BasicColumnMapper<?>) applyUnchecked(publicBasicMapperField::get, Mappers.class))
.collect(toNonEmptyList())
.orElseThrow(() -> new NoSuchElementException("found no public " + BasicColumnMapper.class.getSimpleName() + " fields in " + Mappers.class.getName()));

try (MockResultSet rs = mockSingleColumnResult("value", new Object[] { null })) {
assertAll(publicBasicMappers.stream()
.map(basicMapper -> () -> {
Object mappedValue = applyUnchecked(mapper -> mapper.map("value", rs), basicMapper);
assertThat(mappedValue, nullValue());
}));
}
}
}