org.jdbi.v3.vavr.VavrTupleRowMapperFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jdbi3-vavr Show documentation
Show all versions of jdbi3-vavr Show documentation
Vavr is a functional programming library for the JVM.
It is greatly inspired by scala and brings persistent,
immutable datastructures to the Java World
/*
* 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
*
* http://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 org.jdbi.v3.vavr;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.Optional;
import io.vavr.CheckedFunction1;
import io.vavr.Tuple;
import io.vavr.Tuple0;
import io.vavr.Tuple1;
import io.vavr.Tuple2;
import io.vavr.Tuple3;
import io.vavr.Tuple4;
import io.vavr.Tuple5;
import io.vavr.Tuple6;
import io.vavr.Tuple7;
import io.vavr.Tuple8;
import io.vavr.collection.Array;
import io.vavr.control.Option;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.mapper.ColumnMappers;
import org.jdbi.v3.core.mapper.NoSuchMapperException;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.mapper.RowMapperFactory;
import org.jdbi.v3.core.mapper.RowMappers;
import org.jdbi.v3.core.mapper.SingleColumnMapper;
import static org.jdbi.v3.core.generic.GenericTypes.getErasedType;
import static org.jdbi.v3.core.generic.GenericTypes.resolveType;
class VavrTupleRowMapperFactory implements RowMapperFactory {
@Override
public Optional> build(Type type, ConfigRegistry config) {
Class> erasedType = getErasedType(type);
boolean emptyTupleType = Tuple0.class.equals(erasedType) || Tuple.class.equals(erasedType);
boolean mappableTupleType = type instanceof ParameterizedType && Tuple.class.isAssignableFrom(erasedType);
if (mappableTupleType && !emptyTupleType) {
Class extends Tuple> tupleClass = (Class extends Tuple>) erasedType;
Array> tupleTypes = Array.of(tupleClass.getTypeParameters())
.map(tp -> resolveType(tp, type))
.zipWithIndex((t, i) -> Tuple.of(t, i + 1));
Array>> withConfiguredColumnName;
if (Tuple2.class.equals(erasedType)) {
withConfiguredColumnName = resolveKeyValueColumns(config, tupleTypes);
} else {
withConfiguredColumnName = tupleTypes
.map(t -> Tuple.of(t._1, t._2, getConfiguredColumnName(t._2, config)));
}
boolean anyColumnSet = withConfiguredColumnName.map(t -> t._3).exists(Option::isDefined);
if (anyColumnSet) {
Array>> mappers = withConfiguredColumnName
.map(t -> t._3.isDefined()
? getColumnMapperForDefinedColumn(t._1, t._3.get(), config)
: getRowMapper(t._1, config));
boolean mappableWithConfigured = mappers.forAll(Optional::isPresent);
if (mappableWithConfigured) {
return buildMapper(tupleClass, mappers);
}
Array configuredColumns = withConfiguredColumnName
.map(t -> t._2 + ": " + t._3.getOrNull());
throw new NoSuchMapperException(type + " cannot be mapped. "
+ "If tuple columns are configured (TupleMappers config class), "
+ "each tuple entry must be mappable via "
+ "specified column name or existing RowMapper. "
+ "Currently configured: " + configuredColumns.mkString(", "));
} else {
Array>> colMappers = tupleTypes
.map(t -> getColumnMapper(t._1, t._2, config));
boolean mappableByColumn = colMappers.forAll(Optional::isPresent);
if (mappableByColumn) {
return buildMapper(tupleClass, colMappers);
}
Array>> rowMappers = tupleTypes
.map(t -> getRowMapper(t._1, config));
boolean mappableByRowMappers = rowMappers.forAll(Optional::isPresent);
if (mappableByRowMappers) {
return buildMapper(tupleClass, rowMappers);
}
throw new NoSuchMapperException(type + " cannot be mapped. "
+ "All tuple elements must be mappable by ColumnMapper or all by RowMapper. "
+ "If you want to mix column- and rowmapped entries, you must configure "
+ "columns via TupleMappers config class");
}
}
return Optional.empty();
}
Array>> resolveKeyValueColumns(ConfigRegistry config, Array> tupleTypes) {
Array>> withConfiguredColumnName;
Tuple2 keyType = tupleTypes.get(0);
Tuple2 valueType = tupleTypes.get(1);
withConfiguredColumnName = Array.of(
Tuple.of(keyType._1, keyType._2, Option.of(config.get(TupleMappers.class).getKeyColumn())),
Tuple.of(valueType._1, valueType._2, Option.of(config.get(TupleMappers.class).getValueColumn()))
);
return withConfiguredColumnName;
}
private Optional> buildMapper(Class extends Tuple> tupleClass, Array>> colMappers) {
Array extends RowMapper>> cms = colMappers.map(Optional::get);
return Optional.of((rs, ctx) ->
buildTuple(tupleClass, i -> cms.get(i).map(rs, ctx)));
}
private Tuple buildTuple(Class extends Tuple> tupleClass, MapperValueResolver r) throws SQLException {
if (Tuple1.class.equals(tupleClass)) {
return Tuple.of(r.apply(0));
} else if (Tuple2.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1));
} else if (Tuple3.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1), r.apply(2));
} else if (Tuple4.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1), r.apply(2), r.apply(3));
} else if (Tuple5.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1), r.apply(2), r.apply(3), r.apply(4));
} else if (Tuple6.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1), r.apply(2), r.apply(3), r.apply(4), r.apply(5));
} else if (Tuple7.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1), r.apply(2), r.apply(3), r.apply(4), r.apply(5), r.apply(6));
} else if (Tuple8.class.equals(tupleClass)) {
return Tuple.of(r.apply(0), r.apply(1), r.apply(2), r.apply(3), r.apply(4), r.apply(5), r.apply(6), r.apply(7));
}
throw new IllegalArgumentException("unknown tuple type " + tupleClass);
}
Optional> getColumnMapper(Type type, int tupleIndex, ConfigRegistry config) {
int colIndex = tupleIndex;
return config.get(ColumnMappers.class)
.findFor(type)
.map(cm -> new SingleColumnMapper<>(cm, colIndex));
}
private Optional> getRowMapper(Type type, ConfigRegistry config) {
return config.get(RowMappers.class).findFor(type);
}
private Optional> getColumnMapperForDefinedColumn(Type type, String col, ConfigRegistry config) {
return config
.get(ColumnMappers.class)
.findFor(type)
.map(cm -> new SingleColumnMapper<>(cm, col));
}
Option getConfiguredColumnName(int tupleIndex, ConfigRegistry config) {
return Option.of(config.get(TupleMappers.class)
.getColumn(tupleIndex));
}
private interface MapperValueResolver extends CheckedFunction1 {
/**
* @param tupleIndex the 1-based tuple index
* @return the value that should be resolvable via tuple._tupleIndex
* @throws SQLException if the underlying mapper cannot resolve the value
*/
@Override
Object apply(Integer tupleIndex) throws SQLException;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy